I would like to be able to have the possibility to create option aliases with boost::program_options that stores their arguments under the same key/label.
The architecture of my software uses different specialized option parsers depending on the value argv[1]. However some options are shared, like my option --inputs.
inputOptions.add_options()
("--inputs",
po::value< std::vector<std::string> >()->value_name("paths"),
"List of files to edit.\n");
For compatibility with older version of the program, I would like to add to one of the sub-parsers a compatibility option --input that stores its argument(s) under "--inputs". Ideally that option should take at most one argument instead of arbitrarily many. However if you provide a solution that makes --input identical to --inputs, I guess it's fine too, as in this case positional options are sent to "--inputs" anyway.
Thank you for any help !
You can use extra_parser(see Non-conventional syntax in the docs), i.e. parser that can be used to manipulates tokens from input before they are processed further. It can be used for things like translating --run into --command=run etc.
Extra parser is a functor that has the following signature:
std::pair<std::string, std::string>(const std::string &s)
It should return option name in pair::first and (optional) option value in pair::second. Empty pair::first means that the extra parser has not parsed anything. Value (i.e. pair::second) can be empty - only option name has been parsed. If returned pair is valid then the name or name/value pair is used instead of parsing the original token via normal machinery.
First, we write aliasing function:
using OptionAliases = std::map<std::string, std::string>;
std::pair<std::string, std::string>
renameOptions(const std::string &token, const OptionAliases &aliases)
{
auto rtoken(boost::make_iterator_range(token));
// consume "--" prefix
if (!boost::algorithm::starts_with(rtoken, "--")) { return { "", "" }; }
rtoken.advance_begin(2);
// find equal sign (returns iterator range)
const auto eq(boost::algorithm::find_first(rtoken, "="));
// extract option (between "--prefix" and "="/end()) and map it to output
const auto faliases(aliases.find(std::string(rtoken.begin(), eq.begin())));
if (faliases == aliases.end()) { return { "", "" }; }
// return remapped option and (optionally) value after "="
return std::make_pair(faliases->second
, std::string(eq.end(), rtoken.end()));
}
It simply splits input token into --, name, =, value (no value if there is no = sign) and if the name is found in the provided alias mapping it returns (remapped-name, value).
Then, we create the parser itself, using lambda:
boost::program_options::ext_parser optionAlias(OptionAliases &&aliases)
{
return [aliases{std::move(aliases)}](const std::string &token)
{
return renameOptions(token, aliases);
};
}
(Needs at least C++14, for C++11 change to return [aliases](con...)
You can plug this parser into cmdline parser:
parser.extra_parser(optionAlias({{"mark.twain", "samuel.clemens"}
, {"lewis.caroll", "charles.dodgson"}}));
Now, in the example above, both --mark.twain and --samuel.clemens will point to vars["samuel.clemens"] and both --lewis.caroll and --charles.dodgson will point to vars["charles.dodgson"].
Caveats:
allow_long style (long options with -- prefix). Can be changed in the code.long_allow_adjacent style (values allowed in one token using =). Can be changed in the code, as well.If there is any non-option token that parses as --alias then it is translated as well since there is no context. No way to circumvent.
Example: If there's an option with name name that expects value and long_allow_next style is used, then --name=--mark.twain will be parsed as option name with value --mark.twain (as expected) while --name --mark.twain will be parsed as option name with value samuel.clemens.
Short of that, it works as expected.
Hope it helps.