Demonstration of how to do subcommand option processing with boost program_options
#define BOOST_TEST_MODULE subcommand options | |
#include <boost/test/unit_test.hpp> | |
#include <boost/program_options.hpp> | |
#include <boost/variant/variant.hpp> | |
#include <boost/variant/get.hpp> | |
struct GenericOptions { | |
bool debug_; | |
}; | |
struct LsCommand : public GenericOptions { | |
bool hidden_; | |
std::string path_; | |
}; | |
struct ChmodCommand : public GenericOptions { | |
bool recurse_; | |
std::string perms_; | |
std::string path_; | |
}; | |
typedef boost::variant<LsCommand, ChmodCommand> Command; | |
Command ParseOptions(int argc, const char *argv[]) | |
{ | |
namespace po = boost::program_options; | |
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); | |
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); | |
LsCommand ls; | |
ls.debug_ = vm.count("debug"); | |
ls.hidden_ = vm.count("hidden"); | |
ls.path_ = vm["path"].as<std::string>(); | |
return ls; | |
} | |
else if (cmd == "chmod") | |
{ | |
// Something similar | |
} | |
// unrecognised command | |
throw po::invalid_option_value(cmd); | |
} | |
BOOST_AUTO_TEST_CASE(NoCommand) | |
{ | |
const int argc = 2; | |
const char *argv[argc] = { "0", "nocommand" }; | |
BOOST_CHECK_THROW( | |
ParseOptions(argc, argv), | |
boost::program_options::invalid_option_value); | |
} | |
BOOST_AUTO_TEST_CASE(LsTest) | |
{ | |
const int argc = 5; | |
const char *argv[argc] = { "0", "--debug", "ls", "--hidden", "--path=./" }; | |
Command c = ParseOptions(argc, argv); | |
BOOST_REQUIRE(boost::get<LsCommand>(&c)); | |
const LsCommand& ls = boost::get<LsCommand>(c); | |
BOOST_CHECK(ls.debug_); | |
BOOST_CHECK(ls.hidden_); | |
BOOST_CHECK_EQUAL(ls.path_, "./"); | |
} |
This comment has been minimized.
This comment has been minimized.
I ended up preprocessing the arguments before handing them off to First create a vector of strings that have your command names in, then loop through std::vector<std::<std::string,std::string>> cmds = { {"cmd1","cmd1 desc"}
, {"cmd2", "cmd2 desc"}
/* etc */ };
std::vector<std::string> global_args;
std::vector<std::string> cmd_args;
bool cmd_found = false;
for( int i = 1; i < argc; i++ )
{
std::string arg( argv[i] );
if(!cmd_found)
global_args.push_back( arg );
if(cmd_found)
cmd_args.push_back( arg );
for( auto c : cmds )
{
if( c.first == arg )
{
cmd_found = true;
break;
}
}
} Now you can setup an option parser for the global arguments and pass it Then just put your commands in separate functions, pass the function |
This comment has been minimized.
This comment has been minimized.
This is exactly what I was looking for! Thanks! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
This is great! The only problem is that it doesn't allow a global --help and specific --help options for the subcommands. After adding --help to both options descriptions, a.out ls --help and a.out --help gives the same output.