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")
...;
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