Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement subcommands using Boost.Program_options?

I'd like to implement subcommands to my program. I also need the ability to have different argument options for different subcommands. What's the best way to do this using Boost.Program_options?

Subcommands are used in programs like svn, git and apt-get.

For example in GIT some of the available subcommands are:

git status   git push   git add   git pull   

My question is basically the same as this guy's: http://boost.2283326.n4.nabble.com/subcommands-with-program-options-like-svn-command-td2585537.html

like image 599
Scintillo Avatar asked Mar 21 '13 07:03

Scintillo


2 Answers

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.

like image 52
Alastair Avatar answered Sep 28 '22 20:09

Alastair


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.

like image 39
poolie Avatar answered Sep 28 '22 20:09

poolie