Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::program_options positional options

I have a positional option (a file name), and I want it to be the very last option. The user can pass in a bunch of stuff on the command line, and also use -F for the file name. However, I want the user also to have the ability to place the file name at the end.

For example:

./program --var 3 /path/to/file

The code I currently have implemented allows the caller to place the file name wherever in the command line. Is there any way to force the positional arguments to always come after the "regular" ones?

Here's how I set-up the positional argument:

pos_opts_desc.add("filename", -1);

And to parse the command line:

store(
    command_line_parser(argc, argv).options(opts_desc).positional(pos_opts_desc).run(),
    opts_var_map);

Thanks in advance for the help.

Edited to add:

I'm perfectly OK with -F being specified anywhere in the command line. However, if the setting was done via the positional option, I want to ensure that the positional option is at the very end.

like image 306
It'sPete Avatar asked May 05 '16 22:05

It'sPete


2 Answers

The run() member function gives you back an instance of type parsed_options. The simple usage is to never actually look at this object and pass it directly into store(), as in your example:

po::store(
    po::command_line_parser(argc, argv).options(opts_desc).positional(pos_opts_desc).run(),
    opts_var_map);

But we can hold onto it and examine its contents:

auto parsed = po::command_line_parser(argc, argv)
    .options(opts_desc)
    .positional(pos_opts_desc)
    .run();
po::store(parsed, opts_var_map);

The parsed_options class has a member options which has an ordered list of all the options (unlike the variable map, which is ordered by option name - since it's a std::map). So you can look up the "filename" argument and check its position_key member. We want either: position_key == -1 (which means it provided with -F) or position_key == 0 and it being the last element in the options list (it was a positional argument that was the last argument):

auto it = std::find_if(parsed.options.begin(),
                       parsed.options.end(),
                       [](po::option const& o) {
                         return o.string_key == "filename";
                       });

if (it == parsed.options.end()) {
    // fail: missing filename);
}

if (it->position_key != -1 && it != std::prev(parsed.options.end())) {
    // fail: filename was positional but wasn't last
}
like image 100
Barry Avatar answered Nov 10 '22 07:11

Barry


variables_map is as the name suggests a std::map, which allows us to use regular STL functions on it.

    if ( vm.count("filename") ) {
      if ( vm.find("filename") != std::prev(vm.rbegin()).base() ) {
        std::cout << "filename must go at the end.";
      }
    }

Test cases:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp -lboost_system -lboost_program_options \
&& echo -n "Case 1 " && ./a.out asdf --foo=12 && echo \
&& echo -n "Case 2 " && ./a.out --foo=12 asdf && echo \
&& echo -n "Case 3 " && ./a.out asdf && echo \
&& echo -n "Case 4 " && ./a.out --foo=12 && echo \
&& echo -n "Case 5 " && ./a.out && echo \
&& echo -n "Case 6 " && ./a.out --foo=12 asdf asdf

Result:

Case 1 filename must go at the end.
Case 2 
Case 3 
Case 4 
Case 5 
Case 6 option '--filename' cannot be specified more than once
like image 30
uh oh somebody needs a pupper Avatar answered Nov 10 '22 09:11

uh oh somebody needs a pupper