boost program options count number of occurrences of a flag - c++

I am trying to program in a way for the user of my program to specify the level of verbosity of my program from 0 to 3. I was told by someone that there might be a way to set up the program options so that I am able to detect the number of occurrences of a flag, and then run my program accordingly.
Example:
[none] -> level 0
-v -> level 1
-vv -> level 2
-vvv -> level 3
Does anyone know if this is possible? Do I just need to set up three different options, one for each possibility? I have tried to search around for a similar example however I fear I may be searching the wrong things.

I can't think of a nice way. boost::program_options option syntax is a little more structured (and arguably more simplistic) than getopt.
Here's one way:
#include <boost/program_options.hpp>
#include <iostream>
#include <algorithm>
#include <cstdlib>
int main(int argc, char**argv)
{
namespace po = boost::program_options;
std::string verbosity_values;
po::options_description desc("Command Line Options");
desc.add_options()("verbosity,v",
po::value(&verbosity_values)->implicit_value(""),
"verbose");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("verbosity")) {
verbosity_values += "v";
}
if (std::any_of(begin(verbosity_values), end(verbosity_values), [](auto&c) { return c != 'v'; })) {
std::cerr << "invalid verbosity" << std::endl;
std::exit(100);
}
std::cout << "verbosity: " << verbosity_values.size() << std::endl;
}
How it works:
define an option called "--verbosity" with a synonym "-v".
'verbosity' takes a string argument, which we default to ""
we check that the string contains only 'v's
if the variables_map contains a 'verbosity' argument, then -v or --verbosity must have been mentioned on the command line. Therefore, add a 'v' to the string.
verbosity is the length of the string.
example:
$ ./a.out -vvvv
verbosity: 4
$

Related

How to store data in boost::program_options::variable_map?

I am currently trying to rework some code that was handed down to me. The original point of the code is to read a configuration file, and to set up the different options in the file in a boost::program_options::variable_map, which is then read throughout other parts of the code which is already working fine.
Here is the code I am trying to replace:
// Some helpful definitions
boost::program_options::variables_map vm;
std::string filecfg = "File_name";
std::ifstream ifs(filecfg.c_str());
// Setting options (This is command line example, but config file is the same)
boost::program_options::options_description df("Command Line");
df.add_options()
("help", "display help message")
("file-cfg,f", boost::program_options::value<std::string>(), "config file")
("version", "display version and revision number");
boost::program_options::parsed_options parsedc = boost::program_options::parse_config_file(ifs, df, true);
boost::program_options::store(parsedc, vm);
boost::program_options::notify(vm);
std::vector <std::string> unrc = boost::program_options::collect_unrecognized(parsedc.options, boost::program_options::include_positional)
My thinking it to simply replace the boost::program_options::parsed_options parsedc and create this object by myself. The problem I run into is simply that there is no documentation on how to do this. I think it is mostly because it is not designed to be used this way.
In any case, I am just looking to fill up the vm object with the options described in dc, and with values that I can hold in a separate data structure (like a vector).
Is it possible to simply add values to vm? Or do I have to go through a function such as boost::program_options::store()?
Any help would be greatly appreciated! Let me know if something is unclear, or if there is something you'd like me to try!
Thanks!
Yeah you can.
Be aware that you will have to decide how to "mock"/"fake" the other semantics of it though. (E.g. you might want to masquerade the options as having been defaulted)
Conceptually, variable_map would be a map<string, variable_value>. variable_value:
Class holding value of option. Contains details about how the value is
set and allows to conveniently obtain the value.
Note also that because variable_value uses boost::any for storage you will have to be exact about the types you will store. (So, don't store "oops" if you need a std::string("ah okay")).
Here's a simple demo:
Live On Coliru
#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>
namespace po = boost::program_options;
using namespace std::string_literals;
int main(/*int argc, char** argv*/) {
// Some helpful definitions
po::variables_map vm;
vm.emplace("file-cfg", po::variable_value("string"s, true));
vm.emplace("log-level", po::variable_value(3, false));
vm.emplace("option3", po::variable_value{});
notify(vm);
std::vector unrc = { "unrecognized"s, "options"s };
for (auto& [name, value] : vm) {
std::cout
<< "Name: " << name
<< std::boolalpha
<< "\tdefaulted:" << value.defaulted()
<< "\tempty:" << value.empty();
if (typeid(std::string) == value.value().type())
std::cout << " - string " << std::quoted(value.as<std::string>()) << "\n";
else if (typeid(int) == value.value().type())
std::cout << " - int " << value.as<int>() << "\n";
else if (!value.empty())
std::cout << " - unknown type\n";
}
}
Prints
Name: file-cfg defaulted:true empty:false - string "string"
Name: log-level defaulted:false empty:false - int 3
Name: option3 defaulted:false empty:true
I warn you not to use vm.emplace(…, po::variable_value(…,…)).
It's quite deceptive: it will work to some extent but fail spectacularly elsewhere.
When you use po::store, it internally also produces a po::variable_value and copies the semantic of your option to a private field of the po::variable_value. There's no way to set this semantic yourself. (see: https://github.com/boostorg/program_options/blob/develop/src/variables_map.cpp#L83).
Without the semantic you can't at least:
read the option from multiple sources
vm.notify() will not write the value to variables associated with the option
Here's an (arguably ugly) way that should avoid these issues:
po::variables_map vm;
po::options_description opts;
opts.add_options()("optName", …);
…
po::parsed_options parsedOpts(&opts);
pOpts.options.push_back(po::option("optName", {"optValue"}));
po::store(parsedOpts, vm);
First, one has to create a parsedOpts object that stores the description of options. Then, add an option name and list of values as strings, regardless of the option type. Finally, within po::store, the name and value(s) are parsed and stored in vm.
A complete working example:
#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;
int main(int ac, char **av) {
int i = 0;
po::variables_map vm;
po::options_description opts;
opts.add_options()("id", po::value<int>(&i)->default_value(1));
int userInput;
std::cin >> userInput;
if (userInput == 2) {
vm.emplace("id", po::variable_value(userInput, false));
}
if (userInput == 3) {
po::parsed_options pOpts(&opts);
pOpts.options.push_back(po::option("id", {std::to_string(userInput)}));
po::store(pOpts, vm);
}
po::store(po::parse_command_line(ac, av, opts), vm);
//po::store(po::parse_config_file("options.ini", opts, true), vm);
po::notify(vm);
std::cout << "id (notified): " << i << std::endl;
std::cout << "id (from vm): " << vm["id"].as<int>() << std::endl;
return 0;
}
Typing 2 runs vm.emplace and yields:
id (notified): 0
id (from vm): 2
which is bad, for po::value<int>(&i) was ignored
Typing 2 and adding --id=4 command line argument yields:
terminate called after throwing an instance of 'boost::wrapexcept<boost::program_options::multiple_occurrences>'
which is bad, for you cannot use multiple sources
Typing 3 runs the po::store on hand-crafted po::parsed_options and yields:
id (notified): 3
id (from vm): 3
Typing 3 and adding --id=4 command line argument yields:
id (notified): 3
id (from vm): 3
Which is correct & expected, for the subsequent stores shall not replace values that were stored earlier (see https://www.boost.org/doc/libs/1_80_0/doc/html/program_options/tutorial.html#id-1.3.30.4.5)

Is this kind of repeatable options possible with boost::program_options?

I have options --foo (short form -f) and --bar that need special treatment, they are repeatable and order should matter. So, for the following:
program --foo 1 --z -f 2 --bar 3 --x --foo 4
I'd like to range a key value map being able to construct [("foo", 1), ("foo", 2), ("bar", 3), ("foo", 4)].
Please notice the order of this array of tuples, it's the same as that in the command line. I've discarded non-important options in the array, but they may be present in the command line nonetheless.
It seems the sole way to allow repeatable options with boost::program_options is calling composing() for any given option, but then, since each will store all their values in a vector, I lose the order I need for interlacing options.
So, can boost::program_options help with this?
EDIT
I've asked for alternative software recommendations here: https://softwarerecs.stackexchange.com/questions/31766/
And answered it using Poco.
Assuming you can accept having to put an equals sign between the --foo and the value, this might do what you want:
#include <iostream>
#include <boost/program_options.hpp>
#include <vector>
#include <iterator>
#include <algorithm>
int main(int argc, const char**argv)
{
std::vector<int> foos;
boost::program_options::options_description desc("Command Line Options");
desc.add_options()
("foo,F", boost::program_options::value(&foos)->multitoken(), "integers for foo");
std::cout << desc << std::endl;
boost::program_options::variables_map vm;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
boost::program_options::notify(vm);
std::copy(foos.begin(), foos.end(), std::ostream_iterator<int>(std::cout, ", "));
std::cout << std::endl;
return 0;
}
called with command line:
./a.out --foo=1 --foo=2
yields:
Command Line Options:
-F [ --foo ] arg integers for foo
1, 2,

How do I get default argument values with boost program options?

I want to use default values for some of my command line arguments. How do I tell program_options what the default option is, and, if the user doesn't supply the argument, how do I tell my program to use the default value?
Say I want to have an argument specifying the number of robots to send on a murderous rampage with a default value of 3.
robotkill --robots 5 would produce 5 robots have begun the silicon revolution, whereas
robotkill (no arguments supplied) would produce 3 robots have begun the silicon revolution.
program_options automatically assigns default values to options when the user doesn't supply those options. You don't even need to check whether the user supplied a given option, just use the same assignment in either case.
#include <iostream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
int main (int argc, char* argv[]) {
po::options_description desc("Usage");
desc.add_options()
("robots", po::value<int>()->default_value(3),
"How many robots do you want to send on a murderous rampage?");
po::variables_map opts;
po::store(po::parse_command_line(argc, argv, desc), opts);
try {
po::notify(opts);
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
int nRobots = opts["robots"].as<int>();
// automatically assigns default when option not supplied by user!!
std::cout << nRobots << " robots have begun the silicon revolution"
<< std::endl;
return 0;
}

boost::program_options - does it do an exact string matching for command line options?

There seems to be a problem the way boost::program_options's options_description matching is done.
int main(int argc, char* argv[])
{
boost::program_options::options_description desc("CmdLine utility");
desc.add_options()
("hel", "hel message")
("help", "produce help message")
("helps","helps message")
;
boost::program_options::variables_map vm;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv,desc), vm);
boost::program_options::notify(vm);
if(vm.count("help")) {
std::cout << desc << std::endl;
}
if(vm.count("helps")) {
std::cout << "helps..." << std::endl;
}
if(vm.count("hel")) {
std::cout << "hel..." << std::endl;
}
return 0;
}
Output -
C:\code>cmd.exe --helps
helps...
C:\code>cmd.exe --help
helps...
C:\code>cmd.exe --hel
helps...
The output changes if I change the order in which options are added using add_options() call. Also it looks like program_options does not do a complete command string matching, so even if you enter a substring of the option, it will consider it as a valid option without doing a complete string comparison. If this is a boost::program_options feature, is there any way to force it to do exact string matching rather than doing it with substring matching ? (I am using Boost version 1.42)
By default, program_option has allow_guessing style bit on, so a substring match is sufficient. The behaviour you observe, where an option is matching a prefix of the command line, even when there's a different option that matches fully, is a bug. It's fixed in 1.45.
Maybe you called wrongly. You example is fine. Look at the output I got :
[vladimir#asa example]$ ./a.out --help
CmdLine utility:
--hel hel message
--help produce help message
--helps helps message
[vladimir#asa example]$ ./a.out --hel
hel...
[vladimir#asa example]$ ./a.out --helps
helps...

How to accept empty value in boost::program_options

I'm using boost::program_options library to process command line params.
I need to accept a file name via -r option, in case if it is empty (-r given without params) I need to use stdin.
desc.add_options()
("replay,r", boost::program_options::value<std::string>(), "bla bla bla")
In this case boost wouldn't accept -r without params and throw an exception.
default_value () option does not work as well as it would make library return value even if user didn't give -r option.
Any ideas how to work around?
Please use the implicit_value method, e.g
desc.add_options()
("replay,r", po::value<std::string>()->implicit_value("stdin"), "bla bla bla")
This makes the option accept either 0 or 1 token, and if no tokens are provided, it will act as if 'stdin' was provided. Of course, you can pick any other implicit value -- including empty string and '-' as suggested by mch.
You could try a trick with the multitoken and zero_tokens options:
using namespace std;
namespace po = boost::program_options;
vector<string> replay;
po::options_description desc("Allowed options");
desc.add_options()
("replay,r", po::value< vector<string> >(&replay)->multitoken()->zero_tokens(), "bla bla bla");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("replay"))
{
size_t s = vm["replay"].as< vector<string> >().size();
if (s == 0)
cout << "replay without args" << endl;
else if (s == 1)
cout << "replay with one arg" << endl;
else
cout << "replay with multiple args" << endl;
}
else
cout << "replay not specified" << endl;
Then just count the number of elements in the replay vector. You'll want to throw an error if multiple arguments are passed to the replay option.
I don't think any command line parsing libraries allow you to have options that can either take an argument or not. If an option requires an argument, you must give one. In this case, the standard practice (in *NIX anyway) is to use '-' as a filename to denote that you want to read from standard input.