Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using '--' as end-of-options marker with boost::program_options

The traditional way of indicating the end of options for command line programs is with the option --. How can I get boost::program_options to recognize this as an option and accept the rest of the command line as positional arguments? The following doesn't work:

namespace po = boost::program_options;

po::positional_options_description posOpts;
posOpts.add("keywords", 1);
posOpts.add("input", 1);

std::vector<std::string> final_args;

po::options_description desc("Allowed Options");
desc.add_options()
  ...
  ("", po::value< std::vector<std::string> >(&final_args)->multitoken(), "end of options")
  ...
  ;

po::command_line_parser(argc, argv).options(desc).positional(posOpts).run();

If I give foo bar as arguments, I get nothing in final_args (as expected), but also when I give -- foo bar as arguments (when I would expect to find final_args[0] == "foo" and final_args[1] == "bar"). I'm assuming here that -- is a long argument with the empty string as its argument name. If, instead, it's supposed to be interpreted as a short argument, with - as the argument name, how do I specify that? Changing the argument specification from "" to ",-" doesn't affect the result, so far as I can see.

How does one get boost::program_options to handle -- correctly?

Edit: Here's an attempt to do what Tim Sylvester suggested by creating an extra_style_parser:

std::vector<po::option> end_of_opts_parser(std::vector<std::string>& args) {
  std::vector<po::option> result;

  std::vector<std::string>::const_iterator i(args.begin());
  if (i != args.end() && *i == "--") {
    for (++i; i != args.end(); ++i) {
      po::option opt;
      opt.string_key = "pargs";
      opt.original_tokens.push_back(*i);
      result.push_back(opt);
    }

    args.clear();
  }

  return result;
}

"pargs" was added to the options like this:

("pargs", po::value< std::vector<std::string> >(&pargs), "positional arguments")

Running this with a -- in the argument list causes a required_option exception. (I get similar results if instead of making a po::option for each trailing arg, I pack them all into po::option::original_tokens in one po::option.)

like image 989
uckelman Avatar asked Mar 29 '11 16:03

uckelman


2 Answers

There's a simple, unsatisfying workaround: Before handing off argv to command_line_parser, check whether -- occurs in it. If so, reset argc to the position of -- to hide it and the arguments trailing it from command_line_parser. Then, when finished parsing, deal with the positional arguments after -- by hand. Blech!

like image 181
uckelman Avatar answered Sep 22 '22 18:09

uckelman


I’m a bit confused because boost::program_options already does this all by itself:

$ ./testprog --the-only-option=23 aa --unknown bb
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::program_options::unknown_option> >'
  what():  unknown option unknown
Aborted

$ ./testprog --the-only-option=23 -- aa --unknown bb
the only option: 23
positional: aa
positional: --unknown
positional: bb

The program:

#include <boost/program_options.hpp>

#include <iostream>
#include <vector>

using namespace std;

namespace po = boost::program_options;

static bool handle_command_line(int argc, char *argv[])
{
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "Describe command line options")
        ("the-only-option", po::value<string>(), "some option")
        ;

    po::options_description hidden;
    hidden.add_options()
        ("positional", po::value<vector<string> >())
        ;

    po::options_description all_options;
    all_options.add(desc).add(hidden);

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

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).
              options(all_options).positional(p).run(), vm);
    po::notify(vm);

    if (vm.count("help")) {
        cout << "Usage: " << argv[0] << " [options] [args]" << endl;
        cout << desc << "\n";
        return false;
    }

    if (vm.count("the-only-option"))
        cout << "the only option: " << vm["the-only-option"].as<string>() << endl;

    if (vm.count("positional")) {
        const vector<string> &v = vm["positional"].as<vector<string> >();
        vector<string>::const_iterator it = v.begin();
        for (; it != v.end(); ++it)
            cout << "positional: " << *it << endl;
    }

    return true;
}

int main(int argc, char *argv[])
{
    if (!handle_command_line(argc, argv))
        return 1;
    return 0;
}
like image 31
Ringding Avatar answered Sep 20 '22 18:09

Ringding