24 Jul
2017
24 Jul
'17
2:33 p.m.
I have an SVN-like "multi-command" single executable, i.e. exe [global-options] command [command-options] It uses two levels of option parsing. First level: typedef std::vector<std::string> UnparsedArgs; inline boost::program_options::typed_value<UnparsedArgs>* anyArgs() { return boost::program_options::value<UnparsedArgs>(); } options_description global_all_desc("Global Options", columns); global_all_desc.add_options() ("command", txtArg(), "Command to execute") ("cmdargs", anyArgs(), "Arguments for command") ...; // from http://stackoverflow.com/questions/15541498 // see also https://gist.github.com/randomphrase/10801888 po::positional_options_description pos; pos .add("command", 1) .add("cmdargs", -1); variables_map global_args; parsed_options parsed = po::command_line_parser(argc, argv) .options(global_all_desc) .positional(pos) // for implicit command and cmdargs above .allow_unregistered() // for command-specific arguments. .run(); po::store(parsed, global_args); std::vector<std::string> cmd_args = po::collect_unrecognized( parsed.options, po::include_positional ); Second level parsed_options reparsed = po::command_line_parser(cmd_args) .options(cmd_all_desc) .run(); po::store(reparsed, args_); // JIRA FOO-426 for (const auto& option : reparsed.options) { if (option.position_key >= 0) { std::ostringstream oss; for (const auto& token : option.original_tokens) { oss << token << ' '; } auto s = oss.str(); s.resize(s.size() - 1); err() << "Error: Too many commands: " << command() << ' ' << s << endl; return 1; } } in this particular case, the option that fails is ( "new", txtArgOpt(), "Creates a new group" ), with txtArgOpt() as below: inline boost::program_options::typed_value<std::string>* txtArg() { return boost::program_options::value<std::string>(); } inline boost::program_options::typed_value<std::string>* txtArgOpt(const std::string& implicit_value = "") { return txtArg()->implicit_value(implicit_value); } in the case that fails, the first parsing works fine, and remains ["--new", "groupA"] as cmd_args. in 1.56, reparsed.options contains 1 element: - [0] {string_key="new" position_key=-1 value={ size=1 } ...} boost::program_options::basic_option<char> + string_key "new" std::basic_string<char,std::char_traits<char>,std::allocator<char> > position_key -1 int - value { size=1 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [size] 1 __int64 [capacity] 1 __int64 + [0] "GroupA" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [Raw View] 0x0000000009fae6c0 {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > * - original_tokens { size=2 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [size] 2 __int64 [capacity] 2 __int64 + [0] "--new" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [1] "GroupA" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [Raw View] 0x0000000009fae6e0 {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > * unregistered false bool case_insensitive false bool but in 1.64, it contains 2 elements: - [0] {string_key="new" position_key=-1 value={ size=0 } ...} boost::program_options::basic_option<char> + string_key "new" std::basic_string<char,std::char_traits<char>,std::allocator<char> > position_key -1 int - value { size=0 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [capacity] 0 __int64 + [allocator] allocator std::_Compressed_pair<std::_Wrap_alloc<std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,1> + [Raw View] {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > - original_tokens { size=1 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [capacity] 1 __int64 + [allocator] allocator std::_Compressed_pair<std::_Wrap_alloc<std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,1> + [0] "--new" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [Raw View] {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > unregistered false bool case_insensitive false bool - [1] {string_key="" position_key=0 value={ size=1 } ...} boost::program_options::basic_option<char> + string_key "" std::basic_string<char,std::char_traits<char>,std::allocator<char> > position_key 0 int - value { size=1 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [capacity] 1 __int64 + [allocator] allocator std::_Compressed_pair<std::_Wrap_alloc<std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,1> + [0] "GroupA" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [Raw View] {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > - original_tokens { size=1 } std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > [capacity] 1 __int64 + [allocator] allocator std::_Compressed_pair<std::_Wrap_alloc<std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,1> + [0] "GroupA" std::basic_string<char,std::char_traits<char>,std::allocator<char> > + [Raw View] {...} std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > unregistered false bool case_insensitive false bool This is copy/pasted from VS, sorry, a little hard to read, but basically 1.56 has #0 string_key = "new" position_key = -1 value = ["groupA"] original_tokens = ["--new", "groupA"] unregistered = false case_insensitive = false while 1.64 has 2 items: #0 string_key = "new" position_key = -1 value = [] original_tokens = ["--new"] unregistered = false case_insensitive = false #1 string_key = "" position_key = 0 value = ["groupA"] original_tokens = ["groupA"] unregistered = false case_insensitive = false as-if the arg to --new was now a positional parameter, instead of an optional arg to --new. And this triggers the work-around code that errors out I had put in place to catch cases when the old 1.56 code wouldn't complain about extra arguments (e.g. "exe ... cmd1 cmd1_args cmd2" would be silently accepted, despite the extra cmd2, or "exe ... cmd --arg1 read-only" was accepted, when "exe ... cmd --arg1 --read-only" was expected), and this even though the second parsing doesn't have .allow_unregistered(). I'm trying to understand what's going on; obviously there's some kind of semantic change with optional argument to switches. Could anyone shed some light on this please? I'm not sure how to proceed now. TIA, -DD