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.