diff --git a/mob.ini b/mob.ini index f4fbab0..b459812 100644 --- a/mob.ini +++ b/mob.ini @@ -48,8 +48,15 @@ msbuild = msbuild.exe nuget = nuget.exe vswhere = vswhere.exe nasm = nasm.exe +tx = tx.py37-x64.exe vcvars = +[transifex] +enabled = true +key = +url = https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ +minimum = 60 + [prebuilt] boost = true lz4 = true diff --git a/src/commands.cpp b/src/commands.cpp index 1ea0199..a8c9be3 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1103,18 +1103,19 @@ std::string git_command::do_doc() "All the commands will go through all modorganizer repos, plus usvfs\n" "and NCC.\n" "\n" + "Commands:\n" "set-remotes\n" - "For each repo, this first sets the username and email. Then, it\n" - "will rename the remote 'origin' to 'upstream' and create a new\n" - "remote 'origin' with the given information. If the remote\n" - "'upstream' already exists in a repo, nothing happens.\n" + " For each repo, this first sets the username and email. Then, it\n" + " will rename the remote 'origin' to 'upstream' and create a new\n" + " remote 'origin' with the given information. If the remote\n" + " 'upstream' already exists in a repo, nothing happens.\n" "\n" "add-remote\n" - "For each repo, adds a new remote with the given information. If a\n" - "remote with the same name already exists, nothing happens.\n" + " For each repo, adds a new remote with the given information. If a\n" + " remote with the same name already exists, nothing happens.\n" "\n" "ignore-ts\n" - "Toggles the --assume-changed status of all .ts files in all repos."; + " Toggles the --assume-changed status of all .ts files in all repos."; } void git_command::do_set_remotes() @@ -1325,6 +1326,10 @@ std::string inis_command::do_doc() } +tx_command::tx_command() + : command(requires_options) +{ +} command::meta_t tx_command::meta() const { @@ -1341,18 +1346,114 @@ clipp::group tx_command::do_group() clipp::command("tx").set(picked_), (clipp::option("-h", "--help") >> help_) - % ("shows this message") + % ("shows this message"), + + "get" % + (clipp::command("get").set(mode_, modes::get), + (clipp::option("-m", "--minimum") + & clipp::value("PERCENT") >> min_) + % "minimum translation threshold to download [0-100]", + + (clipp::option("-k", "--key") + & clipp::value("APIKEY") >> key_) + % "API key", + + (clipp::option("-f", "--force").set(force_) + % "don't check timestamps, re-download all translation files"), + + (clipp::option("-u", "--url") + & clipp::value("URL") >> url_) + % "project URL", + + (clipp::value("path") >> path_) + % "path that will contain the .tx directory" + ) ); } int tx_command::do_run() { - return prepare_options(true); + switch (mode_) + { + case modes::get: + do_get(); + break; + + case modes::none: + default: + u8cerr << "bad tx mode " << static_cast(mode_) << "\n"; + throw bailed(); + } + + return 0; } std::string tx_command::do_doc() { - return ""; + return + "Values for --key, --minimum and --url will be taken from the INI\n" + "file if not specified.\n" + "\n" + "Commands:\n" + "get\n" + " Initializes a Transifex project in the given directory if\n" + " necessary and pulls all the translation files."; +} + +void tx_command::do_get() +{ + if (min_ < 0) + { + const auto s = conf::get_global("transifex", "minimum"); + + try + { + min_ = std::stoi(s); + } + catch(std::exception&) + { + gcx().bail_out(context::generic, + "bad transifex minimum percentage '{}'", s); + } + } + + if (key_.empty()) + key_ = conf::get_global("transifex", "key"); + + if (url_.empty()) + url_ = conf::get_global("transifex", "url"); + + if (key_.empty() && !this_env::get_opt("TX_TOKEN")) + { + u8cout << + "(no key was in the INI, --key wasn't given and TX_TOKEN env\n" + "variable doesn't exist, this will probably fail)\n\n"; + } + + + context cxcopy = gcx(); + + u8cout << "initializing\n"; + transifex(transifex::init) + .root(path_) + .run(cxcopy); + + u8cout << "configuring\n"; + transifex(transifex::config) + .stdout_level(context::level::info) + .root(path_) + .api_key(key_) + .url(url_) + .run(cxcopy); + + u8cout << "pulling\n"; + transifex(transifex::pull) + .stdout_level(context::level::info) + .root(path_) + .api_key(key_) + .minimum(min_) + .force(force_) + .run(cxcopy); } } // namespace diff --git a/src/commands.h b/src/commands.h index ffaa9cd..ce6a819 100644 --- a/src/commands.h +++ b/src/commands.h @@ -281,12 +281,29 @@ protected: class tx_command : public command { public: + tx_command(); meta_t meta() const override; protected: clipp::group do_group() override; int do_run() override; std::string do_doc() override; + +private: + enum class modes + { + none = 0, + get + }; + + modes mode_ = modes::none; + int min_ = -1; + std::string key_; + std::string url_; + bool force_ = false; + std::string path_; + + void do_get(); }; } // namespace diff --git a/src/op.cpp b/src/op.cpp index 5454506..5131b52 100644 --- a/src/op.cpp +++ b/src/op.cpp @@ -29,10 +29,12 @@ void touch(const context& cx, const fs::path& p) do_touch(cx ,p); } -void create_directories(const context& cx, const fs::path& p) +void create_directories(const context& cx, const fs::path& p, flags f) { cx.trace(context::fs, "creating dir {}", p); - check(cx, p); + + if (!is_set(f, unsafe)) + check(cx, p); if (!conf::dry()) do_create_directories(cx, p); diff --git a/src/op.h b/src/op.h index a29c638..95213aa 100644 --- a/src/op.h +++ b/src/op.h @@ -22,7 +22,7 @@ MOB_ENUM_OPERATORS(flags); void touch(const context& cx, const fs::path& p); void create_directories( - const context& cx, const fs::path& p); + const context& cx, const fs::path& p, flags f=noflags); void delete_directory( const context& cx, const fs::path& p, flags f=noflags); diff --git a/src/process.cpp b/src/process.cpp index c24833d..35001b9 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -257,6 +257,7 @@ process::process() : cx_(&gcx()), unicode_(false), chcp_(-1), flags_(process::noflags), stdout_(context::level::trace), stderr_(context::level::error), code_(0) { + success_.insert(0); } process::~process() @@ -398,6 +399,12 @@ process::flags_t process::flags() const return flags_; } +process& process::success_exit_codes(std::set v) +{ + success_ = v; + return *this; +} + process& process::env(const mob::env& e) { env_ = e; @@ -663,7 +670,9 @@ void process::read_pipe( return; } - cx_->log_string(f.r, f.lv, f.line); + if (!is_set(flags_, ignore_output_on_success)) + cx_->log_string(f.r, f.lv, f.line); + logs_[f.lv].emplace_back(std::move(line)); }); @@ -706,16 +715,23 @@ void process::on_completed() } // success - if (code_ == 0) + if (success_.contains(static_cast(code_))) { + const bool ignore_output = is_set(flags_, ignore_output_on_success); const auto& warnings = logs_[context::level::warning]; const auto& errors = logs_[context::level::error]; - if (!warnings.empty() || !errors.empty()) + if (ignore_output || (warnings.empty() && errors.empty())) + { + cx_->trace(context::cmd, + "process exit code is {} (considered success)", code_); + } + else { cx_->warning( context::cmd, - "process exit code is 0, but stderr had something"); + "process exit code is {} (considered success), " + "but stderr had something", code_); cx_->warning(context::cmd, "process was: {}", make_cmd()); cx_->warning(context::cmd, "stderr:"); @@ -726,10 +742,6 @@ void process::on_completed() for (auto&& line : errors) cx_->warning(context::std_err, " {}", line); } - else - { - cx_->trace(context::cmd, "process exit code is 0"); - } return; } @@ -940,6 +952,11 @@ std::string process::arg_to_string(const url& u, arg_flags f) return u.string(); } +std::string process::arg_to_string(int i, arg_flags) +{ + return std::to_string(i); +} + encoded_buffer::encoded_buffer(encodings e, std::string bytes) : e_(e), bytes_(std::move(bytes)), last_(0) diff --git a/src/process.h b/src/process.h index feb0798..ba3456d 100644 --- a/src/process.h +++ b/src/process.h @@ -164,9 +164,10 @@ class process public: enum flags_t { - noflags = 0x00, - allow_failure = 0x01, - terminate_on_interrupt = 0x02 + noflags = 0x00, + allow_failure = 0x01, + terminate_on_interrupt = 0x02, + ignore_output_on_success = 0x04 }; enum arg_flags @@ -261,6 +262,8 @@ public: process& flags(flags_t f); flags_t flags() const; + process& success_exit_codes(std::set v); + template >> process& arg(const T& value, arg_flags f=noargflags) { @@ -338,6 +341,7 @@ private: bool unicode_; int chcp_; flags_t flags_; + std::set success_; stream stdout_; stream stderr_; mob::env env_; @@ -371,6 +375,7 @@ private: std::string arg_to_string(const std::string& s, arg_flags f); std::string arg_to_string(const fs::path& p, arg_flags f); std::string arg_to_string(const url& u, arg_flags f); + std::string arg_to_string(int i, arg_flags f); }; diff --git a/src/tools/tools.cpp b/src/tools/tools.cpp index 4934c97..ecf9a4c 100644 --- a/src/tools/tools.cpp +++ b/src/tools/tools.cpp @@ -256,4 +256,132 @@ void pip_install::do_run() execute_and_join(); } + +transifex::transifex(ops o) : + basic_process_runner("transifex"), op_(o), + stdout_(context::level::trace), min_(100), force_(false) +{ +} + +fs::path transifex::binary() +{ + return conf::tool_by_name("tx"); +} + +transifex& transifex::root(const fs::path& p) +{ + root_ = p; + return *this; +} + +transifex& transifex::api_key(const std::string& key) +{ + key_ = key; + return *this; +} + +transifex& transifex::url(const mob::url& u) +{ + url_ = u; + return *this; +} + +transifex& transifex::minimum(int percent) +{ + min_ = percent; + return *this; +} + +transifex& transifex::stdout_level(context::level lv) +{ + stdout_ = lv; + return *this; +} + +transifex& transifex::force(bool b) +{ + force_ = b; + return *this; +} + +void transifex::do_run() +{ + switch (op_) + { + case init: + do_init(); + break; + + case config: + do_config(); + break; + + case pull: + do_pull(); + break; + + default: + cx().bail_out(context::generic, "tx unknown op {}", op_); + } +} + +void transifex::do_init() +{ + op::create_directories(cx(), root_, op::unsafe); + + // exit code is 2 when the directory already contains a .tx + + process_ = process() + .binary(binary()) + .success_exit_codes({0, 2}) + .flags(process::ignore_output_on_success) + .arg("init") + .arg("--no-interactive") + .cwd(root_); + + execute_and_join(); +} + +void transifex::do_config() +{ + if (url_.empty()) + cx().bail_out(context::generic, "missing transifex url"); + + op::create_directories(cx(), root_, op::unsafe); + + process_ = process() + .binary(binary()) + .stdout_level(stdout_) + .arg("config") + .arg("mapping-remote") + .arg(url_) + .env(this_env::get() + .set("TX_TOKEN", key_)) + .cwd(root_); + + execute_and_join(); +} + +void transifex::do_pull() +{ + op::create_directories(cx(), root_, op::unsafe); + + process_ = process() + .binary(binary()) + .stdout_level(stdout_) + .arg("pull") + .arg("--all") + .arg("--parallel") + .arg("--no-interactive") + .arg("--minimum-perc", min_) + .env(this_env::get() + .set("TX_TOKEN", key_)) + .cwd(root_); + + if (force_) + process_.arg("--force"); + + execute_and_join(); +} + } // namespace diff --git a/src/tools/tools.h b/src/tools/tools.h index d4959e9..373465a 100644 --- a/src/tools/tools.h +++ b/src/tools/tools.h @@ -558,4 +558,44 @@ private: fs::path file_; }; + +class transifex : public basic_process_runner +{ +public: + enum ops + { + init = 1, + config, + pull + }; + + static fs::path binary(); + + + transifex(ops op); + + transifex& root(const fs::path& p); + transifex& api_key(const std::string& key); + transifex& url(const mob::url& u); + transifex& minimum(int percent); + transifex& stdout_level(context::level lv); + transifex& force(bool b); + +protected: + void do_run() override; + +private: + ops op_; + context::level stdout_; + fs::path root_; + std::string key_; + mob::url url_; + int min_; + bool force_; + + void do_init(); + void do_config(); + void do_pull(); +}; + } // namespace diff --git a/third-party/bin/tx.py37-x64.exe b/third-party/bin/tx.py37-x64.exe new file mode 100644 index 0000000..b363e2c Binary files /dev/null and b/third-party/bin/tx.py37-x64.exe differ