I use boost.program_options library. Consider this simplified case.
po::options_description desc("Usage");
desc.add_options()
("uninstall,u", "uninstall program")
("custom,c", po::wvalue<std::wstring>(), "specify custom action");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
I want to produce error on such command-line:
testprog.exe -u c- action1
Note, user made a typo "c-" instead of "-c". But the parser understands this as a single -u option. How do I handle such cases?
I want to produce error on such command-line:
testprog.exe -u c- action1
Note, user made a typo "c-" instead of "-c". But the parser
understands this as a single -u option. How do I handle such cases?
Instruct the program_options library to accept no positional arguments and you get the desired behavior
code & compile:
macmini:stackoverflow samm$ cat po.cc
#include <boost/program_options.hpp>
#include <boost/version.hpp>
#include <iostream>
int
main(int argc, char* argv[])
{
namespace po = boost::program_options;
po::options_description desc("Usage");
desc.add_options()
("uninstall,u", "uninstall program")
("custom,c", po::wvalue<std::wstring>(), "specify custom action")
;
po::variables_map vm;
po::command_line_parser cmd_line( argc, argv );
cmd_line.options( desc );
cmd_line.positional( po::positional_options_description() );
try {
po::store( cmd_line.run(), vm );
po::notify(vm);
} catch ( const std::exception& e ) {
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}
macmini:stackoverflow samm$ g++ po.cc -I /opt/local/include -L/opt/local/lib -lboost_program_options -Wl,-rpath,/opt/local/lib
run:
macmini:stackoverflow samm$ ./a.out -u c- action1
too many positional options
macmini:stackoverflow samm$ ./a.out -u -c action1
macmini:stackoverflow samm$
Compare argc-1 to the number of arguments found by program_options? If it doesn't match, there is a syntax error.
It won't catch all cases but it may catch those important to you.
I think the only way you can do this is to ensure that each argument you require is present, for example by testing the count of each type.
if (vm.count("uninstall")) { ... }
if (vm.count("custom")) { ... }
You can generate an error if the options you require are not present (i.e. count is 0) or are present (for example -u and -c cannot be specified would be count of both is >0).
Related
This is my minimal example, basically copied from boost web site:
#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;
int main(int argc, char **argv) {
po::options_description desc("Test options");
desc.add_options()
("help", "Print help")
;
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << std::endl;
}
}
When I run:
./main --help
it prints help, as expected. When I run:
./main --foo
it throws unknown_option error, as expected. But when I run:
./main foobar
it ignores the argument and no error is raised. I specify no positional arguments, so I expect boost to raise error here as well.
Why does boost behave this way (I expect that there is some logical reason that I don't see yet)?
What can I do to force boost to strictly check the options provided and raise error on anything else?
Code below uses po::bool_switch(&flag) in hope of automatically assigning the correct value to flag.
My compile command is clang++ -std=c++11 test.cpp -o test -lboost_program_options
So I run the program with ./test -h which shows no help message.
Why so?
#include <iostream>
#include <boost/program_options.hpp>
namespace
{
namespace po = boost::program_options;
}
int main(int ac, char ** av)
{
bool flag;
po::options_description help("Help options");
help.add_options()
( "help,h"
, po::bool_switch(&flag)->default_value(false)
, "produce this help message" );
po::variables_map vm;
po::parsed_options parsed
= po::parse_command_line(ac,av,help);
po::store(po::parse_command_line(ac,av,help),vm);
if (flag)
{
std::cout << help;
}
po::notify(vm);
return 0;
}
You should call notify after parsing and storing arguments. store just fills in internal data structures of variables_map. notify publishes them.
Your example is nearly exactly like the one in "Getting started section" in tutorial.
And here they give a pretty bold warning not to forget it:
Finally the call to the notify function runs the user-specified notify functions and stores the values into regular variables, if needed.
Warning:
Don't forget to call the notify function after you've stored all parsed values.
This should work:
po::variables_map vm;
po::parsed_options parsed
= po::parse_command_line(ac,av,help);
po::store(po::parse_command_line(ac,av,help),vm);
po::notify(vm); // call after last store, and before accesing parsed variables
if (flag)
{
std::cout << help;
}
Is there a way to have something like
myapp hostname:port
parsed by boost program_options? I'm also using other options and I would love to use boost program_options without having to roll my own parser for argc/argv.
I tried with some combinations of
desc.add_options()
("help", "list all available options")
(new MyCustomValue(&store_var), "")
but it didn't work
As Dan MaĊĦek writes, this looks more fitting for a positional argument. Since you specify a specific structure for the option, though, you might want to add std::regex_match.
Say you start with
#include <string>
#include <iostream>
#include <regex>
#include <boost/program_options.hpp>
using namespace std;
using namespace boost::program_options;
int main(int argc, const char *argv[]) {
try {
options_description desc{"Options"};
desc.add_options()
("help,h", "Help screen")
("ip_port", value<std::string>()->required(), "ip:port");
positional_options_description pos_desc;
pos_desc.add("ip_port", -1);
command_line_parser parser{argc, argv};
parser.options(desc).positional(pos_desc).allow_unregistered();
parsed_options parsed_options = parser.run();
variables_map vm;
store(parsed_options, vm);
notify(vm);
const auto ip_port = vm["ip_port"].as<string>();
At this point, we have the user's input for ip_port. We can define a regex to match it. Note that first part can be a string (e.g., localhost), but the second part must be an integer:
const regex ip_port_re("([^:]+):([[:digit:]]+)");
smatch ip_port_match;
if(!regex_match(ip_port, ip_port_match, ip_port_re))
throw validation_error{validation_error::invalid_option_value, ip_port, "ip_port"};
cout << "ip: " << ip_port_match[1] << " port: " << ip_port_match[2] << endl;
}
catch (const error &ex) {
cerr << ex.what() << '\n';
}
}
When running it, it looks like this:
$ g++ --std=c++11 po.cpp -lboost_program_options && ./a.out 127.0.0.1:30
ip: 127.0.0.1 port: 30
$ g++ --std=c++11 po.cpp -lboost_program_options && ./a.out localhost:30
ip: localhost port: 30
$ g++ --std=c++11 po.cpp -lboost_program_options && ./a.out localhost:30d
the argument for option 'localhost:30d' is invalid
$ g++ --std=c++11 po.cpp -lboost_program_options && ./a.out
the option '--ip_port' is required but missing
$ g++ --std=c++11 po.cpp -lboost_program_options && ./a.out localhost:30 foo
option '--ip_port' cannot be specified more than once
When I'm using boost::program_options, after throwing out validation_error, the error message does not display the full option name.
I have the following simple example source code, compiled with g++ -std=c++11 test.cpp -lboost_program_options. This program has one valid command line option: --hello-world, which is supposed to be set to either a or b, e.g. ./a.out --hello-world a is valid but ./a.out --hello-world c is invalid. Then, execute the code with an invalid option, for example ./a.out --hello-world c. The error message is like the following:
the argument for option 'world' is invalid
But I would expect the option name to be hello-world and the invalid value should also be displayed. Is there any way I can change this?
#include <boost/program_options.hpp>
int main(int argc, const char** argv)
{
namespace po = boost::program_options;
char v;
po::options_description desc("Options");
desc.add_options()
("hello-world", po::value<>(&v)->default_value('a')->notifier(
[](char x)
{
if (x != 'a' && x != 'b')
throw po::validation_error(
po::validation_error::invalid_option_value,
"hello-world", std::string(1, x));
}),
"An option must be set to either 'a' or 'b'");
try
{
po::variables_map vm;
po::store(po::command_line_parser(argc, argv). options(desc).run(), vm);
po::notify(vm);
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}
The name of your option is invalid "hello-world". Option names can not contain a dash, "-".
Typically the message will look like:
the argument ('32768') for option '--the_int16' is invalid
I am writing the following code on boost's program_options (version 1.42). This seems straight-forward and taken pretty much as is from the tutorial. However, I get a "multiple_occurrences" error. Further investigation discovers that it's (probably) the "filename" parameter that raises it.
The parameters I am giving are:
3 1 test.txt 100
I have no insight to it whatsoever.. any help will be appreciated.
po::options_description common("Common options");
common.add_options()
("help", "produce help message")
("motif_size", po::value<int>(&motif_size), "Size of motif (subgraph)")
("prob", po::value<double>(&prob), "Probably to continue examining an edge")
("filename", po::value<string>(&input_filename), "Filename of the input graph")
("repeats", po::value<int>(&n_estimates), "Number of estimates")
;
po::options_description all;
all.add(common);
po::positional_options_description p;
p.add("motif_size", 0).add("prob", 1).add("filename", 2).add("repeats", 3);
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).
options(all).positional(p).run(), vm);
po::notify(vm);
EDIT:
the second parameter to po::positional_options_description::add is the max count, not the position. The position is implied in the order you specify the positional options. So
p.add("motif_size", 0).add("prob", 1).add("filename", 2).add("repeats", 3);
should be
p.add("motif_size", 1).add("prob", 1).add("filename", 1).add("repeats", 1);
Here's a compilable snippet
include <boost/program_options.hpp>
#include <iostream>
#include <string>
int
main(unsigned argc, char** argv)
{
namespace po = boost::program_options;
po::options_description common("Common options");
common.add_options()
("help", "produce help message")
("motif_size", po::value<int>(), "Size of motif (subgraph)")
("prob", po::value<double>(), "Probably to continue examining an edge")
("filename", po::value<std::string>(), "Filename of the input graph")
("repeats", po::value<int>(), "Number of estimates")
;
po::options_description all;
all.add(common);
po::positional_options_description p;
p.add("motif_size", 1).add("prob", 1).add("filename", 1).add("repeats", 1);
po::variables_map vm;
try {
po::store(po::command_line_parser(argc, argv).
options(all).positional(p).run(), vm);
po::notify(vm);
} catch ( const boost::program_options::error& e ) {
std::cerr << e.what() << std::endl;
}
return 0;
}
and sample invocation.
macmini:~ samm$ g++ parse.cc -lboost_program_options
macmini:~ samm$ ./a.out 3 1 test.txt 100
macmini:~ samm$
My original answer is below.
What version of program_options? I had the same problem using boost 1.39, to solve it I ended up using boost 1.42.
Here's a link to the ticket describing the problem, and a patch to apply if you don't want to or can't upgrade your copy of boost. To use the new functionality, do something like this
try {
// argument parsing goes here
} catch ( const boost::program_options::multiple_occurrences& e ) {
std::cerr << e.what() << " from option: " << e.get_option_name() << std::endl;
}