How to implement subcommands using Boost.Program_options?
If I understand the problem correctly, you want to parse command line options of the following form:
[--generic-option ...] cmd [--cmd-specific-option ... ]
Here is my example solution. For clarity I'm going to omit any validation code, but hopefully you can see how it would be added fairly simply.
In this example, we have the "ls" subcommand, and possibly others. Each subcommand has some specific options, and in addition there are generic options. So let's start by parsing the generic options and the command name.
po::options_description global("Global options");
global.add_options()
("debug", "Turn on debug output")
("command", po::value<std::string>(), "command to execute")
("subargs", po::value<std::vector<std::string> >(), "Arguments for command");
po::positional_options_description pos;
pos.add("command", 1).
add("subargs", -1);
po::variables_map vm;
po::parsed_options parsed = po::command_line_parser(argc, argv).
options(global).
positional(pos).
allow_unregistered().
run();
po::store(parsed, vm);
Notice that we've created a single positional option for the command name, and multiple positional options for the command options.
Now we branch on the relevant command name and re-parse. Instead of passing in the original argc
and argv
we now pass in the unrecognized options, in the form of an array of strings. The collect_unrecognized
function can provide this - all we have to do is remove the (positional) command name and re-parse with the relevant options_description
.
std::string cmd = vm["command"].as<std::string>();
if (cmd == "ls")
{
// ls command has the following options:
po::options_description ls_desc("ls options");
ls_desc.add_options()
("hidden", "Show hidden files")
("path", po::value<std::string>(), "Path to list");
// Collect all the unrecognized options from the first pass. This will include the
// (positional) command name, so we need to erase that.
std::vector<std::string> opts = po::collect_unrecognized(parsed.options, po::include_positional);
opts.erase(opts.begin());
// Parse again...
po::store(po::command_line_parser(opts).options(ls_desc).run(), vm);
Note that we used the same variables_map
for the command-specific options as for the generic ones. From this we can perform the relevant actions.
The code fragments here are taken from a compilable source file which includes some unit tests. You can find it on gist here. Please feel free to download and play with it.
You can take the subcommand name off the command line using positional options - see this tutorial.
There doesn't seem to be any built-in support for subcommands - you will need to set the allow_unregistered
option on the top-level parser, find the command name, then run it through a second parser to get any subcommand-specific options.