From 4e206fae5be6093b36c7779ba2a4a3679f0e754b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 23 Nov 2023 01:47:18 +0000 Subject: something --- .clang-format | 2 + .gitignore | 11 +++++ CMakeLists.txt | 8 ++++ heading.cpp | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ heading.h | 41 +++++++++++++++++ main.cpp | 98 ++++++++++++++++++++++++++++++++++++++++ scene.cpp | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scene.h | 34 ++++++++++++++ script.cpp | 106 +++++++++++++++++++++++++++++++++++++++++++ script.h | 36 +++++++++++++++ 10 files changed, 596 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 heading.cpp create mode 100644 heading.h create mode 100644 main.cpp create mode 100644 scene.cpp create mode 100644 scene.h create mode 100644 script.cpp create mode 100644 script.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8de7fe6 --- /dev/null +++ b/.clang-format @@ -0,0 +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 @@ +build +CMakeFiles +cmake_install.cmake +CMakeCache.txt +compile_commands.json +Makefile +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]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 @@ +cmake_minimum_required (VERSION 3.9) +project (godot3_tscn_compactor) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_executable(godot3_tscn_compactor main.cpp heading.cpp scene.cpp script.cpp) +set_property(TARGET godot3_tscn_compactor PROPERTY CXX_STANDARD 17) +set_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 @@ +#include + +#include +#include +#include +#include + +#include "heading.h" + +Heading::Heading(std::string_view line) { + std::string original_line(line); + + if (line[0] != '[') { + std::ostringstream errormsg; + errormsg << "Heading must start with [." << std::endl + << "Bad heading: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + line.remove_prefix(1); + int divider = line.find_first_of(" ]"); + if (divider == std::string_view::npos) { + std::ostringstream errormsg; + errormsg << "Malformatted heading: " << line << std::endl + << "Original line: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + type_ = line.substr(0, divider); + line.remove_prefix(divider + 1); + + while (!line.empty()) { + divider = line.find_first_of("="); + if (divider == std::string_view::npos) { + std::ostringstream errormsg; + errormsg << "Malformatted heading: " << line << std::endl + << "Original line: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + std::string key(line.substr(0, divider)); + line.remove_prefix(divider + 1); + + if (line[0] == '"') { + line.remove_prefix(1); + divider = line.find_first_of("\""); + + if (divider == std::string_view::npos) { + std::ostringstream errormsg; + errormsg << "Malformatted heading: " << line << std::endl + << "Original line: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + std::string strval(line.substr(0, divider)); + line.remove_prefix(divider + 2); + + keyvals_[key] = strval; + } else if (line[0] == 'S' || line[0] == 'E') { + ResourceRef rrval; + rrval.internal = (line[0] == 'S'); + + line.remove_prefix(13); // SubResource(# + divider = line.find_first_of(" "); + + if (divider == std::string_view::npos) { + std::ostringstream errormsg; + errormsg << "Malformatted heading: " << line << std::endl + << "Original line: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + rrval.id = std::atoi(line.substr(0, divider).data()); + line.remove_prefix(divider + 3); + + keyvals_[key] = rrval; + } else { + divider = line.find_first_of(" ]"); + + if (divider == std::string_view::npos) { + std::ostringstream errormsg; + errormsg << "Malformatted heading: " << line << std::endl + << "Original line: " << original_line; + throw std::invalid_argument(errormsg.str()); + } + + int numval = std::atoi(line.substr(0, divider).data()); + line.remove_prefix(divider + 1); + + keyvals_[key] = numval; + } + } +} + +std::string Heading::ToString() const { + std::ostringstream output; + output << "["; + output << type_; + + for (const auto& [key, value] : keyvals_) { + output << " " << key << "="; + + if (std::holds_alternative(value)) { + output << std::get(value); + } else if (std::holds_alternative(value)) { + const auto& rrval = std::get(value); + if (rrval.internal) { + output << "Sub"; + } else { + output << "Ext"; + } + output << "Resource( " << rrval.id << " )"; + } else { + output << "\"" << std::get(value) << "\""; + } + } + + output << "]" << std::endl; + + return output.str(); +} diff --git a/heading.h b/heading.h new file mode 100644 index 0000000..1b1781b --- /dev/null +++ b/heading.h @@ -0,0 +1,41 @@ +#ifndef HEADING_H +#define HEADING_H + +#include +#include +#include +#include + +struct ResourceRef { + bool internal = false; + int id; +}; + +class Heading { + public: + using keyval_t = std::variant; + + Heading() = default; + + explicit Heading(std::string_view line); + + const std::string& type() const { return type_; } + + void set_type(std::string_view type) { type_ = std::string(type); } + + const keyval_t& GetKeyval(std::string_view key) const { + return keyvals_.at(std::string(key)); + } + + void SetKeyval(std::string_view key, const keyval_t& value) { + keyvals_[std::string(key)] = value; + } + + std::string ToString() const; + + private: + std::string type_; + std::map keyvals_; +}; + +#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 @@ +#include +#include +#include +#include +#include + +#include "heading.h" +#include "scene.h" +#include "script.h" + +struct EmbeddedScript { + Script script; + bool internal = false; // true if referred from within another script + int id = -1; // if internal is false, ExtResource id + std::string path; // if internal is true +}; + +void show_usage() { + std::cout << "godot3_tscn_compactor [path to root tscn] [file prefix]" + << std::endl; +} + +int main(int argc, char** argv) { + if (argc != 3) { + show_usage(); + return 0; + } + + std::filesystem::path root_tscn_path(argv[1]); + std::string file_prefix(argv[2]); + + Scene root_tscn(root_tscn_path); + + std::vector sub_scripts; + + for (const auto& [id, heading] : root_tscn.ext_resources()) { + std::string resource_filename = + std::get(heading.GetKeyval("path")); + if (resource_filename.substr(0, file_prefix.size()) == file_prefix) { + std::filesystem::path relative_filename = + resource_filename.substr(file_prefix.size()); + std::filesystem::path resource_path = + root_tscn_path.parent_path() / relative_filename; + + EmbeddedScript embedded_script; + embedded_script.script = Script(resource_path, file_prefix); + embedded_script.id = id; + + sub_scripts.push_back(std::move(embedded_script)); + } + } + + std::list all_scripts; + std::vector scripts_to_check = sub_scripts; + while (!scripts_to_check.empty()) { + std::vector cur_scripts; + cur_scripts.swap(scripts_to_check); + + for (const EmbeddedScript& embedded_script : cur_scripts) { + all_scripts.push_front(embedded_script); + + for (const std::string& loaded_script_path : + embedded_script.script.loaded_scripts()) { + std::filesystem::path resource_path = + root_tscn_path.parent_path() / loaded_script_path; + + EmbeddedScript embedded_script; + embedded_script.script = Script(resource_path, file_prefix); + embedded_script.internal = true; + embedded_script.path = loaded_script_path; + + scripts_to_check.push_back(std::move(embedded_script)); + } + } + } + + std::map new_sub_scripts; + for (EmbeddedScript embedded_script : all_scripts) { + embedded_script.script.ReplaceScriptReferences(new_sub_scripts); + int sub_resource_id = root_tscn.AddSubResource(embedded_script.script); + + if (embedded_script.internal) { + new_sub_scripts[embedded_script.path] = sub_resource_id; + } else { + root_tscn.RemoveExtResource(embedded_script.id); + root_tscn.ReplaceScriptReferences(embedded_script.id, sub_resource_id); + } + } + + std::filesystem::path output_name = root_tscn_path; + output_name.replace_filename(std::string(output_name.stem()) + + "_compiled.tscn"); + + std::ofstream output_stream(output_name); + output_stream << root_tscn.ToString(); + + return 0; +} diff --git a/scene.cpp b/scene.cpp new file mode 100644 index 0000000..6eae537 --- /dev/null +++ b/scene.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "heading.h" +#include "scene.h" + +Scene::Scene(const std::filesystem::path& path) { + std::ifstream input(path.c_str()); + std::string line; + bool section_started = false; + Heading cur_heading; + std::ostringstream cur_value; + bool value_started = false; + auto handle_end_of_section = [&]() { + section_started = false; + value_started = false; + + if (cur_heading.type() == "sub_resource") { + sub_resources_[std::get(cur_heading.GetKeyval("id"))] = { + cur_heading, cur_value.str()}; + } else { + other_.emplace_back(cur_heading, cur_value.str()); + } + + cur_value = {}; + }; + while (std::getline(input, line)) { + if (section_started && (line.empty() || line[0] == '[')) { + handle_end_of_section(); + } + if (!line.empty() && line[0] == '[') { + Heading heading(line); + if (heading.type() == "gd_scene") { + file_descriptor_ = heading; + } else if (heading.type() == "ext_resource") { + ext_resources_[std::get(heading.GetKeyval("id"))] = heading; + } else { + cur_heading = heading; + section_started = true; + } + } else if (!line.empty()) { + if (value_started) { + cur_value << std::endl; + } else { + value_started = true; + } + cur_value << line; + } + } + if (section_started) { + handle_end_of_section(); + } + + for (const auto& [id, stuff] : sub_resources_) { + if (id >= next_sub_id_) { + next_sub_id_ = id + 1; + } + } +} + +int Scene::AddSubResource(const Script& script) { + int new_id = next_sub_id_++; + + std::ostringstream resource_contents; + for (const auto& [variable, value] : script.exports()) { + resource_contents << variable << " = SubResource( " << value << " )" + << std::endl; + } + resource_contents << "script/source = " << std::quoted(script.ToString()); + + Heading heading; + heading.set_type("sub_resource"); + heading.SetKeyval("type", "GDScript"); + heading.SetKeyval("id", new_id); + + sub_resources_[new_id] = {heading, resource_contents.str()}; + + return new_id; +} + +void Scene::RemoveExtResource(int id) { ext_resources_.erase(id); } + +void Scene::ReplaceScriptReferences(int ext_id, int sub_id) { + auto replace_refs = [&](std::string& text) { + std::ostringstream search_str; + search_str << "ExtResource( " << ext_id << " )"; + std::string search = search_str.str(); + + std::ostringstream new_text; + std::string_view corpus = text; + int location; + while ((location = corpus.find(search)) != std::string_view::npos) { + new_text << corpus.substr(0, location) << "SubResource( " << sub_id + << " )"; + corpus.remove_prefix(location + search.size()); + } + new_text << corpus; + + text = new_text.str(); + }; + + for (auto& [id, stuff] : sub_resources_) { + auto& [heading, text] = stuff; + replace_refs(text); + } + + for (auto& [heading, text] : other_) { + replace_refs(text); + } +} + +std::string Scene::ToString() const { + std::ostringstream output; + output << file_descriptor_.ToString() << std::endl; + + for (const auto& [id, heading] : ext_resources_) { + output << heading.ToString(); + } + + output << std::endl; + + for (const auto& [id, resource] : sub_resources_) { + const auto& [heading, value] = resource; + output << heading.ToString() << value << std::endl << std::endl; + } + + for (const auto& [heading, value] : other_) { + output << heading.ToString() << value << std::endl << std::endl; + } + + return output.str(); +} diff --git a/scene.h b/scene.h new file mode 100644 index 0000000..0ee9450 --- /dev/null +++ b/scene.h @@ -0,0 +1,34 @@ +#ifndef SCENE_H +#define SCENE_H + +#include +#include +#include +#include + +#include "heading.h" +#include "script.h" + +class Scene { + public: + explicit Scene(const std::filesystem::path& path); + + const std::map& ext_resources() const { return ext_resources_; } + + int AddSubResource(const Script& script); + + void RemoveExtResource(int id); + + void ReplaceScriptReferences(int ext_id, int sub_id); + + std::string ToString() const; + + private: + Heading file_descriptor_; + std::map ext_resources_; + std::map> sub_resources_; + std::vector> other_; + int next_sub_id_ = 0; +}; + +#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 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "script.h" + +Script::Script(const std::filesystem::path& path, std::string_view file_prefix) + : file_prefix_(file_prefix) { + { + std::ifstream script_file(path); + std::string line; + std::ostringstream text_stream; + while (std::getline(script_file, line)) { + text_stream << line << std::endl; + } + text_ = text_stream.str(); + } + + std::string search = std::string("load(\"") + std::string(file_prefix); + std::string_view corpus = text_; + int location; + while ((location = corpus.find(search)) != std::string_view::npos) { + corpus.remove_prefix(location + search.size()); + int divider = corpus.find_first_of("\""); + if (divider == std::string_view::npos) { + throw std::invalid_argument( + "I don't know how this is happening but I guess I gotta report it."); + } + std::string script_path(corpus.substr(0, divider)); + corpus.remove_prefix(divider + 1); + loaded_scripts_.push_back(script_path); + } +} + +void Script::ReplaceScriptReferences( + const std::map& script_ids) { + std::ostringstream new_text; + std::string_view corpus = text_; + int divider = corpus.find_first_of("\n"); + if (divider == std::string_view::npos) { + throw std::invalid_argument("Script is one line??"); + } + new_text << corpus.substr(0, divider + 1) << std::endl; + ; + corpus.remove_prefix(divider + 1); + + std::map path_to_export; + for (const std::string& loaded_script : loaded_scripts_) { + std::ostringstream export_str; + export_str << "COMPACTED"; + + std::filesystem::path name_path(loaded_script); + name_path.replace_filename(name_path.stem()); + for (const auto& segment : name_path) { + std::string segment_str(segment); + export_str << "_" << segment_str; + } + + std::string export_name = export_str.str(); + exports_.emplace_back(export_name, script_ids.at(loaded_script)); + + new_text << "export(Resource) var " << export_name << std::endl; + + path_to_export[loaded_script] = export_name; + } + + new_text << std::endl; + + std::string search = std::string("load(\"") + file_prefix_; + std::string search_alt1 = std::string("preload(\"") + file_prefix_; + std::string search_alt2 = + std::string("ResourceLoader.load(\"") + file_prefix_; + int location; + while ((location = corpus.find(search)) != std::string_view::npos) { + int alt1 = corpus.find(search_alt1); + int alt2 = corpus.find(search_alt2); + int remove_size = search.size(); + + if (alt1 != std::string_view::npos && alt1 < location) { + location = alt1; + remove_size = search_alt1.size(); + } else if (alt2 != std::string_view::npos && alt2 < location) { + location = alt2; + remove_size = search_alt2.size(); + } + + new_text << corpus.substr(0, location); + corpus.remove_prefix(location + remove_size); + + int divider = corpus.find_first_of("\""); + // TODO: ehhh error reporting + std::string referenced_path(corpus.substr(0, divider)); + corpus.remove_prefix(divider + 2); // the quote and the close paren + new_text << path_to_export.at(referenced_path); + } + + new_text << corpus; + + text_ = new_text.str(); +} + +std::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 @@ +#ifndef SCRIPT_H +#define SCRIPT_H + +#include +#include +#include +#include +#include +#include + +class Script { + public: + Script() = default; + + Script(const std::filesystem::path& path, std::string_view file_prefix); + + const std::vector& loaded_scripts() const { + return loaded_scripts_; + } + + const std::vector>& exports() const { + return exports_; + } + + void ReplaceScriptReferences(const std::map& script_ids); + + std::string ToString() const; + + private: + std::string text_; + std::string file_prefix_; + std::vector loaded_scripts_; + std::vector> exports_; +}; + +#endif /* SCRIPT_H */ -- cgit 1.4.1