diff --git a/mob.ini b/mob.ini index b459812..973afff 100644 --- a/mob.ini +++ b/mob.ini @@ -37,24 +37,27 @@ git_shallow = false git_shallow = false [tools] -sevenz = 7z.exe -jom = jom.exe -patch = patch.exe -git = git.exe -cmake = cmake.exe -perl = perl.exe -devenv = devenv.exe -msbuild = msbuild.exe -nuget = nuget.exe -vswhere = vswhere.exe -nasm = nasm.exe -tx = tx.py37-x64.exe -vcvars = +sevenz = 7z.exe +jom = jom.exe +patch = patch.exe +git = git.exe +cmake = cmake.exe +perl = perl.exe +devenv = devenv.exe +msbuild = msbuild.exe +nuget = nuget.exe +vswhere = vswhere.exe +nasm = nasm.exe +tx = tx.py37-x64.exe +lrelease = lrelease.exe +vcvars = [transifex] enabled = true -key = -url = https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ +key = +team = mod-organizer-2-team +project = mod-organizer-2 +url = https://www.transifex.com minimum = 60 [prebuilt] diff --git a/src/commands.cpp b/src/commands.cpp index a8c9be3..1f95208 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -9,6 +9,10 @@ namespace mob { +// in main.cpp +void set_sigint_handler(); + + std::string version() { return "mob 4.0"; @@ -219,6 +223,9 @@ int command::run() return r; } + if (flags_ & handle_sigint) + set_sigint_handler(); + const auto r = do_run(); if (code_) @@ -362,7 +369,7 @@ std::string options_command::do_doc() build_command::build_command() - : command(requires_options) + : command(flags(requires_options | handle_sigint)) { } @@ -1368,6 +1375,18 @@ clipp::group tx_command::do_group() (clipp::value("path") >> path_) % "path that will contain the .tx directory" ) + + | + + "build" % + (clipp::command("build").set(mode_, modes::build), + + (clipp::value("source") >> path_) + % "path that contains the translation directories", + + (clipp::value("destination") >> dest_) + % "path that will contain the .qm files" + ) ); } @@ -1379,6 +1398,10 @@ int tx_command::do_run() do_get(); break; + case modes::build: + do_build(); + break; + case modes::none: default: u8cerr << "bad tx mode " << static_cast(mode_) << "\n"; @@ -1397,7 +1420,12 @@ std::string tx_command::do_doc() "Commands:\n" "get\n" " Initializes a Transifex project in the given directory if\n" - " necessary and pulls all the translation files."; + " necessary and pulls all the translation files.\n" + "\n" + "build\n" + " Builds all .qm files. The path can either be the transifex\n" + " project (where .tx is) or the `translations` directory (where the\n" + " individual translation directories are)."; } void tx_command::do_get() @@ -1421,7 +1449,12 @@ void tx_command::do_get() key_ = conf::get_global("transifex", "key"); if (url_.empty()) - url_ = conf::get_global("transifex", "url"); + { + url_ = + conf::get_global("transifex", "url") + "/" + + conf::get_global("transifex", "team") + "/" + + conf::get_global("transifex", "project"); + } if (key_.empty() && !this_env::get_opt("TX_TOKEN")) { @@ -1456,4 +1489,105 @@ void tx_command::do_get() .run(cxcopy); } +void tx_command::do_build() +{ + fs::path root = path_; + if (fs::exists(root / ".tx") && fs::exists(root / "translations")) + root = root / "translations"; + + fs::path dest = dest_; + op::create_directories(gcx(), dest, op::unsafe); + + std::set warned; + + for (auto e : fs::directory_iterator(root)) + { + if (!e.is_directory()) + continue; + + const auto dir = path_to_utf8(e.path().filename()); + const auto dir_cs = split(dir, "."); + if (dir_cs.size() != 2) + { + u8cerr << "bad directory name '" << dir << "'; skipping\n"; + continue; + } + + const auto project = trim_copy(dir_cs[1]); + if (project.empty()) + { + u8cerr << "bad directory name '" << dir << "'; skipping\n"; + continue; + } + + u8cout << project << "\n"; + + const bool is_gamebryo_plugin = [&] + { + auto tasks = find_tasks(project); + if (tasks.empty()) + { + u8cerr + << "directory '" << dir << "' was parsed as project " + << "'" << project << "', but there's no task with this " + << "name\n"; + + return false; + } + + const task& t = *tasks[0]; + + if (!t.is_super()) + return false; + + const auto& mo_task = static_cast(t); + return mo_task.is_gamebryo_plugin(); + }(); + + + const fs::path gamebryo_dir = + conf::get_global("transifex", "project") + "." + + "game_gamebryo"; + + + for (auto f : fs::directory_iterator(e.path())) + { + if (!f.is_regular_file()) + continue; + + lrelease lr; + lr + .project(project) + .add_source(f.path()) + .out(dest_); + + if (is_gamebryo_plugin) + { + const auto gb_f = root / gamebryo_dir / f.path().filename(); + + if (fs::exists(gb_f)) + { + lr.add_source(gb_f); + } + else + { + if (!warned.contains(gb_f)) + { + u8cerr + << "this is a gamebryo plugin but there is no " + << "'" << path_to_utf8(gb_f) << "'; " + << "the .qm file will be missing some " + << "translations (will only warn once)\n"; + + warned.insert(gb_f); + } + } + } + + context cxcopy = gcx(); + lr.run(cxcopy); + } + } +} + } // namespace diff --git a/src/commands.h b/src/commands.h index ce6a819..123f581 100644 --- a/src/commands.h +++ b/src/commands.h @@ -50,6 +50,7 @@ protected: { noflags = 0x00, requires_options = 0x01, + handle_sigint = 0x02 }; bool picked_; @@ -293,7 +294,8 @@ private: enum class modes { none = 0, - get + get, + build }; modes mode_ = modes::none; @@ -302,8 +304,10 @@ private: std::string url_; bool force_ = false; std::string path_; + std::string dest_; void do_get(); + void do_build(); }; } // namespace diff --git a/src/main.cpp b/src/main.cpp index 0e42d2e..9a12e39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -113,59 +113,68 @@ void add_tasks() .add_task() .add_task(); - add_task(true) - .add_task("cmake_common") - .add_task("modorganizer-uibase"); + + using mo = modorganizer; + + // most of the alternate names below are from the transifex slugs, which + // are sometimes different from the project names, for whatever reason add_task(true) - .add_task("modorganizer-game_features") - .add_task("modorganizer-archive") - .add_task("modorganizer-lootcli") - .add_task("modorganizer-esptk") - .add_task("modorganizer-bsatk") - .add_task("modorganizer-nxmhandler") - .add_task("modorganizer-helper") - .add_task("githubpp") - .add_task("modorganizer-game_gamebryo") - .add_task("modorganizer-bsapacker") - .add_task("modorganizer-preview_bsa"); + .add_task("cmake_common") + .add_task("modorganizer-uibase"); add_task(true) - .add_task("modorganizer-game_oblivion") - .add_task("modorganizer-game_fallout3") - .add_task("modorganizer-game_fallout4") - .add_task("modorganizer-game_fallout4vr") - .add_task("modorganizer-game_falloutnv") - .add_task("modorganizer-game_morrowind") - .add_task("modorganizer-game_skyrim") - .add_task("modorganizer-game_skyrimse") - .add_task("modorganizer-game_skyrimvr") - .add_task("modorganizer-game_ttw") - .add_task("modorganizer-game_enderal"); + .add_task("modorganizer-game_features") + .add_task("modorganizer-archive") + .add_task("modorganizer-lootcli") + .add_task("modorganizer-esptk") + .add_task("modorganizer-bsatk") + .add_task("modorganizer-nxmhandler") + .add_task("modorganizer-helper") + .add_task("githubpp") + .add_task("modorganizer-game_gamebryo") + .add_task({"modorganizer-bsapacker", "bsa_packer"}) + .add_task("modorganizer-preview_bsa"); add_task(true) - .add_task("modorganizer-tool_inieditor") - .add_task("modorganizer-tool_inibakery") - .add_task("modorganizer-preview_base") - .add_task("modorganizer-diagnose_basic") - .add_task("modorganizer-check_fnis") - .add_task("modorganizer-installer_bain") - .add_task("modorganizer-installer_manual") - .add_task("modorganizer-installer_bundle") - .add_task("modorganizer-installer_quick") - .add_task("modorganizer-installer_fomod") - .add_task("modorganizer-installer_fomod_csharp") - .add_task("modorganizer-installer_ncc") - .add_task("modorganizer-bsa_extractor") - .add_task("modorganizer-plugin_python"); + .add_task("modorganizer-game_oblivion", mo::gamebryo) + .add_task("modorganizer-game_fallout3", mo::gamebryo) + .add_task("modorganizer-game_fallout4", mo::gamebryo) + .add_task("modorganizer-game_fallout4vr", mo::gamebryo) + .add_task("modorganizer-game_falloutnv", mo::gamebryo) + .add_task("modorganizer-game_morrowind", mo::gamebryo) + .add_task("modorganizer-game_skyrim", mo::gamebryo) + .add_task("modorganizer-game_skyrimse", mo::gamebryo) + .add_task("modorganizer-game_skyrimvr", mo::gamebryo) + .add_task("modorganizer-game_ttw", mo::gamebryo) + .add_task("modorganizer-game_enderal", mo::gamebryo); add_task(true) - .add_task("modorganizer-tool_configurator") - .add_task("modorganizer-fnistool") - .add_task("modorganizer-script_extender_plugin_checker") - .add_task("modorganizer-form43_checker") - .add_task("modorganizer-preview_dds") - .add_task("modorganizer"); + .add_task({"modorganizer-tool_inieditor", "inieditor"}) + .add_task("modorganizer-tool_inibakery") + .add_task("modorganizer-preview_base") + .add_task("modorganizer-diagnose_basic") + .add_task("modorganizer-check_fnis") + .add_task("modorganizer-installer_bain") + .add_task("modorganizer-installer_manual") + .add_task("modorganizer-installer_bundle") + .add_task("modorganizer-installer_quick") + .add_task("modorganizer-installer_fomod") + .add_task("modorganizer-installer_fomod_csharp") + .add_task("modorganizer-installer_ncc") + .add_task("modorganizer-bsa_extractor") + .add_task("modorganizer-plugin_python"); + + add_task(true) + .add_task({"modorganizer-tool_configurator", "pycfg"}) + .add_task("modorganizer-fnistool") + .add_task({ + "modorganizer-script_extender_plugin_checker", + "diagnose-script_extender_plugin_checker", + }) + .add_task({"modorganizer-form43_checker", "form43checker"}) + .add_task({"modorganizer-preview_dds", "ddspreview"}) + .add_task({"modorganizer", "organizer"}); } @@ -211,6 +220,11 @@ private: bool restore_; }; +void set_sigint_handler() +{ + ::SetConsoleCtrlHandler(mob::signal_handler, TRUE); +} + int run(const std::vector& args) { @@ -240,7 +254,6 @@ int wmain(int argc, wchar_t** argv) { mob::set_std_streams(); mob::set_thread_exception_handlers(); - ::SetConsoleCtrlHandler(mob::signal_handler, TRUE); std::vector args; for (int i=1; i{long_name}, f) { - if (long_name != name()) - add_name(long_name); +} + +modorganizer::modorganizer(std::vector names, flags f) + : modorganizer(std::vector(names.begin(), names.end()), f) +{ +} + +modorganizer::modorganizer(std::vector names, flags f) + : basic_task(make_short_name(names[0])), repo_(names[0]), flags_(f) +{ + for (auto&& n : names) + add_name(std::move(n)); } std::string modorganizer::version() @@ -39,6 +49,11 @@ bool modorganizer::is_super() const return true; } +bool modorganizer::is_gamebryo_plugin() const +{ + return is_set(flags_, gamebryo); +} + fs::path modorganizer::source_path() { return {}; diff --git a/src/tasks/task.cpp b/src/tasks/task.cpp index b668614..88c1976 100644 --- a/src/tasks/task.cpp +++ b/src/tasks/task.cpp @@ -330,6 +330,10 @@ const context& task::cx() const void task::add_name(std::string s) { + auto itor = std::find(names_.begin(), names_.end(), s); + if (itor != names_.end()) + return; + names_.push_back(s); } diff --git a/src/tasks/task.h b/src/tasks/task.h index ced0563..8156acf 100644 --- a/src/tasks/task.h +++ b/src/tasks/task.h @@ -213,6 +213,15 @@ public: return *this; } + template + parallel_tasks& add_task(std::initializer_list il, Args&&... args) + { + children_.push_back( + std::make_unique(std::move(il), std::forward(args)...)); + + return *this; + } + fs::path get_source_path() const override { return {}; diff --git a/src/tasks/tasks.h b/src/tasks/tasks.h index 76d2ebd..fc3b063 100644 --- a/src/tasks/tasks.h +++ b/src/tasks/tasks.h @@ -276,7 +276,15 @@ private: class modorganizer : public basic_task { public: - modorganizer(std::string name); + enum flags + { + noflags = 0x00, + gamebryo = 0x01 + }; + + modorganizer(std::string name, flags f=noflags); + modorganizer(std::vector names, flags f=noflags); + modorganizer(std::vector names, flags f=noflags); static std::string version(); static bool prebuilt(); @@ -288,6 +296,7 @@ public: const fs::path& root, cmake::ops o=cmake::generate); bool is_super() const override; + bool is_gamebryo_plugin() const; protected: void do_clean(clean c) override; @@ -296,6 +305,7 @@ protected: private: std::string repo_; + flags flags_; cmake create_this_cmake_tool(cmake::ops o=cmake::generate); msbuild create_this_msbuild_tool(msbuild::ops o=msbuild::build); diff --git a/src/tools/tools.cpp b/src/tools/tools.cpp index ecf9a4c..66fe18f 100644 --- a/src/tools/tools.cpp +++ b/src/tools/tools.cpp @@ -384,4 +384,73 @@ void transifex::do_pull() execute_and_join(); } + +lrelease::lrelease() + : basic_process_runner("lrelease") +{ +} + +fs::path lrelease::binary() +{ + return conf::path_by_name("qt_bin") / conf::tool_by_name("lrelease"); +} + +lrelease& lrelease::project(const std::string& name) +{ + project_ = name; + return *this; +} + +lrelease& lrelease::add_source(const fs::path& ts_file) +{ + sources_.push_back(ts_file); + return *this; +} + +lrelease& lrelease::out(const fs::path& dir) +{ + out_ = dir; + return *this; +} + +fs::path lrelease::qm_file() const +{ + if (sources_.empty()) + cx().bail_out(context::generic, "lrelease: no sources"); + + const auto lang = trim_copy(path_to_utf8(sources_[0].stem())); + if (lang.empty()) + { + cx().bail_out(context::generic, + "lrelease: bad file name '{}'", sources_[0]); + } + + return project_ + "_" + lang + ".qm"; +} + +void lrelease::do_run() +{ + const auto qm = qm_file(); + + process_ = process() + .binary(binary()) + .arg("-silent") + .stderr_filter([](auto&& f) + { + if (f.line.find("dropping duplicate") != -1) + f.lv = context::level::debug; + else if (f.line.find("try -verbose") != -1) + f.lv = context::level::debug; + }); + + + for (auto&& s : sources_) + process_.arg(s); + + process_ + .arg("-qm", (out_ / qm)); + + execute_and_join(); +} + } // namespace diff --git a/src/tools/tools.h b/src/tools/tools.h index 373465a..098b16f 100644 --- a/src/tools/tools.h +++ b/src/tools/tools.h @@ -598,4 +598,27 @@ private: void do_pull(); }; + +class lrelease : public basic_process_runner +{ +public: + static fs::path binary(); + + lrelease(); + + lrelease& project(const std::string& name); + lrelease& add_source(const fs::path& ts_file); + lrelease& out(const fs::path& dir); + + fs::path qm_file() const; + +protected: + void do_run() override; + +private: + std::string project_; + std::vector sources_; + fs::path out_; +}; + } // namespace diff --git a/src/utility.cpp b/src/utility.cpp index f15a7fc..5d81e16 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -322,7 +322,16 @@ bool glob_match(const std::string& pattern, const std::string& s) { try { - return std::regex_match(s, std::regex(replace_all(pattern, "*", ".*"))); + std::string fixed_pattern = pattern; + fixed_pattern = replace_all(fixed_pattern, "*", ".*"); + fixed_pattern = replace_all(fixed_pattern, "_", "-"); + + std::string fixed_string = s; + fixed_string = replace_all(fixed_string, "_", "-"); + + std::regex re(fixed_pattern, std::regex::icase); + + return std::regex_match(fixed_string, re); } catch(std::exception&) { diff --git a/src/utility.h b/src/utility.h index 5b49b75..b0bdd59 100644 --- a/src/utility.h +++ b/src/utility.h @@ -310,6 +310,9 @@ url make_prebuilt_url(const std::string& filename); url make_appveyor_artifact_url( arch a, const std::string& project, const std::string& filename); +// case insensitive, underscores and dashes are equivalent; gets converted to +// a regex where * becomes .* +// bool glob_match(const std::string& pattern, const std::string& s); std::string replace_all(