.. _program_listing_file_src_common_cli_wrapper.h: Program Listing for File cli_wrapper.h ====================================== |exhale_lsh| :ref:`Return to documentation for file ` (``src/common/cli_wrapper.h``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #pragma once #include "3rd_party/CLI/CLI.hpp" #include "3rd_party/any_type.h" #include "3rd_party/yaml-cpp/yaml.h" #include "common/definitions.h" #include #include #include #include #include namespace marian { class Options; namespace cli { // Option priority enum struct OptionPriority : int { DefaultValue = 0, ConfigFile = 1, CommandLine = 2 }; struct CLIOptionTuple { CLI::Option *opt; // a pointer to an option object from CLI11 Ptr var; // value assigned to the option via command-line size_t idx{0}; // order in which the option was created OptionPriority priority{cli::OptionPriority::DefaultValue}; }; // Helper tuple for aliases storing the alias name, value, and options to be expanded struct CLIAliasTuple { std::string key; // alias option name std::string value; // value for the alias option indicating that it should be expanded YAML::Node config; // config with options that the alias adds }; // The helper class for cli::CLIWrapper handling formatting of options and their descriptions. class CLIFormatter : public CLI::Formatter { public: CLIFormatter(size_t columnWidth, size_t screenWidth); virtual std::string make_option_desc(const CLI::Option*) const override; private: size_t screenWidth_{0}; }; class CLIWrapper { private: // Map with option names and option tuples std::unordered_map options_; // Counter for created options to keep track of order in which options were created size_t counter_{0}; std::vector aliases_; // List of alias tuples Ptr app_; // Command-line argument parser from CLI11 std::string defaultGroup_{""}; // Name of the default option group std::string currentGroup_{""}; // Name of the current option group YAML::Node &config_; // Reference to the main config object // Option for --version flag. This is a special flag and similarly to --help, // the key "version" will be not added into the YAML config CLI::Option *optVersion_; // Extract option name from a comma-separated list of long and short options, e.g. 'help' from // '--help,-h' std::string keyName(const std::string &args) const; // Get names of options passed via command-line std::unordered_set getParsedOptionNames() const; // Get option names in the same order as they are created std::vector getOrderedOptionNames() const; static std::string failureMessage(const CLI::App *app, const CLI::Error &e); public: CLIWrapper(YAML::Node &config, const std::string &description = "", const std::string &header = "General options", const std::string &footer = "", size_t columnWidth = 40, size_t screenWidth = 0); virtual ~CLIWrapper(); template CLI::Option* add(const std::string& args, const std::string& help, T val) { return addOption(keyName(args), args, help, val, /*defaulted =*/true); } template CLI::Option* add(const std::string& args, const std::string& help) { return addOption(keyName(args), args, help, T(), /*defaulted =*/false); } void alias(const std::string &key, const std::string &value, const std::function &fun) { ABORT_IF(!options_.count(key), "Option '{}' is not defined so alias can not be created", key); aliases_.resize(aliases_.size() + 1); aliases_.back().key = key; aliases_.back().value = value; fun(aliases_.back().config); } std::string switchGroup(std::string name = ""); // Parse command-line arguments. Handles --help and --version options void parse(int argc, char** argv); void parseAliases(); void updateConfig(const YAML::Node &config, cli::OptionPriority priority, const std::string &errorMsg); // Get textual YAML representation of the config std::string dumpConfig(bool skipUnmodified = false) const; private: template using EnableIfNumbericOrString = CLI::enable_if_t::value && !CLI::is_vector::value, CLI::detail::enabler>; template = CLI::detail::dummy> CLI::Option* addOption(const std::string &key, const std::string &args, const std::string &help, T val, bool defaulted) { // add key to YAML config_[key] = val; // create option tuple CLIOptionTuple option; option.idx = counter_++; option.var = std::make_shared(val); // callback function collecting a command-line argument CLI::callback_t fun = [this, key](CLI::results_t res) { options_[key].priority = cli::OptionPriority::CommandLine; // get variable associated with the option auto& var = options_[key].var->as(); // store parser result in var auto ret = CLI::detail::lexical_cast(res[0], var); // update YAML entry config_[key] = var; return ret; }; auto opt = app_->add_option(args, fun, help, defaulted); // set human readable type value: UINT, INT, FLOAT or TEXT opt->type_name(CLI::detail::type_name()); // set option group if(!currentGroup_.empty()) opt->group(currentGroup_); // set textual representation of the default value for help message if(defaulted) { std::stringstream ss; ss << val; opt->default_str(ss.str()); } // store option tuple option.opt = opt; options_.insert(std::make_pair(key, option)); return options_[key].opt; } template using EnableIfVector = CLI::enable_if_t::value, CLI::detail::enabler>; template = CLI::detail::dummy> CLI::Option* addOption(const std::string &key, const std::string &args, const std::string &help, T val, bool defaulted) { // add key to YAML config_[key] = val; // create option tuple CLIOptionTuple option; option.idx = counter_++; option.var = std::make_shared(val); // callback function collecting command-line arguments CLI::callback_t fun = [this, key](CLI::results_t res) { options_[key].priority = cli::OptionPriority::CommandLine; // get vector variable associated with the option auto& vec = options_[key].var->as(); vec.clear(); bool ret = true; // handle '[]' as an empty vector if(res.size() == 1 && res.front() == "[]") { ret = true; } else { // populate the vector with parser results for(const auto& a : res) { vec.emplace_back(); ret &= CLI::detail::lexical_cast(a, vec.back()); } ret &= !vec.empty(); } // update YAML entry config_[key] = vec; return ret; }; auto opt = app_->add_option(args, fun, help); // set human readable type value: VECTOR and opt->type_name(CLI::detail::type_name()); // accept unlimited number of arguments opt->type_size(-1); // set option group if(!currentGroup_.empty()) opt->group(currentGroup_); // set textual representation of the default vector values for help message if(defaulted) opt->default_str(CLI::detail::join(val)); // store option tuple option.opt = opt; options_.insert(std::make_pair(key, option)); return options_[key].opt; } template using EnableIfBoolean = CLI::enable_if_t::value, CLI::detail::enabler>; template = CLI::detail::dummy> CLI::Option* addOption(const std::string &key, const std::string &args, const std::string &help, T val, bool defaulted) { // add key to YAML config_[key] = val; // create option tuple CLIOptionTuple option; option.idx = counter_++; option.var = std::make_shared(val); // callback function setting the flag CLI::callback_t fun = [this, key](CLI::results_t res) { options_[key].priority = cli::OptionPriority::CommandLine; // get parser result, it is safe as boolean options have an implicit value auto val = res[0]; auto ret = true; if(val == "true" || val == "on" || val == "yes" || val == "1") { options_[key].var->as() = true; config_[key] = true; } else if(val == "false" || val == "off" || val == "no" || val == "0") { options_[key].var->as() = false; config_[key] = false; } else { ret = false; } return ret; }; auto opt = app_->add_option(args, fun, help, defaulted); // set option group if(!currentGroup_.empty()) opt->group(currentGroup_); // set textual representation of the default value for help message if(defaulted) opt->default_str(val ? "true" : "false"); // allow to use the flag without any argument opt->implicit_val("true"); // store option tuple option.opt = opt; options_.insert(std::make_pair(key, option)); return options_[key].opt; } }; } // namespace cli } // namespace marian