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,
Related
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)
I'm currently working with boost::program_options. My program is supposed to take as arguments (amongst other things...) an arbitrary number of 'lists' of arbitrary length. For example, the user should be able to call
./myprogram -list item1 item2 item3 -list item1 item2 -list item1 item2
Obviously, I don't want to get one list/vector with all the items one after the other as a result, but (in this case) three lists/vectors (or, for example, one vector of vectors containing the elements) with two or three items per list (each item is supposed to be a string, but I guess this doesn't matter).
As I said before, the number of lists (as well as the number of items per list!) should be arbitrary.
How can I do that with boost::program_options?
This can be done without a whole lot of extra code. The secret is to separate the parsing step from the storage step, as also done in this answer.
The parser will return a container of key/value structs as the options are presented from the user. If an option is submitted multiple times then the container will have a separate entry for each option submission. It is quite straightforward to scan for a particular option and organize its values however we want.
Here's an example that prints out each input multi-token option on a separate line:
#include <iostream>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
int main(int argc, char *argv[]) {
// Define a multi-token option.
po::options_description desc("Allowed options");
desc.add_options()
("list", po::value<std::vector<std::string>>()->multitoken(), "multiple values");
// Just parse the options without storing them in a map.
po::parsed_options parsed_options = po::command_line_parser(argc, argv)
.options(desc)
.run();
// Build list of multi-valued option instances. We iterate through
// each command-line option, whether it is repeated or not. We
// accumulate the values for our multi-valued option in a
// container.
std::vector<std::vector<std::string>> lists;
for (const po::option& o : parsed_options.options) {
if (o.string_key == "list")
lists.push_back(o.value);
}
// If we had other normal options, we would store them in a map
// here. In this demo program it isn't really necessary because
// we are only interested in our special multi-valued option.
po::variables_map vm;
po::store(parsed_options, vm);
// Print out the multi-valued option, each separate instance on its
// own line.
for (size_t i = 0; i < lists.size(); ++i) {
for (size_t j = 0; j < lists[i].size(); ++j)
std::cout << lists[i][j] << ' ';
std::cout << '\n';
}
return 0;
}
And here's a sample invocation (live at coliru):
$ ./po --list 1 2 3 --list foo bar --list how now brown cow
1 2 3
foo bar
how now brown cow
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
$
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...
I would like to pass the multiple arguments with positive or negative values.
Is it possible to parse it?
Currently I have a following initialization:
vector<int> IDlist;
namespace po = boost::program_options;
po::options_description commands("Allowed options");
commands.add_options()
("IDlist",po::value< vector<int> >(&IDlist)->multitoken(), "Which IDs to trace: ex. --IDlist=0 1 200 -2")
("help","print help")
;
and I would like to call:
./test_ids.x --IDlist=0 1 200 -2
unknown option -2
So,the program_options assumes that I am passing -2 as an another option.
Can I configure the program_options in such a way that it can accept the negative integer values?
Thanks
Arman.
EDIT:
BTW I was parsing it by the simple parser
store(command_line_parser(argc, argv).options(commands).run(), vm);
, but solution was to use the extended one:
parse_command_line
Have you tried "-2"?
Edit: Quoting doesn't seem to do the trick, however, changing the command line style works:
char* v[] = {"name","--IDlist=0","1","200","-2"};
int c = 5;
std::vector<int> IDlist;
namespace po = boost::program_options;
po::options_description commands("Allowed options");
commands.add_options()
("IDlist",po::value< std::vector<int> >(&IDlist)->multitoken(), "Which IDs to trace: ex. --IDlist=0 1 200 -2")
("help","print help")
;
po::variables_map vm;
po::store(parse_command_line(c, v, commands, po::command_line_style::unix_style ^ po::command_line_style::allow_short), vm);
po::notify(vm);
BOOST_FOREACH(int id, IDlist)
std::cout << id << std::endl;
NOTE: this is a remark to the accepted solution.
Disabling short options is the key. The solution above proposed by kloffy works great, but if you happen to use positional_option_description (e.g. to parse parameters without using an option like ls file.txt instead of ls --file=file.txt) you might have a hard time converting your code to do that using parse_command_line.
However you can also disable short options and keep using the basic_command_line_parser like this:
Replace
store(command_line_parser(argc, argv).options(commands).run(), vm);
with
store(command_line_parser(argc, argv).options(commands).style(
po::command_line_style::unix_style ^ po::command_line_style::allow_short
).run(), vm);
maybe try --IDlist "0, 1, 200, -2" or --IDlist="0, 1, 200, -2"