Last active
May 25, 2024 08:26
-
-
Save randomphrase/10801888 to your computer and use it in GitHub Desktop.
Demonstration of how to do subcommand option processing with boost program_options
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 is exactly what I was looking for! Thanks!
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.
I know this reply comes late but I just had the same issue...
You can fix this by checking if a command was provided.
If you just use if (vm.count("help")) std::cout << global << std::endl;
, you get the same help output whenever a --help
flag is set anywhere in your options.
You can use this instead and you will get your different outpus:
// ...
if (vm.count("help") && !vm.count("command")) {
std::cout << bm_options << std::endl;
return 0;
}
// ...
// within the command specific section:
if (vm.count("help")) {
std::cout << ls_desc << std::endl;
return 0;
}
@lfreist Nice one! I'll update the code above
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I ended up preprocessing the arguments before handing them off to
boost
.First create a vector of strings that have your command names in, then loop through
argv
and sort the arguments into "global" and "command" arguments. I actually am using a vector of string pairs so I can put the command description with the command.Now you can setup an option parser for the global arguments and pass it
global_args
. Note that this snippet puts the command name in theglobal_args
vector, so you global argument parser can look for it as a positional parameter.Then just put your commands in separate functions, pass the function
cmd_args
and use another option parser inside the function to parse it.