Program Listing for File cli_wrapper.h¶
↰ Return to documentation for file (src/common/cli_wrapper.h
)
#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 <iostream>
#include <map>
#include <string>
#include <unordered_set>
#include <vector>
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<any_type> 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<std::string, CLIOptionTuple> options_;
// Counter for created options to keep track of order in which options were created
size_t counter_{0};
std::vector<CLIAliasTuple> aliases_; // List of alias tuples
Ptr<CLI::App> 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<std::string> getParsedOptionNames() const;
// Get option names in the same order as they are created
std::vector<std::string> 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 <typename T>
CLI::Option* add(const std::string& args, const std::string& help, T val) {
return addOption<T>(keyName(args),
args,
help,
val,
/*defaulted =*/true);
}
template <typename T>
CLI::Option* add(const std::string& args, const std::string& help) {
return addOption<T>(keyName(args),
args,
help,
T(),
/*defaulted =*/false);
}
void alias(const std::string &key,
const std::string &value,
const std::function<void(YAML::Node &config)> &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 <typename T>
using EnableIfNumbericOrString = CLI::enable_if_t<!CLI::is_bool<T>::value
&& !CLI::is_vector<T>::value, CLI::detail::enabler>;
template <typename T, EnableIfNumbericOrString<T> = 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<any_type>(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<T>();
// 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<T>());
// 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 <typename T>
using EnableIfVector = CLI::enable_if_t<CLI::is_vector<T>::value, CLI::detail::enabler>;
template <typename T, EnableIfVector<T> = 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<any_type>(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<T>();
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<T>());
// 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 <typename T>
using EnableIfBoolean = CLI::enable_if_t<CLI::is_bool<T>::value, CLI::detail::enabler>;
template <typename T, EnableIfBoolean<T> = 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<any_type>(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<T>() = true;
config_[key] = true;
} else if(val == "false" || val == "off" || val == "no" || val == "0") {
options_[key].var->as<T>() = 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