Single-token and multi-token positional options with boost::program_options - c++

I am writing a program that would receive as parameters a filename followed by multiple strings. Optionally it could take a -count argument;
./program [-count n] <filename> <string1> <string2> ...
This is the code I wrote:
PO::positional_options_description l_positionalOptions;
l_positionalOptions.add("file", 1);
l_positionalOptions.add("strings", -1);
PO::options_description l_optionsDescription;
l_optionsDescription.add_options()
("count", PO::value<int>()->default_value(1), "How many times to write"),
("file", PO::value<std::string>(), "Output file name"),
("strings", PO::value<std::vector<std::string>>()->multitoken()->zero_tokens()->composing(), "Strings to be written to the file");
PO::command_line_parser l_parser {argc, argv};
l_parser.options(l_optionsDescription)
.positional(l_positionalOptions)
.allow_unregistered();
PO::variables_map l_userOptions;
try {
PO::store(l_parser.run(), l_userOptions);
}
catch (std::exception &ex) {
std::cerr << ex.what() << std::endl;
exit(1);
}
However, when I run ./program file.out str1 str2 str3 it fails with:
unrecognised option 'str1'
What am I doing wrong? Is this even possible with boost::program_options?

I figured it out. It's as dumb as it can get.
The issue was that I had a , after every entry in add_options().
This made it so only the first entry would get saved.

Related

How to add a description to boost::program_options' positional options?

I would like to make a positional, list program option with boost_program_options that do not allow named program options (like --files).
I have the following snippet of code:
#include <boost/program_options.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace po = boost::program_options;
int main(int argc, const char* argv[]) {
po::options_description desc("Allowed options");
desc.add_options()("help", "produce help message")
( "files", po::value<std::vector<std::string>>()->required(), "list of files");
po::positional_options_description pos;
pos.add("files", -1);
po::variables_map vm;
try {
po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm);
po::notify(vm);
} catch(const po::error& e) {
std::cerr << "Couldn't parse command line arguments properly:\n";
std::cerr << e.what() << '\n' << '\n';
std::cerr << desc << '\n';
return 1;
}
if(vm.count("help") || !vm.count("files")) {
std::cout << desc << "\n";
return 1;
}
}
The problem is that I can read files list as positional arguments lists as follows:
./a.out file1 file2 file3
but unfortunately like this as well ( which I would like to disable )
./a.out --files file1 file2 file3
The problem is also with the help which yields:
./a.out
Couldn't parse command line arguments properly:
the option '--files' is required but missing
Allowed options:
--help produce help message
--files arg list of files
So my desired scenario would be more like (os similar):
./a.out
Couldn't parse command line arguments properly:
[FILES ...] is required but missing
Allowed options:
--help produce help message
--optionx some random option used in future
[FILE ...] list of files
After I remove files options from desc.add_option()(...) it stop working so I believe I need it there.
As to the question posed in the title, "How to add a description to boost::program_options' positional options?", there's no functionality provided for this in the library. You need to handle that part yourself.
As for the body of the question... it's possible, but in a slightly round-about way.
The positional options map each position to a name, and the names need to exist. From what I can tell in the code (cmdline.cpp), the unregistered flag won't be set for arguments that are positional. [1], [2]
So, to do what you want, we can do the following:
Hide the --files option from showing up in the help. You will need to display appropriate help for the positional options yourself, but this is no different than before.
Add our own validation between parsing and storing of the parsed options to the variables_map.
Hiding --files from help
Here we take advantage of the the fact that we can create composite options_description using the add(...) member function:
po::options_description desc_1;
// ...
po::options_description desc_2;
// ...
po::options_description desc_composite;
desc_composite.add(desc_1).add(desc_2);
We can therefore place our files option into a hidden options_description, and create a composite that we will use only for the parsing stage. (see code below)
Preventing explicit --files
We need to intercept the list of options between parsing and storing them into the variables_map.
The run() method of command_line_parser returns an instance of basic_parsed_options, whose member options holds a vector of basic_options. There is an element for each parsed argument, and any positional options are enumerated starting from 0, any non-positional options have position -1. We can use this to perform our own validation and raise an error when we see --files as an explicit (non-positional) argument.
Example Source Code
See on Coliru
#include <boost/program_options.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace po = boost::program_options;
int main(int argc, const char* argv[])
{
std::vector<std::string> file_names;
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("test", "test option");
std::string const FILES_KEY("files");
// Hide the `files` options in a separate description
po::options_description desc_hidden("Hidden options");
desc_hidden.add_options()
(FILES_KEY.c_str(), po::value(&file_names)->required(), "list of files");
// This description is used for parsing and validation
po::options_description cmdline_options;
cmdline_options.add(desc).add(desc_hidden);
// And this one to display help
po::options_description visible_options;
visible_options.add(desc);
po::positional_options_description pos;
pos.add(FILES_KEY.c_str(), -1);
po::variables_map vm;
try {
// Only parse the options, so we can catch the explicit `--files`
auto parsed = po::command_line_parser(argc, argv)
.options(cmdline_options)
.positional(pos)
.run();
// Make sure there were no non-positional `files` options
for (auto const& opt : parsed.options) {
if ((opt.position_key == -1) && (opt.string_key == FILES_KEY)) {
throw po::unknown_option(FILES_KEY);
}
}
po::store(parsed, vm);
po::notify(vm);
} catch(const po::error& e) {
std::cerr << "Couldn't parse command line arguments properly:\n";
std::cerr << e.what() << '\n' << '\n';
std::cerr << visible_options << '\n';
return 1;
}
if (vm.count("help") || !vm.count("files")) {
std::cout << desc << "\n";
return 1;
}
if (!file_names.empty()) {
std::cout << "Files: \n";
for (auto const& file_name : file_names) {
std::cout << " * " << file_name << "\n";
}
}
}
Test Output
Valid options:
>example a b c --test d e
Files:
* a
* b
* c
* d
* e
Invalid options:
>example a b c --files d e
Couldn't parse command line arguments properly:
unrecognised option 'files'
Allowed options:
--help produce help message
--test test option

program_options - "invalid option value" when has to read array from file

I'm trying to read an array from a configuration file but it shows the message: " in option 'PARAM_ARRAY': invalid option value ".
The topic doesn't helps me because it reads the array from command line.
The code (only important lines) is something like this:
typedef boost::numeric::ublas::bounded_vector<double,6> vec6;
po::options_description parameters("Options");
parameters.add_options()
("PARAM_ARRAY", po::value< vec6 >(&configParameters.PARAM_ARRAY), "parameters array comment")
;
then I have this lines too:
po::options_description config_file_options;
config_file_options.add(parameters);
// Parser the command line options
po::variables_map vm;
po::store(po::command_line_parser(ac, av).
options(cmdline_options).run(), vm);
po::notify(vm);
// Verify if a config file was passed as an option
if (vm.count("config")) {
const std::vector<std::string> &config_files = vm["config"].as< std::vector<std::string> >();
for(std::vector<std::string>::const_iterator it = config_files.begin();
it != config_files.end(); ++it) {
ifstream ifs(it->c_str());
if (!ifs)
{
cout << "can not open config file: " << *it << "\n";
exit(1);
}
// Parse the options on the config file.
// NOTE: They are not going to be updated if they were already set by the command line option
po::store(parse_config_file(ifs, config_file_options), vm);
po::notify(vm);
}
}
And finally, my configuration file (.yaml) has:
PARAM_ARRAY= (0,0,0,0,0,0)
I've also tried:
PARAM_ARRAY= {0,0,0,0,0,0} and many other formats.
I already solved the problem. It was a little annoying blank space between each number, the "{}" should be "()" and I missed the size indication of the array PARAM_ARRAY.
I had:
PARAM_ARRAY= {0, 0, 0, 0, 0, 0}
but the configuration file should be like:
PARAM_ARRAY=[6](0,0,0,0,0,0)
Thank you anyway and hope this will help someone in the future.

Boost::Program_options, passing an unknown command line argument

I am using boost::program_options to pass configuration files for my program. In particular I use often command line overriding of some of the options. For example if I register two options "opt1" and "opt2" I can successfully override the default values by running my program with
myProgram.exe --opt1=option_value_1 --opt2=option_value_2
All good, but it happened already few times that I run my program mistakenly as
myProgram.exe --opt1=option_value_1 opt2=option_value_2
In such a case (missing double hyphen) no error is thrown. In fact I can apparently run myProgram as
myProgram.exe list of any unregistered and unknown values
and it still runs correctly. I would expect to at least get informed that something unexpected happened. Is there a solution to my problem?
You should remove allow_unregistered() from your parse command. You command should simply be
po::store(parse_command_line(argc, argv, desc), vm);
then exception will be thrown on unknown options.
http://www.boost.org/doc/libs/1_54_0/doc/html/program_options/howto.html#idp123440592
If you want exception/error, if option has no "--" you should write extra parser, something like this, can help you
std::pair<std::string, std::string> fix_option(const std::string& value)
{
std::string name = value;
std::string val;
std::string::size_type pos = name.find("=");
if (pos != std::string::npos)
{
val = name.substr(pos + 1);
name = name.substr(0, pos);
}
if (name.substr(0, 2) != "--")
{
throw std::logic_error(std::string("invalid command, no -- in command: ") + name);
}
return std::make_pair(name.substr(2), val);
}
code example
results:
./new --help=j
output: j
./new help=j
output:
terminate called after throwing an instance of 'std::logic_error'
what(): invalid command, no -- in command: help
It seems that boost::program_options does not recognize positional arguments per default. This means that non-option arguments like opt2=option_value_2 are ignored. However, the documentation is not clear about it. You can enable handling of positional arguments with basic_command_line_parser::positional().
By Example
try {
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).
options(desc).positional({}).run(),
vm);
po::notify(vm);
} catch (po::too_many_positional_options_error &e) {
// A positional argument like `opt2=option_value_2` was given
cerr << e.what() << endl;
exit(1);
} catch (po::error_with_option_name &e) {
// Another usage error occurred
cerr << e.what() << endl;
exit(1);
}
Explanation
Basically,
po::store(po::parse_command_line(argc, argv, desc), vm);
has been replaced with
po::store(po::command_line_parser(argc, argv)
.options(desc).positional({}).run(),
vm);
As I understand the documentation, parse_command_line(argc, argv, desc) is a shorthand for command_line_parser(argc, argv).options(desc).run(). Through adding a call to positional(), we are enabling handling of positional arguments. Through specifying {}, no positional arguments are allowed. An instance of too_many_positional_options_error is thrown when too many positional arguments are given.

boost::program_options crashes on help when implicit_value used somewhere else

I have the following code snippet to accept runtime program options. Everything works well as long as I don't have --help on the command line. On invoking --help I receive
malloc: * error for object 0x7fff7b646570: pointer being freed was not allocated
* set a breakpoint in malloc_error_break to debug
on the boost::any::holder class. If the implicit_value setting is removed everything works well (even with --help). Am I missing something here?
TIA,
Nikhil
// program options descritor
po::options_description allOpts("");
// general
po::options_description genOpt("General options");
genOpt.add_options()
("help", "produce help message")
;
// mandatory options
po::options_description manOpt("Mandatory options");
manOpt.add_options()
("tilesetData", po::value<std::string>()->required(),
"tile set image file (required)")
;
// modifiables
po::options_description modifiers("Modifiable options");
modifiers.add_options()
("takeSnaps", po::value<std::string>()->implicit_value("gameShots"),
"take screen shots after every display refresh")
("music", po::value<std::string>()->implicit_value("NOT_SPECIFIED.mp3"),
"play the music specified by the file")
;
// compile all options
allOpts.add(genOpt).add(manOpt).add(modifiers);
// parse command line
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, allOpts), vm);
// create help message
if (vm.count("help")) {
std::cout << allOpts << std::endl;;
return false;
}
// check program options
try {
po::notify(vm);
}
catch (std::exception& e){
std::cerr << "Error: " << e.what() << std::endl;
return false;
}
catch(...){
std::cerr << "Unknown error!" << std::endl;
return false;
}
I suspect this is due to an incompatibility between compiler versions. Probably the boost version you are using was compiled with an older version of gcc than the version you are using to compile the program. The solution is to use the same compiler to build boost and for compiling your program.

"Multiple occurrences" exception for boost program_options

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;
}