c++c++11boostboost-program-options

How to create option aliases with boost::program_options?


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 !


Solution

  • 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:

    Hope it helps.