Search code examples
c++boostboost-program-optionscustom-validators

Want to allow options to be specified multiple times when using boost program options. Right now I get multiple occurrences


I am using boost program_options 1.50.0

I want to ALLOW the following for my program foobar foobar --debug 2 --debug 3

From the boost program_options code, there is an example regex.cpp that shows creating a new type and creating a validator for that type.
I tried that, and it works, but now I cannot use some of the other add_options() typed_value options, like default_value, composing, etc.

Here is what I tried so far:

    #include <boost/program_options.hpp>

    using namespace boost;

    using namespace boost::program_options;

    #include <iostream>
    using namespace std;
    struct lastmultioccurrenceint {
        public:
         lastmultioccurrenceint(int n) : n(n) {}
         int n;
    };    

void validate(boost::any& v, 
                  const std::vector< std::string >& xs, 
                  //const std::vector< std::basic_string<charT> >& xs, 
                  lastmultioccurrenceint* , int)
    {
        using namespace boost::program_options;

        cerr << "IN VALIDATE" << endl;
        //validators::check_first_occurrence(v);
        string s = validators::get_single_string(xs);
        if (!v.empty()) {
            cerr << "\tPRINTTING MULTIOCCURENCE WARNING, allowing v to be overwritten" << endl;
            cerr << "\tEarlier value was: " <<  boost::any_cast<int>(v) << endl;
            cerr << "\tNew value is: " << s << endl;
        }
        try {
            //v = any(lastmultioccurrenceint(lexical_cast<int>(sx)));
            //v = any(lexical_cast<int>(sx)); // works
            v = any(lexical_cast<int>(s));
            //v = any(lexical_cast<lastmultioccurrenceint>(s));
            //v = any(4);
        //}
        /*catch(const bad_lexical_cast&) {
            boost::throw_exception(validation_error::invalid_option_value(s));
        } */
        }
        catch(const bad_lexical_cast&) {
            throw validation_error(validation_error::invalid_option_value);
        }
        cerr << "made it through" << endl;

        int main (int argc, char **argv) {

    variables_map m_varMap;
        // define style
        //  unix_style =  (allow_short | short_allow_adjacent | short_allow_next
        //            | allow_long | long_allow_adjacent | long_allow_next
        //            | allow_sticky | allow_guessing 
        //            | allow_dash_for_short), 
        // ... allows typical unix-style options
        // allow_long_disguise = can use "-" instead of "--"
        // Reference: http://www.boost.org/doc/libs/1_42_0/doc/html/boost/program_options/command_line_style/style_t.html
        //
    try {

    ProgOpts::command_line_style::style_t style = ProgOpts::command_line_style::style_t(
            ProgOpts::command_line_style::unix_style |
            //ProgOpts::command_line_style::case_insensitive |
            ProgOpts::command_line_style::allow_long_disguise );

    options_description options("YDD");

    //lastmultioccurrenceint debugOpt;

    options.add_options()
    ("debug", value<lastmultioccurrenceint>(), "debug value (0-4), default is 0 (performance mode)")
    //("debug", value<lastmultioccurrenceint>(&debugOpt)->default_value(0)->composing(), "debug value (0-4), default is 0 (performance mode)")
    ;

        //ProgOpts::parsed_options firstPreParsed = ProgOpts::command_line_parser(argc,argv).options(options).style(style).allow_unregistered().run();
        ProgOpts::parsed_options firstPreParsed = ProgOpts::command_line_parser(argc,argv).options(options).allow_unregistered().run();
    ProgOpts::store(firstPreParsed, m_varMap);

    ProgOpts::notify(m_varMap);
    } 
    /*catch (boost::program_options::multiple_occurrences &e) {
        cerr << "GOT MULTIPLES" << endl;
        cerr << "Option Name: " << e.get_option_name() << endl;
        cerr << e.what() << endl;
    }
    catch(boost::bad_any_cast& e) {
        cerr << "WRONG TYPE" << endl;
        cerr << e.what() << endl;
    } */
    catch(std::exception& e) {
        cerr << "SOMETHING ELSE" << endl;
        cerr << e.what() << endl;
    }
    catch(...) {
        cerr << "UNKNOWN ERROR" << endl;
    }

    cerr << "DEBUG OPT IS: " << m_varMap["debug"].as<int>() << endl;
}

So if I do: foobar --debug 2 --debug 3

If I comment out the current debug option ....

("debug", value<lastmultioccurrenceint>(), "debug value (0-4), default is 0 (performance mode)")

... and uncomment out the following two lines:

lastmultioccurrenceint debugOpt;
("debug", value<lastmultioccurrenceint>(&debugOpt)->default_value(0)->composing(), "debug value (0-4), default is 0 (performance mode)")

... then it doesn't even compile.

Do you know how to do this so that it allows me to use default_value and composing? It might be inheriting from typed_value, but I haven't found a good way to do this yet.


Solution

  • I don't think you need to define a custom type with a validator to achieve the desired result. It can be done with the existing semantic information support of the library. Consider this example

    #include <boost/assign/list_of.hpp>
    #include <boost/program_options.hpp>
    #include <boost/version.hpp>
    
    #include <iostream>
    
    int
    main( int argc, char** argv )
    {
        namespace po = boost::program_options;
    
        po::options_description desc("Options");
    
        typedef std::vector<unsigned> DebugValues;
        DebugValues debug;
        desc.add_options()
            ("help,h", "produce help message")
            ("debug", po::value<DebugValues>(&debug)->default_value(boost::assign::list_of(0), "0")->composing(), "set debug level")
    
            ;
    
        po::variables_map vm;
        try {
            const po::positional_options_description p; // note empty positional options
            po::store(
                    po::command_line_parser( argc, argv).
                              options( desc ).
                              positional( p ).
                              run(),
                              vm
                              );
            po::notify( vm );
    
            if ( vm.count("help") ) {
                std::cout << desc << "\n";
                std::cout << "boost version: " << BOOST_LIB_VERSION << std::endl;
                return 0;
            }
        } catch ( const boost::program_options::error& e ) {
            std::cerr << e.what() << std::endl;
        }
    
        std::cout << "got " << debug.size() << " debug values" << std::endl;
        if ( !debug.empty() ) {
            DebugValues::const_iterator value( debug.end() );
            std::advance( value, -1 );
            std::cout << "using last value of " << *value << std::endl;
        }
    }
    

    and sample usage:

    samm$ ./a.out -h
    Options:
      -h [ --help ]         produce help message
      --debug arg (=0)      set debug level
    
    boost version: 1_46_1
    samm$ ./a.out --debug 1 --debug 2
    got 2 debug values
    using last value of 2
    samm$ ./a.out --debug 4 --debug 1
    got 2 debug values
    using last value of 1
    samm$ ./a.out --debug 4 --debug 1 --debug 9
    got 3 debug values
    using last value of 9
    samm$