summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2023-11-23 01:47:18 +0000
committerStar Rauchenberger <fefferburbia@gmail.com>2023-11-23 01:47:18 +0000
commit4e206fae5be6093b36c7779ba2a4a3679f0e754b (patch)
tree3a1377ae7781debeb04c17464f9eab13f7e47131
downloadgodot3-tscn-compactor-4e206fae5be6093b36c7779ba2a4a3679f0e754b.tar.gz
godot3-tscn-compactor-4e206fae5be6093b36c7779ba2a4a3679f0e754b.tar.bz2
godot3-tscn-compactor-4e206fae5be6093b36c7779ba2a4a3679f0e754b.zip
something
-rw-r--r--.clang-format2
-rw-r--r--.gitignore11
-rw-r--r--CMakeLists.txt8
-rw-r--r--heading.cpp121
-rw-r--r--heading.h41
-rw-r--r--main.cpp98
-rw-r--r--scene.cpp139
-rw-r--r--scene.h34
-rw-r--r--script.cpp106
-rw-r--r--script.h36
10 files changed, 596 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8de7fe6 --- /dev/null +++ b/.clang-format
@@ -0,0 +1,2 @@
1---
2 BasedOnStyle: Google
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f85f339 --- /dev/null +++ b/.gitignore
@@ -0,0 +1,11 @@
1build
2CMakeFiles
3cmake_install.cmake
4CMakeCache.txt
5compile_commands.json
6Makefile
7[._]*.s[a-v][a-z]
8[._]*.sw[a-p]
9[._]s[a-rt-v][a-z]
10[._]ss[a-gi-z]
11[._]sw[a-p]
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b4f5e05 --- /dev/null +++ b/CMakeLists.txt
@@ -0,0 +1,8 @@
1cmake_minimum_required (VERSION 3.9)
2project (godot3_tscn_compactor)
3
4set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
5
6add_executable(godot3_tscn_compactor main.cpp heading.cpp scene.cpp script.cpp)
7set_property(TARGET godot3_tscn_compactor PROPERTY CXX_STANDARD 17)
8set_property(TARGET godot3_tscn_compactor PROPERTY CXX_STANDARD_REQUIRED ON)
diff --git a/heading.cpp b/heading.cpp new file mode 100644 index 0000000..ff4a1a6 --- /dev/null +++ b/heading.cpp
@@ -0,0 +1,121 @@
1#include <stdlib.h>
2
3#include <sstream>
4#include <stdexcept>
5#include <string>
6#include <string_view>
7
8#include "heading.h"
9
10Heading::Heading(std::string_view line) {
11 std::string original_line(line);
12
13 if (line[0] != '[') {
14 std::ostringstream errormsg;
15 errormsg << "Heading must start with [." << std::endl
16 << "Bad heading: " << original_line;
17 throw std::invalid_argument(errormsg.str());
18 }
19
20 line.remove_prefix(1);
21 int divider = line.find_first_of(" ]");
22 if (divider == std::string_view::npos) {
23 std::ostringstream errormsg;
24 errormsg << "Malformatted heading: " << line << std::endl
25 << "Original line: " << original_line;
26 throw std::invalid_argument(errormsg.str());
27 }
28
29 type_ = line.substr(0, divider);
30 line.remove_prefix(divider + 1);
31
32 while (!line.empty()) {
33 divider = line.find_first_of("=");
34 if (divider == std::string_view::npos) {
35 std::ostringstream errormsg;
36 errormsg << "Malformatted heading: " << line << std::endl
37 << "Original line: " << original_line;
38 throw std::invalid_argument(errormsg.str());
39 }
40
41 std::string key(line.substr(0, divider));
42 line.remove_prefix(divider + 1);
43
44 if (line[0] == '"') {
45 line.remove_prefix(1);
46 divider = line.find_first_of("\"");
47
48 if (divider == std::string_view::npos) {
49 std::ostringstream errormsg;
50 errormsg << "Malformatted heading: " << line << std::endl
51 << "Original line: " << original_line;
52 throw std::invalid_argument(errormsg.str());
53 }
54
55 std::string strval(line.substr(0, divider));
56 line.remove_prefix(divider + 2);
57
58 keyvals_[key] = strval;
59 } else if (line[0] == 'S' || line[0] == 'E') {
60 ResourceRef rrval;
61 rrval.internal = (line[0] == 'S');
62
63 line.remove_prefix(13); // SubResource(#
64 divider = line.find_first_of(" ");
65
66 if (divider == std::string_view::npos) {
67 std::ostringstream errormsg;
68 errormsg << "Malformatted heading: " << line << std::endl
69 << "Original line: " << original_line;
70 throw std::invalid_argument(errormsg.str());
71 }
72
73 rrval.id = std::atoi(line.substr(0, divider).data());
74 line.remove_prefix(divider + 3);
75
76 keyvals_[key] = rrval;
77 } else {
78 divider = line.find_first_of(" ]");
79
80 if (divider == std::string_view::npos) {
81 std::ostringstream errormsg;
82 errormsg << "Malformatted heading: " << line << std::endl
83 << "Original line: " << original_line;
84 throw std::invalid_argument(errormsg.str());
85 }
86
87 int numval = std::atoi(line.substr(0, divider).data());
88 line.remove_prefix(divider + 1);
89
90 keyvals_[key] = numval;
91 }
92 }
93}
94
95std::string Heading::ToString() const {
96 std::ostringstream output;
97 output << "[";
98 output << type_;
99
100 for (const auto& [key, value] : keyvals_) {
101 output << " " << key << "=";
102
103 if (std::holds_alternative<int>(value)) {
104 output << std::get<int>(value);
105 } else if (std::holds_alternative<ResourceRef>(value)) {
106 const auto& rrval = std::get<ResourceRef>(value);
107 if (rrval.internal) {
108 output << "Sub";
109 } else {
110 output << "Ext";
111 }
112 output << "Resource( " << rrval.id << " )";
113 } else {
114 output << "\"" << std::get<std::string>(value) << "\"";
115 }
116 }
117
118 output << "]" << std::endl;
119
120 return output.str();
121}
diff --git a/heading.h b/heading.h new file mode 100644 index 0000000..1b1781b --- /dev/null +++ b/heading.h
@@ -0,0 +1,41 @@
1#ifndef HEADING_H
2#define HEADING_H
3
4#include <map>
5#include <string>
6#include <string_view>
7#include <variant>
8
9struct ResourceRef {
10 bool internal = false;
11 int id;
12};
13
14class Heading {
15 public:
16 using keyval_t = std::variant<std::string, int, ResourceRef>;
17
18 Heading() = default;
19
20 explicit Heading(std::string_view line);
21
22 const std::string& type() const { return type_; }
23
24 void set_type(std::string_view type) { type_ = std::string(type); }
25
26 const keyval_t& GetKeyval(std::string_view key) const {
27 return keyvals_.at(std::string(key));
28 }
29
30 void SetKeyval(std::string_view key, const keyval_t& value) {
31 keyvals_[std::string(key)] = value;
32 }
33
34 std::string ToString() const;
35
36 private:
37 std::string type_;
38 std::map<std::string, keyval_t> keyvals_;
39};
40
41#endif /* HEADING_H */
diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..9da0c40 --- /dev/null +++ b/main.cpp
@@ -0,0 +1,98 @@
1#include <filesystem>
2#include <fstream>
3#include <iostream>
4#include <list>
5#include <utility>
6
7#include "heading.h"
8#include "scene.h"
9#include "script.h"
10
11struct EmbeddedScript {
12 Script script;
13 bool internal = false; // true if referred from within another script
14 int id = -1; // if internal is false, ExtResource id
15 std::string path; // if internal is true
16};
17
18void show_usage() {
19 std::cout << "godot3_tscn_compactor [path to root tscn] [file prefix]"
20 << std::endl;
21}
22
23int main(int argc, char** argv) {
24 if (argc != 3) {
25 show_usage();
26 return 0;
27 }
28
29 std::filesystem::path root_tscn_path(argv[1]);
30 std::string file_prefix(argv[2]);
31
32 Scene root_tscn(root_tscn_path);
33
34 std::vector<EmbeddedScript> sub_scripts;
35
36 for (const auto& [id, heading] : root_tscn.ext_resources()) {
37 std::string resource_filename =
38 std::get<std::string>(heading.GetKeyval("path"));
39 if (resource_filename.substr(0, file_prefix.size()) == file_prefix) {
40 std::filesystem::path relative_filename =
41 resource_filename.substr(file_prefix.size());
42 std::filesystem::path resource_path =
43 root_tscn_path.parent_path() / relative_filename;
44
45 EmbeddedScript embedded_script;
46 embedded_script.script = Script(resource_path, file_prefix);
47 embedded_script.id = id;
48
49 sub_scripts.push_back(std::move(embedded_script));
50 }
51 }
52
53 std::list<EmbeddedScript> all_scripts;
54 std::vector<EmbeddedScript> scripts_to_check = sub_scripts;
55 while (!scripts_to_check.empty()) {
56 std::vector<EmbeddedScript> cur_scripts;
57 cur_scripts.swap(scripts_to_check);
58
59 for (const EmbeddedScript& embedded_script : cur_scripts) {
60 all_scripts.push_front(embedded_script);
61
62 for (const std::string& loaded_script_path :
63 embedded_script.script.loaded_scripts()) {
64 std::filesystem::path resource_path =
65 root_tscn_path.parent_path() / loaded_script_path;
66
67 EmbeddedScript embedded_script;
68 embedded_script.script = Script(resource_path, file_prefix);
69 embedded_script.internal = true;
70 embedded_script.path = loaded_script_path;
71
72 scripts_to_check.push_back(std::move(embedded_script));
73 }
74 }
75 }
76
77 std::map<std::string, int> new_sub_scripts;
78 for (EmbeddedScript embedded_script : all_scripts) {
79 embedded_script.script.ReplaceScriptReferences(new_sub_scripts);
80 int sub_resource_id = root_tscn.AddSubResource(embedded_script.script);
81
82 if (embedded_script.internal) {
83 new_sub_scripts[embedded_script.path] = sub_resource_id;
84 } else {
85 root_tscn.RemoveExtResource(embedded_script.id);
86 root_tscn.ReplaceScriptReferences(embedded_script.id, sub_resource_id);
87 }
88 }
89
90 std::filesystem::path output_name = root_tscn_path;
91 output_name.replace_filename(std::string(output_name.stem()) +
92 "_compiled.tscn");
93
94 std::ofstream output_stream(output_name);
95 output_stream << root_tscn.ToString();
96
97 return 0;
98}
diff --git a/scene.cpp b/scene.cpp new file mode 100644 index 0000000..6eae537 --- /dev/null +++ b/scene.cpp
@@ -0,0 +1,139 @@
1#include <filesystem>
2#include <fstream>
3#include <iomanip>
4#include <iostream>
5#include <map>
6#include <sstream>
7#include <stdexcept>
8#include <string_view>
9#include <variant>
10
11#include "heading.h"
12#include "scene.h"
13
14Scene::Scene(const std::filesystem::path& path) {
15 std::ifstream input(path.c_str());
16 std::string line;
17 bool section_started = false;
18 Heading cur_heading;
19 std::ostringstream cur_value;
20 bool value_started = false;
21 auto handle_end_of_section = [&]() {
22 section_started = false;
23 value_started = false;
24
25 if (cur_heading.type() == "sub_resource") {
26 sub_resources_[std::get<int>(cur_heading.GetKeyval("id"))] = {
27 cur_heading, cur_value.str()};
28 } else {
29 other_.emplace_back(cur_heading, cur_value.str());
30 }
31
32 cur_value = {};
33 };
34 while (std::getline(input, line)) {
35 if (section_started && (line.empty() || line[0] == '[')) {
36 handle_end_of_section();
37 }
38 if (!line.empty() && line[0] == '[') {
39 Heading heading(line);
40 if (heading.type() == "gd_scene") {
41 file_descriptor_ = heading;
42 } else if (heading.type() == "ext_resource") {
43 ext_resources_[std::get<int>(heading.GetKeyval("id"))] = heading;
44 } else {
45 cur_heading = heading;
46 section_started = true;
47 }
48 } else if (!line.empty()) {
49 if (value_started) {
50 cur_value << std::endl;
51 } else {
52 value_started = true;
53 }
54 cur_value << line;
55 }
56 }
57 if (section_started) {
58 handle_end_of_section();
59 }
60
61 for (const auto& [id, stuff] : sub_resources_) {
62 if (id >= next_sub_id_) {
63 next_sub_id_ = id + 1;
64 }
65 }
66}
67
68int Scene::AddSubResource(const Script& script) {
69 int new_id = next_sub_id_++;
70
71 std::ostringstream resource_contents;
72 for (const auto& [variable, value] : script.exports()) {
73 resource_contents << variable << " = SubResource( " << value << " )"
74 << std::endl;
75 }
76 resource_contents << "script/source = " << std::quoted(script.ToString());
77
78 Heading heading;
79 heading.set_type("sub_resource");
80 heading.SetKeyval("type", "GDScript");
81 heading.SetKeyval("id", new_id);
82
83 sub_resources_[new_id] = {heading, resource_contents.str()};
84
85 return new_id;
86}
87
88void Scene::RemoveExtResource(int id) { ext_resources_.erase(id); }
89
90void Scene::ReplaceScriptReferences(int ext_id, int sub_id) {
91 auto replace_refs = [&](std::string& text) {
92 std::ostringstream search_str;
93 search_str << "ExtResource( " << ext_id << " )";
94 std::string search = search_str.str();
95
96 std::ostringstream new_text;
97 std::string_view corpus = text;
98 int location;
99 while ((location = corpus.find(search)) != std::string_view::npos) {
100 new_text << corpus.substr(0, location) << "SubResource( " << sub_id
101 << " )";
102 corpus.remove_prefix(location + search.size());
103 }
104 new_text << corpus;
105
106 text = new_text.str();
107 };
108
109 for (auto& [id, stuff] : sub_resources_) {
110 auto& [heading, text] = stuff;
111 replace_refs(text);
112 }
113
114 for (auto& [heading, text] : other_) {
115 replace_refs(text);
116 }
117}
118
119std::string Scene::ToString() const {
120 std::ostringstream output;
121 output << file_descriptor_.ToString() << std::endl;
122
123 for (const auto& [id, heading] : ext_resources_) {
124 output << heading.ToString();
125 }
126
127 output << std::endl;
128
129 for (const auto& [id, resource] : sub_resources_) {
130 const auto& [heading, value] = resource;
131 output << heading.ToString() << value << std::endl << std::endl;
132 }
133
134 for (const auto& [heading, value] : other_) {
135 output << heading.ToString() << value << std::endl << std::endl;
136 }
137
138 return output.str();
139}
diff --git a/scene.h b/scene.h new file mode 100644 index 0000000..0ee9450 --- /dev/null +++ b/scene.h
@@ -0,0 +1,34 @@
1#ifndef SCENE_H
2#define SCENE_H
3
4#include <filesystem>
5#include <map>
6#include <tuple>
7#include <vector>
8
9#include "heading.h"
10#include "script.h"
11
12class Scene {
13 public:
14 explicit Scene(const std::filesystem::path& path);
15
16 const std::map<int, Heading>& ext_resources() const { return ext_resources_; }
17
18 int AddSubResource(const Script& script);
19
20 void RemoveExtResource(int id);
21
22 void ReplaceScriptReferences(int ext_id, int sub_id);
23
24 std::string ToString() const;
25
26 private:
27 Heading file_descriptor_;
28 std::map<int, Heading> ext_resources_;
29 std::map<int, std::tuple<Heading, std::string>> sub_resources_;
30 std::vector<std::tuple<Heading, std::string>> other_;
31 int next_sub_id_ = 0;
32};
33
34#endif /* SCENE_H */
diff --git a/script.cpp b/script.cpp new file mode 100644 index 0000000..509d4a7 --- /dev/null +++ b/script.cpp
@@ -0,0 +1,106 @@
1#include <filesystem>
2#include <fstream>
3#include <map>
4#include <sstream>
5#include <stdexcept>
6#include <string>
7#include <string_view>
8
9#include "script.h"
10
11Script::Script(const std::filesystem::path& path, std::string_view file_prefix)
12 : file_prefix_(file_prefix) {
13 {
14 std::ifstream script_file(path);
15 std::string line;
16 std::ostringstream text_stream;
17 while (std::getline(script_file, line)) {
18 text_stream << line << std::endl;
19 }
20 text_ = text_stream.str();
21 }
22
23 std::string search = std::string("load(\"") + std::string(file_prefix);
24 std::string_view corpus = text_;
25 int location;
26 while ((location = corpus.find(search)) != std::string_view::npos) {
27 corpus.remove_prefix(location + search.size());
28 int divider = corpus.find_first_of("\"");
29 if (divider == std::string_view::npos) {
30 throw std::invalid_argument(
31 "I don't know how this is happening but I guess I gotta report it.");
32 }
33 std::string script_path(corpus.substr(0, divider));
34 corpus.remove_prefix(divider + 1);
35 loaded_scripts_.push_back(script_path);
36 }
37}
38
39void Script::ReplaceScriptReferences(
40 const std::map<std::string, int>& script_ids) {
41 std::ostringstream new_text;
42 std::string_view corpus = text_;
43 int divider = corpus.find_first_of("\n");
44 if (divider == std::string_view::npos) {
45 throw std::invalid_argument("Script is one line??");
46 }
47 new_text << corpus.substr(0, divider + 1) << std::endl;
48 ;
49 corpus.remove_prefix(divider + 1);
50
51 std::map<std::string, std::string> path_to_export;
52 for (const std::string& loaded_script : loaded_scripts_) {
53 std::ostringstream export_str;
54 export_str << "COMPACTED";
55
56 std::filesystem::path name_path(loaded_script);
57 name_path.replace_filename(name_path.stem());
58 for (const auto& segment : name_path) {
59 std::string segment_str(segment);
60 export_str << "_" << segment_str;
61 }
62
63 std::string export_name = export_str.str();
64 exports_.emplace_back(export_name, script_ids.at(loaded_script));
65
66 new_text << "export(Resource) var " << export_name << std::endl;
67
68 path_to_export[loaded_script] = export_name;
69 }
70
71 new_text << std::endl;
72
73 std::string search = std::string("load(\"") + file_prefix_;
74 std::string search_alt1 = std::string("preload(\"") + file_prefix_;
75 std::string search_alt2 =
76 std::string("ResourceLoader.load(\"") + file_prefix_;
77 int location;
78 while ((location = corpus.find(search)) != std::string_view::npos) {
79 int alt1 = corpus.find(search_alt1);
80 int alt2 = corpus.find(search_alt2);
81 int remove_size = search.size();
82
83 if (alt1 != std::string_view::npos && alt1 < location) {
84 location = alt1;
85 remove_size = search_alt1.size();
86 } else if (alt2 != std::string_view::npos && alt2 < location) {
87 location = alt2;
88 remove_size = search_alt2.size();
89 }
90
91 new_text << corpus.substr(0, location);
92 corpus.remove_prefix(location + remove_size);
93
94 int divider = corpus.find_first_of("\"");
95 // TODO: ehhh error reporting
96 std::string referenced_path(corpus.substr(0, divider));
97 corpus.remove_prefix(divider + 2); // the quote and the close paren
98 new_text << path_to_export.at(referenced_path);
99 }
100
101 new_text << corpus;
102
103 text_ = new_text.str();
104}
105
106std::string Script::ToString() const { return text_; }
diff --git a/script.h b/script.h new file mode 100644 index 0000000..fabf531 --- /dev/null +++ b/script.h
@@ -0,0 +1,36 @@
1#ifndef SCRIPT_H
2#define SCRIPT_H
3
4#include <filesystem>
5#include <map>
6#include <string>
7#include <string_view>
8#include <tuple>
9#include <vector>
10
11class Script {
12 public:
13 Script() = default;
14
15 Script(const std::filesystem::path& path, std::string_view file_prefix);
16
17 const std::vector<std::string>& loaded_scripts() const {
18 return loaded_scripts_;
19 }
20
21 const std::vector<std::tuple<std::string, int>>& exports() const {
22 return exports_;
23 }
24
25 void ReplaceScriptReferences(const std::map<std::string, int>& script_ids);
26
27 std::string ToString() const;
28
29 private:
30 std::string text_;
31 std::string file_prefix_;
32 std::vector<std::string> loaded_scripts_;
33 std::vector<std::tuple<std::string, int>> exports_;
34};
35
36#endif /* SCRIPT_H */