开发者

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

开发者 https://www.devze.com 2023-02-20 09:18 出处:网络
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 com

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 comm开发者_如何学JAVAand 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.)


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!


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;
}


I had this same question, but gave up on it.

I believe the way to do this is to call program_options::command_line_parser::extra_style_parser(), passing it a function that takes a vector of string by reference and returns a vector of options (see the style_parser typedef in cmdline.hpp).

Your function will need to detect that the first token is "--", create a new option object, place all the rest of the tokens in the input into the option's value vector and empty the input vector. See program_options::detail::cmdline::parse_long_option, etc., in libs/program_options/src/cmdline.cpp for something to start with.

You'll probably need to register a specific option value to use so that you can easily find this special option object at the end of the parsing and extract the set of additional non-option parameters from it.

I wish I could give you some code but I never got around to actually doing this, I ended up just taking the additional parameters one-per-line on stdin.

edit:

I felt bad about pointing you in a direction that didn't solve the problem, so I went back and got it working. The problem is that your positional argument entry wasn't set up to accept multiple tokens and you weren't filling in the value. The program_options code expects both or it doesn't work.

Here's the complete code that works for me:

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

namespace po = boost::program_options;
typedef std::vector<std::string> stringvec;

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

int main(int argc, char* argv[])
{
    po::options_description desc("Allowed Options");
    desc.add_options()
        ("help,h", "produce help message")
        ("pargs", po::value<stringvec>()->multitoken(), "positional arguments");
    //                          and here     ^^^^^^^^^^^^^

    po::command_line_parser clparser(argc, argv);
    clparser.extra_style_parser(end_of_opts_parser);

    po::variables_map vm;
    po::store(clparser.options(desc).run(), vm);
    po::notify(vm);

    bool const help = !vm["help"].empty();
    std::cout << "help = " << help << " - ";

    // in addition, you don't need to use a separate vector of strings:    
    stringvec const& pargs = vm["pargs"].as<stringvec>();
    std::copy(pargs.begin(), pargs.end(),
        std::ostream_iterator<std::string>(std::cout, ","));

    return 0;
}

When run with -h -- foo bar baz it prints help = 1 - foo,bar,baz,.

0

精彩评论

暂无评论...
验证码 换一张
取 消