This is a purely aesthetic issue.
I have written a CLI program in C++ and use boost::program_options to pasrse its args.
Per default, boost names all meta variables arg. I'd like to change that.
Here's the code:
#include <iostream>
using std::cerr;
using std::cout;
#include <optional>
using std::optional;
#include <string>
using std::string;
#include <boost/program_options.hpp>
using boost::program_options::unknown_option;
#include "Net.h"
#include "GameServer.h"
using proto::GameServer;
namespace args = boost::program_options;
static auto parseArgs(int argc, const char *argv[])
{
args::options_description desc("Command line options");
desc.add_options()
("help,h", "Show this page")
("address,a", args::value<string>()->default_value(net::Defaults::HOST), "IP address to listen on")
("port,p", args::value<unsigned short>()->default_value(net::Defaults::PORT), "Port to listen on")
;
optional<args::variables_map> result;
args::variables_map varMap;
try {
args::store(args::parse_command_line(argc, argv, desc), varMap);
} catch (unknown_option const &error) {
cerr << "Invalid option: " << error.get_option_name() << "\n";
cerr << desc << "\n";
return result;
}
args::notify(varMap);
if (varMap.count("help")) {
cout << desc << "\n";
return result;
}
return result = varMap;
}
int main(int argc, const char *argv[])
{
auto parsedArgs = parseArgs(argc, argv);
if (!parsedArgs.has_value())
return 1;
auto args = parsedArgs.value();
auto address = args.at("address").as<string>();
auto port = args.at("port").as<unsigned short>();
GameServer server(address, port);
server.listen();
}
And the output generated by the help page:
Command line options:
-h [ --help ] Show this page
-a [ --address ] arg (=127.0.0.1) IP address to listen on
-p [ --port ] arg (=9000) Port to listen on
I'd like to rename arg for -a to ip_address and for -p to portnum respectively.
#prehistoricpenguin's answer brought me onto the right track. In boost, the typed_value class has a method value_name() to set the argument name, which works analogous to setting the defaults, i.e. it modifies the value object and returns it for subsequent operations:
static auto parseArgs(int argc, const char *argv[])
{
options_description desc("Command line options");
desc.add_options()
("help,h", "Show this page")
("address,a", value<string>()->default_value(HOST)->value_name("ip_address"),
"IP address to connect to")
("port,p", value<unsigned short>()->default_value(PORT)->value_name("portnum"),
"Port to connect to");
return parseArgDesc(argc, argv, desc);
}
Resulting in:
Command line options:
-h [ --help ] Show this page
-a [ --address ] ip_address (=127.0.0.1)
IP address to connect to
-p [ --port ] portnum (=9000) Port to connect to
Seems it's an undocumented part of boost::program_options, I have a quick look at the implementation of boost::program_options and find there is a global variable which is used to control the behavior, so we come up with one line code hack:
args::arg = "i_am_not_arg";
Modify a global variable is not an elegant way, but I haven't found any usable APIs to do it, you may do more research and try to find a better solution(Don't forget to notify me here!)
Insert this line in some place of your code, then we get the output for --help command line:
->./a.out --help
Command line options:
-h [ --help ] Show this page
-a [ --address ] i_am_not_arg (=1234) IP address to listen on
-p [ --port ] i_am_not_arg (=42) Port to listen on
Suggestions for asking question on SO:
provide a minimal, reproducible program. Your code doesn't compile on my machine, because you used variables from files that are not provided.
Online demo
Related
I need to pass some specific arguments from one programm to another using boost::program_options and boost::process. Here is a simple example. In this example I need to pass all args stored in vm_slave to child process, but in common case I wanna pass one or more specific args from vm_slave.
#include <iostream>
#include <boost/process.hpp>
#include <boost/program_options.hpp>
using namespace std;
namespace po = boost::program_options;
int main(int argc, char* argv[]) {
po::options_description tester_options("Tester options");
po::options_description slave_options("Slave options");
tester_options.add_options()
("help,h", "Show help")
("iter,i", po::value<short>()->default_value(1), "TODO")
("modules,m", po::value<std::vector<string>>()->multitoken(), "TODO");
slave_options.add_options()
("size,s", po::value<size_t>()->required(), "TODO")
("threads,t", po::value<short>()->default_value(1), "TODO");
po::variables_map vm;
po::variables_map vm_slave;
auto p0 = po::command_line_parser(argc, argv).options(tester_options).allow_unregistered().run();
auto p1 = po::command_line_parser(argc, argv).options(slave_options).allow_unregistered().run();
po::store(p0, vm);
po::store(p1, vm);
po::store(p1, vm_slave);
// Do some stuff such as write help if needed
// ...
// I need call child process with all (or specific) args from vm_slave
boost::process::ipstream pipe;
boost::process::child cp(
"slave" /* + vm_slave args */,
boost::process::std_err > pipe,
boost::process::std_out > pipe
);
cp.wait();
return 0;
}
Of course I can do something like this:
ostringstream args;
for (const auto& arg : p1.options) {
if (vm_slave.count(arg.string_key) == 0)
continue;
args << arg.string_key << " ";
for (const auto& val : arg.value)
args << val << " ";
}
string cmd_args = args.str();
But in this case some args stored in vm_slave by default is lost.
Or I can do this:
ostringstream args;
for (const auto& arg : vm_slave) {
args << arg.first << " ";
const auto& any_val = arg.second.value();
if (boost::any_cast<size_t>(any_val))
args << to_string(boost::any_cast<size_t>(any_val));
else if (boost::any_cast<short>(any_val))
args << to_string(boost::any_cast<size_t>(any_val));
// And more and more casts...
args << " ";
}
But now we have many any_cast...
If I just pass argv to child process, the child might fail because extra args presented (args like iter not intended for this application).
All this attempts seem bad to me.
What is the proper way to convert parsed arguments back to command line?
There is no "proper way" - as composing command lines is not a feature of the library (neither is writing config-files).
I would use the parsed options. Unless you need to interpret the options you don't have to notify/store into a variable map at all:
std::vector<std::string> passthrough;
for (auto& opt : p1.options) {
if (opt.unregistered || opt.string_key.empty())
continue;
assert(p1.description == &slave_options);
auto& tok = opt.original_tokens;
fmt::print("passing through {}: {}\n", opt.string_key, tok);
passthrough.insert(passthrough.end(), tok.begin(), tok.end());
}
Demoing, using printf instead of slave:
if (vm.count("help")) {
std::cout << tester_options << "\n\n" << slave_options << "\n";
} else {
// demo using printf '- %q\n'
passthrough.insert(passthrough.begin(), " - %q\n");
bp::child cp("/usr/bin/printf", passthrough);
cp.wait();
}
See it Compiler Explorer
running ./sotest -i 1000 -m some modules to follow -h
Tester options:
-h [ --help ] Show help
-i [ --iter ] arg (=1) TODO
-m [ --modules ] arg TODO
Slave options:
-s [ --size ] arg TODO
-t [ --threads ] arg (=1) TODO
running ./sotest -i 1000 -m some modules to follow -s=89 -t42
passing through size: ["-s=89"]
passing through threads: ["-t42"]
- '-s=89'
- -t42
running ./sotest -i 1000 -m some modules to follow --size 89 --threads=42
passing through size: ["--size", "89"]
passing through threads: ["--threads=42"]
- --size
- 89
- '--threads=42'
running ./sotest -i 1000 -m some modules to follow -s 89 -t 42
passing through size: ["-s", "89"]
passing through threads: ["-t", "42"]
- -s
- 89
- -t
- 42
I want to write a program that parses a config file, and allows the command line to override what's written there. So I can have a config file that says:
[section1]
opt1=42
[section2]
opt2=17
And then, I can run the command with:
./so --opt2=3
And the program will get opt1 as 42 and opt2 and 3. I use the following program to try and do it:
#include <fstream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
int main(int argc, char *argv[]) {
po::options_description options1("section1");
options1.add_options()
("opt1", po::value<int>(), "Option 1");
po::options_description options2("section2");
options2.add_options()
("opt2", po::value<int>(), "Option 2");
po::options_description options;
options.add(options1);
options.add(options2);
po::variables_map values;
po::store( po::command_line_parser( argc, argv ).options(options).run(), values );
std::ifstream iniFile( "options.ini" );
po::store(
parse_config_file( iniFile, options ),
values );
}
This, of course, doesn't work. Boost::program_options wants opt1 under section1 to be called section1.opt1. If I do that, however, my program becomes harder to maintain on two fronts:
I need to define two options_descriptions, one for the INI and one for the command line.
Since the options' keys are now different, I need to manually merge the two.
Is there a way achieve this without doing the work manually?
The trivial solution is, to not use the sections. There might be some confusion around "sections" in the options descriptions vs. sections in ini-files.
The sections in ini-files refer only to options named with embedded periods: "section1.opt1". So you can simply write the config file as:
opt1=42
# perhaps with a comment
opt2=17
See it Live On Coliru
#include <boost/program_options.hpp>
#include <fstream>
#include <iostream>
namespace po = boost::program_options;
int main(int argc, char* argv[]) {
po::options_description options;
options.add_options()
("opt1", po::value<int>(), "Option 1")
("section2.opt2", po::value<int>(), "Option 2");
std::cout << options << "\n";
po::variables_map values;
po::store(po::parse_command_line(argc, argv, options), values);
std::ifstream iniFile("options.ini");
po::store(parse_config_file(iniFile, options), values);
auto report = [&values](char const* name) {
if (auto opt = values[name]; !opt.empty())
std::cout << name << ": " << opt.as<int>() << "\n";
};
report("opt1");
report("opt2");
report("section1.opt1");
report("section2.opt2");
}
Prints
echo "opt1=42" >> options.ini; ./a.out --section2.opt2 99
--opt1 arg Option 1
--section2.opt2 arg Option 2
opt1: 42
section2.opt2: 99
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
I have two switches, 'i' and 'p' that represent IPAddress and Port respectively.
What is the format of the command line?
I have tried:
app -i192.168.1.1 -p12345
app -i 192.168.1.1 -p 12345
app -i=192.168.1.1 -p=12345
app -i='192.168.1.1' -p='12345'
app --IPAddress 192.168.1.1 --Port12345
My application is having a problem with the IPAddress, and troubleshooting with DDD is unrevealing as I get for the vm.
Also, the app is running as a daemon, so my cout statements for the IP address and Port are going into oblivion, and printing to the syslog is hindered by the fact that outputting the values is not a const char*.
I plan to use program options for other things as well, but I am in over my head a bit with this.
po::options_description config("Configuration");
config.add_options()
("IPAddress,i","IP Address")
("Port,p","Port")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, config),
vm);
po::notify(vm);
//...and this is how the values are used
int retval = getaddrinfo((vm["IPAddress"].as< string >()).c_str(),(vm["Port"].as<string>()).c_str(), &hint, &list);
Here is a complete program...nothing is printed to the console after 'Values':
#include <sstream>
#include <algorithm>
#include <stdlib.h>
#include <iterator>
#include <string>
//Using boost program options to read command line and config file data
#include <boost/program_options.hpp>
using namespace std;
using namespace boost;
namespace po = boost::program_options;
int main (int argc, char *argv[])
{
po::options_description config("Configuration");
config.add_options()
("IPAddress,i","IP Address")
("Port,p","Port")
;
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, config),vm);
po::notify(vm);
cout << "Values\n";
cout << (vm["IPAddress"].as< string >()).c_str();
cout << " " << (vm["Port"].as<string>()).c_str();
return 0;
}
Are the inputted values somehow unprintable?
Here is gdb output, seems to be be cast problem:
28 string address = (vm["IPAddress"].as< string >()).c_str();
(gdb) n
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::bad_any_cast> >'
what(): boost::bad_any_cast: failed conversion using boost::any_cast
Program received signal SIGABRT, Aborted.
0x0000003afd835935 in raise () from /lib64/libc.so.6
BOOST Program options support the common command line flavours known from Unix systems.
Thus those two should work (they are working for me)
app -i 192.168.1.1 -p 12345
app --IPAddress=192.168.1.1 --Port=12345
Remarks:
The documentation with basic tutorial is at boost.org (probably you know this already)
writing a standalone unit test for this is certainly a good advice; boost also provides an easy-to-use test framework for C++
So I need some way of turning given Protocol://URLorIP:Port string into string ip int port How to do such thing with boost ASIO and Boost Regex? Or is it possible - to get IP using C++ Net Lib (boost candidate) - notice - we do not need long connection - only IP.
So I currently use such code for parsing
#include <boost/regex.hpp>
#include <vector>
#include <string>
int main(int argc, char** argv)
{
if (argc < 2) return 0;
std::vector<std::string> values;
boost::regex expression(
// proto host port
"^(\?:([^:/\?#]+)://)\?(\\w+[^/\?#:]*)(\?::(\\d+))\?"
// path file parameters
"(/\?(\?:[^\?#/]*/)*)\?([^\?#]*)\?(\\\?(.*))\?"
);
std::string src(argv[1]);
if (boost::regex_split(std::back_inserter(values), src, expression))
{
const char* names[] = {"Protocol", "Host", "Port", "Path", "File",
"Parameters", NULL};
for (int i = 0; names[i]; i++)
printf("%s: %s\n", names[i], values[i].c_str());
}
return 0;
}
What shall I add to my small program to parse URL into IP?
Remember that there may be multiple IP addresses for any one hostname, boost gives you an iterator that will go through them.
The use is fairly straightforward, add this before return 0; of your program:
std::cout << "IP addresses: \n";
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(values[1], "");
for(boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query);
i != boost::asio::ip::tcp::resolver::iterator();
++i)
{
boost::asio::ip::tcp::endpoint end = *i;
std::cout << end.address() << ' ';
}
std::cout << '\n';
and don't forget #include <boost/asio.hpp>
test run:
~ $ g++ -g -Wall -Wextra -pedantic -Wconversion -ansi -o test test.cc -lboost_regex -lboost_system -lboost_thread
~ $ ./test http://www.google.com:7777
Protocol: http
Host: www.google.com
Port: 7777
Path:
File:
Parameters:
IP addresses:
74.125.226.179 74.125.226.176 74.125.226.178 74.125.226.177 74.125.226.180
PS: For reference, I called
TCP resolver's constructor
query's host/service constructor with a don't-care service value of ""
the exception-throwing form of resolve()
dereferenced the iterator to get a resolver entry
used resolver_entry's type conversion to endpoint
used the TCP endpoint's address() accessor
used operator<< to show the address: you could use to_string() instead, if needed.