views:

113

answers:

2

If my command line is:

> prog --mylist=a,b,c

Can Boost's program_options be setup to see three distinct argument values for the mylist argument? I have configured program_options as:

namespace po = boost::program_options;
po::options_description opts("blah")

opts.add_options()
    ("mylist", std::vector<std::string>>()->multitoken, "description");

po::variables_map vm;
po::store(po::parse_command_line(argc, argv, opts), vm);
po::notify(vm);

When I check the value of the mylist argument, I see one value as a,b,c. I'd like to see three distinct values, split on comma. This works fine if I specify the command line as:

> prog --mylist=a b c

or

> prog --mylist=a --mylist=b --mylist=c

Is there a way to configure program_options so that it sees a,b,c as three values that should each be inserted into the vector, rather than one?

I am using boost 1.41, g++ 4.5.0 20100520, and have enabled c++0x experimental extensions.

EDIT:

The accepted solution works but ends up being more complicated, IMO, than just iterating through a vector and splitting the values manually. In the end, I took the suggestion from James McNellis and implemented it that way. His solution wasn't submitted as an answer, however, so I accepted the other correct solution from hkaiser. Both worked, but the manual tokenization is clearer.

+1  A: 

I haven't tried doing so myself, but you could possibly be able to use the same approach as in custom_syntax.cpp example that's provided with program_options, to write your own parser that you can provide as an extra parser. There's a bit of info here with a short example. Then you could either combine that with James' suggestion of using boost::tokenizer, or just follow his suggestion.

Jacob
I don't think that will work. It appears to only call the parser for arguments, not their values.
lrm
I take that back, it calls it for each token, not each argument. Will experiment some more.
lrm
@lrm: Sorry I can't be of more help, haven't used program_options a lot, but let us know how it goes if you decide to go with this, rather than just plain tokenizing the string.
Jacob
+2  A: 

You could register a custom validator for your option:

namespace po = boost::program_options;

struct mylist_option 
{
    // values specified with --mylist will be stored here
    vector<std::string> values;

    // Function which validates additional tokens from command line.
    static void
    validate(boost::any &v, std::vector<std::string> const &tokens)
    {
        if (v.empty())
            v = boost::any(mylist_option());

        mylist_option *p = boost::any_cast<mylist_option>(&v);
        BOOST_ASSERT(p);

        boost::char_separator<char> sep(",");
        BOOST_FOREACH(std::string const& t, tokens)
        {
            if (t.find(",")) {
                // tokenize values and push them back onto p->values
                boost::tokenizer<boost::char_separator<char> > tok(t, sep);
                std::copy(tok.begin(), tok.end(), 
                    std::back_inserter(p->values));
            }
            else {
                // store value as is
                p->values.push_back(t);
            }
        }
    }
};

which then can be used as:

opts.add_options()                 
    ("mylist", po::value<mylist_option>()->multitoken(), "description");

and:

if (vm.count("mylist"))
{
    // vm["mylist"].as<mylist_option>().values will hold the value specified
    // using --mylist
}
hkaiser
This worked, with some modification. I had to extract the validate function out of the struct and overload it according to the documentation. I hate having to create a faux-type to do what I want, but that's life. In the end, I just iterated through the vector and tokenized the values w/ a simple loop. Its about 50% less code than the custom validator. Nevertheless, I'm accepting this answer as it works and is the only correct answer submitted.
lrm