diff --git a/.gitmodules b/.gitmodules index 9486843..c0c6e23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/cxxopts"] path = third_party/cxxopts url = https://github.com/jarro2783/cxxopts.git +[submodule "third_party/json"] + path = third_party/json + url = https://github.com/nlohmann/json.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a5021c..6d9f3c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories(${PROJECT_BINARY_DIR}) add_subdirectory(third_party) include_directories(third_party/cxxopts/include) +include_directories(third_party/json/single_include) include_directories(src) add_subdirectory(src) diff --git a/LibraryLicenses.txt b/LibraryLicenses.txt index ec1b996..42997b8 100644 --- a/LibraryLicenses.txt +++ b/LibraryLicenses.txt @@ -23,3 +23,26 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +json: + +MIT License + +Copyright (c) 2013-2020 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/PckTool.cpp b/src/PckTool.cpp index 1f88b2e..0f73eb4 100644 --- a/src/PckTool.cpp +++ b/src/PckTool.cpp @@ -12,6 +12,9 @@ PckTool::PckTool(const Options& options) : Opts(options) {} // ------------------------------------ // int PckTool::Run() { + if(!BuildFileList()) + return 1; + if(Opts.Action == "list" || Opts.Action == "l") { auto pck = LoadPck(); @@ -32,13 +35,13 @@ int PckTool::Run() if(!pck) return 2; - if(!Opts.Files.empty()) { - if(Opts.Files.size() != 1) { + if(!Files.empty()) { + if(Files.size() != 1) { std::cout << "ERROR: only one target file to repack as is allowed\n"; return 1; } - pck->ChangePath(Opts.Files.front()); + pck->ChangePath(Files.front().InputFile); } std::cout << "Repacking to: " << pck->GetPath() << "\n"; @@ -67,7 +70,7 @@ int PckTool::Run() } else if(Opts.Action == "add" || Opts.Action == "a") { std::unique_ptr pck; - if(Opts.Files.empty()) { + if(Files.empty()) { std::cout << "ERROR: no files specified\n"; return 1; } @@ -84,12 +87,21 @@ int PckTool::Run() } } else { pck = std::make_unique(Opts.Pack); + + pck->SetGodotVersion(Opts.GodotMajor, Opts.GodotMinor, Opts.GodotPatch); } - for(const auto& entry : Opts.Files) { - if(!pck->AddFilesFromFilesystem(entry, Opts.RemovePrefix)) { - std::cout << "ERROR: failed to process file to add: " << entry << "\n"; - return 3; + for(const auto& entry : Files) { + if(!entry.Target.empty()) { + // Already known target for a single file + pck->AddSingleFile(entry.InputFile, pck->PreparePckPath(entry.Target, "")); + } else { + // Potentially a folder tree with no pre-defined targets + if(!pck->AddFilesFromFilesystem(entry.InputFile, Opts.RemovePrefix)) { + std::cout << "ERROR: failed to process file to add: " << entry.InputFile + << "\n"; + return 3; + } } } @@ -135,3 +147,36 @@ std::unique_ptr PckTool::LoadPck() return pck; } +// ------------------------------------ // +bool PckTool::BuildFileList() +{ + // Copy standard files + for(const auto& entry : Opts.Files) + Files.push_back({entry}); + + // Handle json commands + if(Opts.FileCommands.is_array()) { + for(const auto& entry : Opts.FileCommands) { + Files.push_back( + {entry["file"].get(), entry["target"].get()}); + } + } + + // Use first file as the pck if pck is missing + if(Opts.Pack.empty()) { + if(Files.empty()) { + std::cout << "ERROR: No pck file or list of files given\n"; + return false; + } + + // User first file as the pck file + Opts.Pack = Files.front().InputFile; + Files.erase(Files.begin()); + } + + if(Opts.Pack.find(pcktool::GODOT_PCK_EXTENSION) == std::string::npos) { + std::cout << "WARNING: Given pck file doesn't contain the pck file extension\n"; + } + + return true; +} diff --git a/src/PckTool.h b/src/PckTool.h index a649c6d..ad0a344 100644 --- a/src/PckTool.h +++ b/src/PckTool.h @@ -2,16 +2,25 @@ #include "Define.h" +#include + #include #include #include namespace pcktool { +using json = nlohmann::json; + class PckFile; //! \brief Main class for the Godot Pck Tool class PckTool { + struct FileEntry { + std::string InputFile; + std::string Target; + }; + public: struct Options { std::string Pack; @@ -24,6 +33,8 @@ class PckTool { int GodotMajor; int GodotMinor; int GodotPatch; + + json FileCommands; }; public: @@ -34,6 +45,8 @@ class PckTool { int Run(); private: + bool BuildFileList(); + bool TargetExists(); bool RequireTargetFileExists(); @@ -42,6 +55,8 @@ class PckTool { private: Options Opts; + + std::vector Files; }; } // namespace pcktool diff --git a/src/main.cpp b/src/main.cpp index d4beed6..a9c68a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,6 +84,7 @@ int main(int argc, char* argv[]) std::string output; std::string removePrefix; int godotMajor, godotMinor, godotPatch; + nlohmann::json fileCommands; if(result.count("file")) { files = result["file"].as(); @@ -97,28 +98,12 @@ int main(int argc, char* argv[]) removePrefix = result["remove-prefix"].as(); } - if(result.count("pack") < 1) { - // Use first file as the pack file - if(files.empty()) { - std::cout << "ERROR: No pck file or list of files given\n"; - return 1; - } - - // User first file as the pck file - pack = files.front(); - files.erase(files.begin()); - - } else { + if(result.count("pack")) { pack = result["pack"].as(); } action = result["action"].as(); - if(pack.find(pcktool::GODOT_PCK_EXTENSION) == std::string::npos) { - std::cout << "ERROR: Given pck file doesn't contain the pck file extension\n"; - return 1; - } - try { std::tie(godotMajor, godotMinor, godotPatch) = ParseGodotVersion(result["set-godot-version"].as()); @@ -127,8 +112,39 @@ int main(int argc, char* argv[]) return 1; } - auto tool = pcktool::PckTool( - {pack, action, files, output, removePrefix, godotMajor, godotMinor, godotPatch}); + bool alreadyRead = false; + + // Stdin json commands + for(auto iter = files.begin(); iter != files.end();) { + if(*iter != "-" || alreadyRead) { + ++iter; + continue; + } + + alreadyRead = true; + iter = files.erase(iter); + + std::cout << "Reading JSON file commands from STDIN until EOF...\n"; + + std::string data; + + std::string line; + while(std::getline(std::cin, line)) { + data += line; + } + + std::cout << "Finished reading STDIN (total characters: " << data.size() << ").\n"; + + try { + fileCommands = nlohmann::json::parse(data); + } catch(const nlohmann::json::parse_error& e) { + std::cout << "ERROR: invalid json: " << e.what() << "\n"; + return 1; + } + } + + auto tool = pcktool::PckTool({pack, action, files, output, removePrefix, godotMajor, + godotMinor, godotPatch, fileCommands}); return tool.Run(); } diff --git a/third_party/json b/third_party/json new file mode 160000 index 0000000..5bfb27c --- /dev/null +++ b/third_party/json @@ -0,0 +1 @@ +Subproject commit 5bfb27c86550f14ee752a60bd21893dcc53ba2c7