Zulan
Zulan

Reputation: 22670

Create a prefix command with Boost.Program_options

I would like to create a prefix program like strace using Boost.Program_options. Prefix means that my program is put in front of another arbitrary command [args]. Hence, my program should accept a number of keyword arguments / flags that are defined. The first positional argument indicates the command that I am prefixing. This command may itself be followed by any combination of parameters that I do not know and might overlap with the parameters of my program. Therefore this first positional argument and anything that comes after it should end up in a std::vector<std::string>:

./foo --bar 13 command1                      # Should run fine
./foo command2 positional                    # Should run fine
./foo --bar 13 command3 --unknown argument   # Should run fine
./foo --unknown command4                     # should fail
./foo --bar 13 command5 --bar 42             # Should work but set bar to 13
./foo command6 --bar 42                      # Should not set bar at all
./foo --bar 13 -- command7 --bar 42 --unknown argument     # Should work

A correct usage is to first specify any of the defined keyword Basically in a correct form, there are first a number of my defined should take any number of predefined

I have tried two approaches:

1) Using allow_unregistered:

#include <iostream>
#include <boost/program_options.hpp>

namespace po = boost::program_options;

int main(int argc, const char** argv)
{
    int bar = 0;
    po::options_description desc("Allowed options");

    desc.add_options()
            ("bar", po::value(&bar), "bar");


    po::variables_map vm;
    po::parsed_options parsed =
        po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
    po::store(parsed, vm);
    po::notify(vm);

    auto command = po::collect_unrecognized(parsed.options, po::include_positional);
    std::cout << "bar: " << bar << ", command:";
    for (const auto& c : command) std::cout << " " << c;
    std::cout << std::endl;
}

This fails for commands 4,5,6

2) One positional options with unlimited occurrences

std::vector<std::string> command;

desc.add_options()
        ("bar", po::value(&bar), "bar")
        ("command", po::value(&command));

po::positional_options_description p;
p.add("command", -1);

po::variables_map vm;
po::parsed_options parsed =
    po::command_line_parser(argc, argv).options(desc).positional(p).run();
po::store(parsed, vm);
po::notify(vm);

This fails for commands 3,5,6.

Upvotes: 2

Views: 746

Answers (1)

ecatmur
ecatmur

Reputation: 157354

There is a feature request for this on Boost Trac, including patches: https://svn.boost.org/trac/boost/ticket/6991. There hasn't been much movement on the ticket, but the patch still applies cleanly to Boost (as of 1.61.0, the latest version).

If your build system allows it, you could apply the patch to your local copy of Boost; otherwise you could extract boost::program_options::detail::cmdline from cmdline.hpp, detail/cmdline.hpp and cmdline.cpp, into your own namespace, patch that component, and use your patched component in place of boost::program_options::cmdline.

One more option is to hack the behavior of boost::program_options::detail::cmdline, using its extra_style_parser parameter:

po::detail::cmdline cmdline(argc, argv);
cmdline.set_options_description(desc);
cmdline.set_positional_options(p);
std::vector<po::detail::cmdline::style_parser> style_parsers{
    [&](auto& args) { return cmdline.parse_long_option(args); },
    [&](auto& args) { return cmdline.parse_short_option(args); }};
cmdline.extra_style_parser([&](std::vector<std::string>& args) {
    auto const current_size = args.size();
    std::vector<po::option> result;
    for (auto const& parser : style_parsers) {
        auto const next = parser(args);
        result.insert(result.end(), next.begin(), next.end());
        if (args.size() != current_size)
            return result;
    }
    if (args.size() && args[0] != "--") args.insert(args.begin(), "--");
    auto const next = cmdline.parse_terminator(args);
    result.insert(result.end(), next.begin(), next.end());
    return result;
});
po::parsed_options parsed{&desc};
parsed.options = cmdline.run();
po::store(parsed, vm);
po::notify(vm);

Example. Warning: this is hacking with undocumented library internals, and could break at any time.

Upvotes: 2

Related Questions