boost::program_options ignore options in config file - c++

Could you please help me with boost::program_options?
I want the parser to ignore unknown options that are saved in config file.
I know that allow_unregistered() can be used for cmd line options, how do I proceed with text files?
Here is stripped code:
namespace po = boost::program_options;
try {
string config_file;
string gps_source;
int op_baud;
po::options_description generic("Generic options");
generic.add_options()
("ssdvpacksize", po::value<int>(),
"ssdv packets size in bytes")
("ssdvdir", po::value<string>()->default_value("/ARY1/ssdv"),
"ssdv image dir")
//unused
//I have to specify these even if they're unused
("ssdvproc_dir", po::value<string>(), "")
;
po::options_description file_options;
file_options.add(generic);
po::options_description cli_options("command line interface options");
cli_options.add(generic);
cli_options.add_options()
("config", po::value<string>(&config_file)->default_value("/boot/ary-1.cfg"), "name of a file of a configuration.");
po::variables_map vm;
store( po::command_line_parser(ac, av).options(cli_options).allow_unregistered().run(), vm );
//store( po::basic_command_line_parser<char>(ac, av).options(cli_options).allow_unregistered().run(), vm );
notify(vm);
ifstream ifs(config_file.c_str());
if (!ifs)
{
cout << "Can not open config file: " << config_file << "\n";
}
else
{
// probably smth. to do here ?
//store(parse_config_file(ifs, file_options).allow_unregistered(), vm); // does not work
store(parse_config_file(ifs, file_options), vm);
notify(vm);
}
// ...
// rest of program
}

OK, the solution is embarrassingly easy.
Line 44 should be:
store(parse_config_file(ifs, file_options, true/*allow unregistered*/), vm);

Related

boost program_options: Read required parameter from config file

I want to use boost_program_options as follows:
get name of an optional config file as a program option
read mandatory options either from command line or the config file
The problem is: The variable containing the config file name is not populated until po::notify() is called, and that function also throws exceptions for any unfulfilled mandatory options. So if the mandatory options are not specified on the command line (rendering the config file moot), the config file is not read.
The inelegant solution is to not mark the options as mandatory in add_options(), and enforce them 'by hand' afterwards. Is there a solution to this within the boost_program_options library?
MWE
bpo-mwe.conf:
db-hostname = foo
db-username = arthurdent
db-password = forty-two
Code:
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <boost/program_options.hpp>
// enable/disable required() below
#ifndef WITH_REQUIRED
#define WITH_REQUIRED
#endif
namespace po = boost::program_options;
namespace fs = std::filesystem;
int main(int argc, char *argv[])
{
std::string config_file;
po::options_description generic("Generic options");
generic.add_options()
("config,c", po::value<std::string>(&config_file)->default_value("bpo-mwe.conf"), "configuration file")
;
// Declare a group of options that will be
// allowed both on command line and in
// config file
po::options_description main_options("Main options");
main_options.add_options()
#ifdef WITH_REQUIRED
("db-hostname", po::value<std::string>()->required(), "database service name")
("db-username", po::value<std::string>()->required(), "database user name")
("db-password", po::value<std::string>()->required(), "database user password")
#else
("db-hostname", po::value<std::string>(), "database service name")
("db-username", po::value<std::string>(), "database user name")
("db-password", po::value<std::string>(), "database user password")
#endif
;
// set options allowed on command line
po::options_description cmdline_options;
cmdline_options.add(generic).add(main_options);
// set options allowed in config file
po::options_description config_file_options;
config_file_options.add(main_options);
// set options shown by --help
po::options_description visible("Allowed options");
visible.add(generic).add(main_options);
po::variables_map variable_map;
// store command line options
// Why not po::store?
//po::store(po::parse_command_line(argc, argv, desc), vm);
store(po::command_line_parser(argc, argv).options(cmdline_options).run(), variable_map);
notify(variable_map); // <- here is the problem point
// Problem: config_file is not set until notify() is called, and notify() throws exception for unfulfilled required variables
std::ifstream ifs(config_file.c_str());
if (!ifs)
{
std::cout << "can not open configuration file: " << config_file << "\n";
}
else
{
store(parse_config_file(ifs, config_file_options), variable_map);
notify(variable_map);
}
std::cout << config_file << " was the config file\n";
return 0;
}
I'd simply not use the notifying value-semantic to put the value in config_file. Instead, use it directly from the map:
auto config_file = variable_map.at("config").as<std::string>();
Now you can do the notify at the end, as intended:
Live On Coliru
#include <boost/program_options.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
namespace po = boost::program_options;
int main(int argc, char *argv[])
{
po::options_description generic("Generic options");
generic.add_options()
("config,c", po::value<std::string>()->default_value("bpo-mwe.conf"), "configuration file")
;
// Declare a group of options that will be allowed both on command line and
// in config file
struct {
std::string host, user, pass;
} dbconf;
po::options_description main_options("Main options");
main_options.add_options()
("db-hostname", po::value<std::string>(&dbconf.host)->required(), "database service name")
("db-username", po::value<std::string>(&dbconf.user)->required(), "database user name")
("db-password", po::value<std::string>(&dbconf.pass)->required(), "database user password")
;
// set options allowed on command line
po::options_description cmdline_options;
cmdline_options.add(generic).add(main_options);
// set options allowed in config file
po::options_description config_file_options;
config_file_options.add(main_options);
// set options shown by --help
po::options_description visible("Allowed options");
visible.add(generic).add(main_options);
po::variables_map variable_map;
//po::store(po::parse_command_line(argc, argv, desc), vm);
store(po::command_line_parser(argc, argv).options(cmdline_options).run(),
variable_map);
auto config_file = variable_map.at("config").as<std::string>();
std::ifstream ifs(config_file.c_str());
if (!ifs) {
std::cout << "can not open configuration file: " << config_file << "\n";
} else {
store(parse_config_file(ifs, config_file_options), variable_map);
notify(variable_map);
}
notify(variable_map);
std::cout << config_file << " was the config file\n";
std::cout << "dbconf: " << std::quoted(dbconf.host) << ", "
<< std::quoted(dbconf.user) << ", "
<< std::quoted(dbconf.pass) << "\n"; // TODO REMOVE FOR PRODUCTION :)
}
Prints eg.
$ ./sotest
bpo-mwe.conf was the config file
dbconf: "foo", "arthurdent", "forty-two"
$ ./sotest -c other.conf
other.conf was the config file
dbconf: "sbb", "neguheqrag", "sbegl-gjb"
$ ./sotest -c other.conf --db-user PICKME
other.conf was the config file
dbconf: "sbb", "PICKME", "sbegl-gjb"
Where as you might have guessed other.conf is derived from bpo-mwe.conf by ROT13.
Differentiate between configuration file and command-line arguments, don't parse both into the same map.
Instead first parse the command-line arguments separately, get the configuration file name (if there is any) and then load the file and parse it into a second map.
If some configuration-file values can be provided on the command line as well, then I personally do two passes over the command-line arguments, making it a three-step process:
Parse command-line arguments, ignore all but the config option
Read and parse the configuration file
And do a second pass over the command-line arguments, ignoring the config 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_option case insensitive parsing

Has anyone worked out how to get boost program options to parse case insensitive argument lists
In the boost documentation, it appears that it is supported. See http://www.boost.org/doc/libs/1_53_0/boost/program_options/cmdline.hpp
Namely, setting the style_t enum flag such as long_case_insensitive. However, I'm not sure how to do it. Eg how would you get the following code snippet to accept --Help or --help or --HELP
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<double>(), "set compression level")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << "\n";
return 0;
}
You can modify the style when you call store. I believe this should work for you:
namespace po_style = boost::program_options::command_line_style;
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc)
.style(po_style::unix_style|po_style::case_insensitive).run(), vm);
po::notify(vm);

Read one parameter from config file where there are more parameters, with boost program_options

I have a cfg file as the following:
parameter1="hello"
parameter2=22
parameter3=12
Using boost_program to read all the parameters works fine with this code:
po::options_description options("Options");
options.add_options()
("help,h", "produce help message")
("parameter1", po::value<string>(&parameter1)->default_value("bye"),
"parameter1")
("parameter2", po::value<int>(&parameter2)->default_value(2),
"parameter2")
("parameter3", po::value<int>(&parameter3)->default_value(4),
"parameter3");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, options), vm);
notify(vm);
try
{
po::store(po::parse_config_file< char >(filePath, options), vm);
}
catch (const std::exception& e)
{
std::cerr << "Error parsing file: " << filePath << ": " << e.what() << std::endl;
}
...
But when I try to do a generic method where i just want to read one parameter given from a call, I have an error parsing.
I want to read the second parameter for instance, so I write this:
const char parameter_string = "parameter2";
int default = 30;
int parameter;
getparameter(parameter_string,parameter,default);
and goes to the method getsparameter where this is what I have this time:
...
po::options_description options("Options");
options.add_options()
("help,h", "produce help message")
(parameter_string, po::value<int>(&parameter)->default_value(default),
"reading parameter");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, options), vm);
notify(vm);
but the error is:
Error parsing file: file.cfg: unknown option parameter1
So my question is if it is posible to read only one parameter from a file or it is necessary to parse all the parameters with boost_program in options.add_option including as many lines as parameters I write in the config file and then take the value from the parameter I want.
Use allow_unregistered function :
Specifies that unregistered options are allowed and should be passed
though. For each command like token that looks like an option but does
not contain a recognized name, an instance of basic_option will
be added to result, with 'unrecognized' field set to 'true'. It's
possible to collect all unrecognized options with the
'collect_unrecognized' funciton.
As I an using "parse_config_file" I see in the documentation that "allow_unregistered" is set to false by default.
template<typename charT>
BOOST_PROGRAM_OPTIONS_DECL basic_parsed_options< charT >
parse_config_file(std::basic_istream< charT > &,
const options_description &,
bool allow_unregistered = false);
So i modified my line like this:
Old code:
po::store(po::parse_config_file< char >(filePath, options), vm);
New code:
po::store(po::parse_config_file< char >(filePath, options, true), vm);
And as I said, It works. Thank you for your answer.

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