Merge branch 'develop'

This commit is contained in:
Vladimir Prus
2018-06-27 22:32:29 +03:00
19 changed files with 1522 additions and 183 deletions

View File

@@ -114,21 +114,6 @@ jobs:
script:
- libs/$SELF/ci/cppcheck.sh
- os: linux
env:
- COMMENT=UBSAN
- B2_VARIANT=variant=debug
- TOOLSET=gcc-7
- CXXFLAGS="cxxflags=-fno-omit-frame-pointer cxxflags=-fsanitize=undefined cxxflags=-fno-sanitize-recover=undefined"
- LINKFLAGS="linkflags=-fsanitize=undefined linkflags=-fno-sanitize-recover=undefined"
- UBSAN_OPTIONS=print_stacktrace=1
addons:
apt:
packages:
- g++-7
sources:
- ubuntu-toolchain-r-test
- os: linux
env:
- COMMENT=CodeCov
@@ -154,17 +139,6 @@ jobs:
# - CXXSTD=03,11
#################### Jobs to run on pushes to master, develop ###################
# Coverity Scan
- os: linux
if: (branch IN (develop, master)) AND (type IN (cron, push))
env:
- COMMENT="Coverity Scan"
- TOOLSET=gcc
script:
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
- cd libs/$SELF
- ci/coverity.sh
notifications:
email:

View File

@@ -12,10 +12,10 @@ Distributed under the [Boost Software License, Version 1.0](http://www.boost.org
### Build Status
(in progress...)
|Branch | Travis | Appveyor | Coverity Scan | codecov.io | Deps | Docs | Tests |
|:-------------: | ------ | -------- | ------------- | ---------- | ---- | ---- | ----- |
|[`master`](https://github.com/boostorg/program_options/tree/master) | [![Build Status](https://travis-ci.org/boostorg/program_options.svg?branch=master)](https://travis-ci.org/boostorg/program_options) | [![Build status](https://ci.appveyor.com/api/projects/status/upf5c528fy09fudk?svg=true)](https://ci.appveyor.com/project/jeking3/date-time-1evbf) | [![Coverity Scan Build Status](https://scan.coverity.com/projects/14908/badge.svg)](https://scan.coverity.com/projects/boostorg-program_options) | [![codecov](https://codecov.io/gh/boostorg/program_options/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/program_options/branch/master) | [![Deps](https://img.shields.io/badge/deps-master-brightgreen.svg)](https://pdimov.github.io/boostdep-report/master/program_options.html) | [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](http://www.boost.org/doc/libs/master/doc/html/program_options.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-master-brightgreen.svg)](http://www.boost.org/development/tests/master/developer/program_options.html)
|[`develop`](https://github.com/boostorg/program_options/tree/develop) | [![Build Status](https://travis-ci.org/boostorg/program_options.svg?branch=develop)](https://travis-ci.org/boostorg/program_options) | [![Build status](https://ci.appveyor.com/api/projects/status/e0quisadwh1v7ok5/branch/develop?svg=true)](https://ci.appveyor.com/project/vprus/program-options/branch/develop) | [![Coverity Scan Build Status](https://scan.coverity.com/projects/14908/badge.svg)](https://scan.coverity.com/projects/boostorg-program_options) | [![codecov](https://codecov.io/gh/boostorg/program_options/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/program_options/branch/develop) | [![Deps](https://img.shields.io/badge/deps-develop-brightgreen.svg)](https://pdimov.github.io/boostdep-report/develop/program_options.html) | [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](http://www.boost.org/doc/libs/develop/doc/html/program_options.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-develop-brightgreen.svg)](http://www.boost.org/development/tests/develop/developer/program_options.html)
|Branch | Travis | Appveyor | codecov.io | Deps | Docs | Tests |
|:-------------: | ------ | -------- | ---------- | ---- | ---- | ----- |
|[`master`](https://github.com/boostorg/program_options/tree/master) | [![Build Status](https://travis-ci.org/boostorg/program_options.svg?branch=master)](https://travis-ci.org/boostorg/program_options) | [![Build status](https://ci.appveyor.com/api/projects/status/e0quisadwh1v7ok5/branch/master?svg=true)](https://ci.appveyor.com/project/vprus/program-options/branch/master) | [![codecov](https://codecov.io/gh/boostorg/program_options/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/program_options/branch/master) | [![Deps](https://img.shields.io/badge/deps-master-brightgreen.svg)](https://pdimov.github.io/boostdep-report/master/program_options.html) | [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](http://www.boost.org/doc/libs/master/doc/html/program_options.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-master-brightgreen.svg)](http://www.boost.org/development/tests/master/developer/program_options.html)
|[`develop`](https://github.com/boostorg/program_options/tree/develop) | [![Build Status](https://travis-ci.org/boostorg/program_options.svg?branch=develop)](https://travis-ci.org/boostorg/program_options) | [![Build status](https://ci.appveyor.com/api/projects/status/e0quisadwh1v7ok5/branch/develop?svg=true)](https://ci.appveyor.com/project/vprus/program-options/branch/develop) | [![codecov](https://codecov.io/gh/boostorg/program_options/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/program_options/branch/develop) | [![Deps](https://img.shields.io/badge/deps-develop-brightgreen.svg)](https://pdimov.github.io/boostdep-report/develop/program_options.html) | [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](http://www.boost.org/doc/libs/develop/doc/html/program_options.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-develop-brightgreen.svg)](http://www.boost.org/development/tests/develop/developer/program_options.html)
### Directories
@@ -34,4 +34,4 @@ Distributed under the [Boost Software License, Version 1.0](http://www.boost.org
* [Ask questions](http://stackoverflow.com/questions/ask?tags=c%2B%2B,boost,boost-program_options): Be sure to read the documentation first to see if it answers your question.
* [Report bugs](https://github.com/boostorg/program_options/issues): Be sure to mention Boost version, platform and compiler you're using. A small compilable code sample to reproduce the problem is always good as well.
* [Submit Pull Requests](https://github.com/boostorg/program_options/pulls) against the **develop** branch. Note that by submitting patches you agree to license your modifications under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt). Be sure to include tests proving your changes work properly.
* Discussions about the library are held on the [Boost developers mailing list](http://www.boost.org/community/groups.html#main). Be sure to read the [discussion policy](http://www.boost.org/community/policy.html) before posting and add the `[date_time]` tag at the beginning of the subject line.
* Discussions about the library are held on the [Boost developers mailing list](http://www.boost.org/community/groups.html#main). Be sure to read the [discussion policy](http://www.boost.org/community/policy.html) before posting and add the `[program_options]` tag at the beginning of the subject line.

View File

@@ -512,7 +512,13 @@ visual_bell=yes
<screen>
gui.accessibility.visual_bell=yes
</screen>
<para>When the option "gui.accessibility.visual_bell" has been added to the options</para>
<programlisting>
options_description desc;
desc.add_options()
("gui.accessibility.visual_bell", value&lt;string&gt;(), "flash screen for bell")
;
</programlisting>
</section>
<section>
@@ -559,12 +565,49 @@ gui.accessibility.visual_bell=yes
function, any function taking a <code>std::string</code> and returning
<code>std::string</code>. That function will be called for each
environment variable and should return either the name of the option, or
empty string if the variable should be ignored.
empty string if the variable should be ignored. An example showing this
method can be found in "example/env_options.cpp".
</para>
</section>
</section>
<section>
<title>Types</title>
<para>Everything that is passed in on the command line, as an environmental
variable, or in a config file is a string. For values that need to be used
as a non-string type, the value in the variables_map will attempt to
convert it to the correct type.</para>
<para>Integers and floating point values are converted using Boost's
lexical_cast. It will accept integer values such as "41" or "-42". It will
accept floating point numbers such as "51.1", "-52.1", "53.1234567890" (as
a double), "54", "55.", ".56", "57.1e5", "58.1E5", ".591e5", "60.1e-5",
"-61.1e5", "-62.1e-5", etc. Unfortunately, hex, octal, and binary
representations that are available in C++ literals are not supported by
lexical_cast, and thus will not work with program_options.</para>
<para>Booleans a special in that there are multiple ways to come at them.
Similar to another value type, it can be specified as <code>("my-option",
value&lt;bool&gt;())</code>, and then set as:</para>
<screen>
example --my-option=true
</screen>
<para>However, more typical is that boolean values are set by the simple
presence of a switch. This is enabled by &bool_switch; as in <code>
("other-option", bool_switch())</code>. This will cause the value to
default to false and it will become true if the switch is found:</para>
<screen>
example --other-switch
</screen>
<para>When a boolean does take a parameter, there are several options.
Those that evaluate to true in C++ are: "true", "yes", "on", "1". Those
that evaluate to false in C++ are: "false", "no", "off", "0". In addition,
when reading from a config file, the option name with an equal sign and no
value after it will also evaluate to true.</para>
</section>
<section>
<title>Annotated List of Symbols</title>
@@ -649,4 +692,4 @@ gui.accessibility.visual_bell=yes
sgml-parent-document: ("program_options.xml" "section")
sgml-set-face: t
End:
-->
-->

View File

@@ -44,3 +44,5 @@
<!ENTITY basic_option
"<classname alt='boost::program_options::basic_option'>basic_option</classname>">
<!ENTITY bool_switch
"<functionname alt='boost::program_options::bool_switch'>bool_switch</functionname>">

View File

@@ -208,9 +208,9 @@ Allowed options:
--input-file arg : input file
$ <userinput>bin/gcc/debug/options_description</userinput>
Optimization level is 10
$ <userinput>bin/gcc/debug/options_description --optimization 4 -I foo a.cpp</userinput>
Include paths are: foo
Input files are: a.cpp
$ <userinput>bin/gcc/debug/options_description --optimization 4 -I foo -I another/path --include-path third/include/path a.cpp b.cpp</userinput>
Include paths are: foo another/path third/include/path
Input files are: a.cpp b.cpp
Optimization level is 4
</screen>
</para>
@@ -350,4 +350,4 @@ Optimization level is 4
sgml-parent-document: ("program_options.xml" "section")
sgml-set-face: t
End:
-->
-->

View File

@@ -12,3 +12,10 @@ exe custom_syntax : custom_syntax.cpp ;
exe real : real.cpp ;
exe regex : regex.cpp /boost/regex//boost_regex ;
# The following examples use C++ features beyond C++03.
# It would be possible to make compilation of each conditional on specific config check,
# for now just disable the compilation.
#exe config_file_types : config_file_types.cpp ;
#exe env_options : env_options.cpp ;
#exe options_heirarchy : options_heirarchy.cpp ;

View File

@@ -0,0 +1,242 @@
// Copyright Thomas Kent 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
// This example shows a config file (in ini format) being parsed by the
// program_options library. It includes a numebr of different value types.
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <assert.h>
#include <iostream>
#include <sstream>
using namespace std;
const double FLOAT_SEPERATION = 0.00000000001;
bool check_float(double test, double expected)
{
double seperation = expected * (1 + FLOAT_SEPERATION) / expected;
if ((test < expected + seperation) && (test > expected - seperation))
{
return true;
}
return false;
}
stringstream make_file()
{
stringstream ss;
ss << "# This file checks parsing of various types of config values\n";
//FAILS: ss << "; a windows style comment\n";
ss << "global_string = global value\n";
ss << "unregistered_entry = unregistered value\n";
ss << "\n[strings]\n";
ss << "word = word\n";
ss << "phrase = this is a phrase\n";
ss << "quoted = \"quotes are in result\"\n";
ss << "\n[ints]\n";
ss << "positive = 41\n";
ss << "negative = -42\n";
//FAILS: Lexical cast doesn't support hex, oct, or bin
//ss << "hex = 0x43\n";
//ss << "oct = 044\n";
//ss << "bin = 0b101010\n";
ss << "\n[floats]\n";
ss << "positive = 51.1\n";
ss << "negative = -52.1\n";
ss << "double = 53.1234567890\n";
ss << "int = 54\n";
ss << "int_dot = 55.\n";
ss << "dot = .56\n";
ss << "exp_lower = 57.1e5\n";
ss << "exp_upper = 58.1E5\n";
ss << "exp_decimal = .591e5\n";
ss << "exp_negative = 60.1e-5\n";
ss << "exp_negative_val = -61.1e5\n";
ss << "exp_negative_negative_val = -62.1e-5\n";
ss << "\n[booleans]\n";
ss << "number_true = 1\n";
ss << "number_false = 0\n";
ss << "yn_true = yes\n";
ss << "yn_false = no\n";
ss << "tf_true = true\n";
ss << "tf_false = false\n";
ss << "onoff_true = on\n";
ss << "onoff_false = off\n";
ss << "present_equal_true = \n";
//FAILS: Must be an =
//ss << "present_no_equal_true\n";
ss.seekp(ios_base::beg);
return ss;
}
po::options_description set_options()
{
po::options_description opts;
opts.add_options()
("global_string", po::value<string>())
("strings.word", po::value<string>())
("strings.phrase", po::value<string>())
("strings.quoted", po::value<string>())
("ints.positive", po::value<int>())
("ints.negative", po::value<int>())
("ints.hex", po::value<int>())
("ints.oct", po::value<int>())
("ints.bin", po::value<int>())
("floats.positive", po::value<float>())
("floats.negative", po::value<float>())
("floats.double", po::value<double>())
("floats.int", po::value<float>())
("floats.int_dot", po::value<float>())
("floats.dot", po::value<float>())
("floats.exp_lower", po::value<float>())
("floats.exp_upper", po::value<float>())
("floats.exp_decimal", po::value<float>())
("floats.exp_negative", po::value<float>())
("floats.exp_negative_val", po::value<float>())
("floats.exp_negative_negative_val", po::value<float>())
// Load booleans as value<bool>, so they will require a --option=value on the command line
//("booleans.number_true", po::value<bool>())
//("booleans.number_false", po::value<bool>())
//("booleans.yn_true", po::value<bool>())
//("booleans.yn_false", po::value<bool>())
//("booleans.tf_true", po::value<bool>())
//("booleans.tf_false", po::value<bool>())
//("booleans.onoff_true", po::value<bool>())
//("booleans.onoff_false", po::value<bool>())
//("booleans.present_equal_true", po::value<bool>())
//("booleans.present_no_equal_true", po::value<bool>())
// Load booleans as bool_switch, so that a --option will set it true on the command line
// The difference between these two types does not show up when parsing a file
("booleans.number_true", po::bool_switch())
("booleans.number_false", po::bool_switch())
("booleans.yn_true", po::bool_switch())
("booleans.yn_false", po::bool_switch())
("booleans.tf_true", po::bool_switch())
("booleans.tf_false", po::bool_switch())
("booleans.onoff_true", po::bool_switch())
("booleans.onoff_false", po::bool_switch())
("booleans.present_equal_true", po::bool_switch())
("booleans.present_no_equal_true", po::bool_switch())
;
return opts;
}
vector<string> parse_file(stringstream &file, po::options_description &opts, po::variables_map &vm)
{
const bool ALLOW_UNREGISTERED = true;
cout << file.str() << endl;
po::parsed_options parsed = parse_config_file(file, opts, ALLOW_UNREGISTERED);
store(parsed, vm);
vector<string> unregistered = po::collect_unrecognized(parsed.options, po::exclude_positional);
notify(vm);
return unregistered;
}
void check_results(po::variables_map &vm, vector<string> unregistered)
{
// Check that we got the correct values back
string expected_global_string = "global value";
string expected_unreg_option = "unregistered_entry";
string expected_unreg_value = "unregistered value";
string expected_strings_word = "word";
string expected_strings_phrase = "this is a phrase";
string expected_strings_quoted = "\"quotes are in result\"";
int expected_int_postitive = 41;
int expected_int_negative = -42;
int expected_int_hex = 0x43;
int expected_int_oct = 044;
int expected_int_bin = 0b101010;
float expected_float_positive = 51.1f;
float expected_float_negative = -52.1f;
double expected_float_double = 53.1234567890;
float expected_float_int = 54.0f;
float expected_float_int_dot = 55.0f;
float expected_float_dot = .56f;
float expected_float_exp_lower = 57.1e5f;
float expected_float_exp_upper = 58.1E5f;
float expected_float_exp_decimal = .591e5f;
float expected_float_exp_negative = 60.1e-5f;
float expected_float_exp_negative_val = -61.1e5f;
float expected_float_exp_negative_negative_val = -62.1e-5f;
bool expected_number_true = true;
bool expected_number_false = false;
bool expected_yn_true = true;
bool expected_yn_false = false;
bool expected_tf_true = true;
bool expected_tf_false = false;
bool expected_onoff_true = true;
bool expected_onoff_false = false;
bool expected_present_equal_true = true;
bool expected_present_no_equal_true = true;
assert(vm["global_string"].as<string>() == expected_global_string);
assert(unregistered[0] == expected_unreg_option);
assert(unregistered[1] == expected_unreg_value);
assert(vm["strings.word"].as<string>() == expected_strings_word);
assert(vm["strings.phrase"].as<string>() == expected_strings_phrase);
assert(vm["strings.quoted"].as<string>() == expected_strings_quoted);
assert(vm["ints.positive"].as<int>() == expected_int_postitive);
assert(vm["ints.negative"].as<int>() == expected_int_negative);
//assert(vm["ints.hex"].as<int>() == expected_int_hex);
//assert(vm["ints.oct"].as<int>() == expected_int_oct);
//assert(vm["ints.bin"].as<int>() == expected_int_bin);
assert(check_float(vm["floats.positive"].as<float>(), expected_float_positive));
assert(check_float(vm["floats.negative"].as<float>(), expected_float_negative));
assert(check_float(vm["floats.double"].as<double>(), expected_float_double));
assert(check_float(vm["floats.int"].as<float>(), expected_float_int));
assert(check_float(vm["floats.int_dot"].as<float>(), expected_float_int_dot));
assert(check_float(vm["floats.dot"].as<float>(), expected_float_dot));
assert(check_float(vm["floats.exp_lower"].as<float>(), expected_float_exp_lower));
assert(check_float(vm["floats.exp_upper"].as<float>(), expected_float_exp_upper));
assert(check_float(vm["floats.exp_decimal"].as<float>(), expected_float_exp_decimal));
assert(check_float(vm["floats.exp_negative"].as<float>(), expected_float_exp_negative));
assert(check_float(vm["floats.exp_negative_val"].as<float>(), expected_float_exp_negative_val));
assert(check_float(vm["floats.exp_negative_negative_val"].as<float>(), expected_float_exp_negative_negative_val));
assert(vm["booleans.number_true"].as<bool>() == expected_number_true);
assert(vm["booleans.number_false"].as<bool>() == expected_number_false);
assert(vm["booleans.yn_true"].as<bool>() == expected_yn_true);
assert(vm["booleans.yn_false"].as<bool>() == expected_yn_false);
assert(vm["booleans.tf_true"].as<bool>() == expected_tf_true);
assert(vm["booleans.tf_false"].as<bool>() == expected_tf_false);
assert(vm["booleans.onoff_true"].as<bool>() == expected_onoff_true);
assert(vm["booleans.onoff_false"].as<bool>() == expected_onoff_false);
assert(vm["booleans.present_equal_true"].as<bool>() == expected_present_equal_true);
//assert(vm["booleans.present_no_equal_true"].as<bool>() == expected_present_no_equal_true);
}
int main(int ac, char* av[])
{
auto file = make_file();
auto opts = set_options();
po::variables_map vars;
auto unregistered = parse_file(file, opts, vars);
check_results(vars, unregistered);
return 0;
}

47
example/env_options.cpp Normal file
View File

@@ -0,0 +1,47 @@
// Copyright Thomas Kent 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <string>
#include <iostream>
std::string mapper(std::string env_var)
{
// ensure the env_var is all caps
std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper);
if (env_var == "PATH") return "path";
if (env_var == "EXAMPLE_VERBOSE") return "verbosity";
return "";
}
void get_env_options()
{
po::options_description config("Configuration");
config.add_options()
("path", "the execution path")
("verbosity", po::value<std::string>()->default_value("INFO"), "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL")
;
po::variables_map vm;
store(po::parse_environment(config, boost::function1<std::string, std::string>(mapper)), vm);
notify(vm);
if (vm.count("path"))
{
std::cout << "First 75 chars of the system path: \n";
std::cout << vm["path"].as<std::string>().substr(0, 75) << std::endl;
}
std::cout << "Verbosity: " << vm["verbosity"].as<std::string>() << std::endl;
}
int main(int ac, char* av[])
{
get_env_options();
return 0;
}

View File

@@ -0,0 +1,690 @@
// Copyright Thomas Kent 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is an example of a program that uses multiple facets of the boost
// program_options library. It will go through different types of config
// options in a heirarchal manner:
// 1. Default options are set.
// 2. Command line options are set (they override defaults).
// 3. Environment options are set (they override defaults but not command
// line options).
// 4. Config files specified on the command line are read, if present, in
// the order specified. (these override defaults but not options from the
// other steps).
// 5. Default config file (default.cfg) is read, if present (it overrides
// defaults but not options from the other steps).
//
// See the bottom of this file for full usage examples
//
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <string>
#include <iostream>
#include <map>
#include <stdexcept>
#include <fstream>
const std::string version("1.0");
// Used to exit the program if the help/version option is set
class OptionsExitsProgram : public std::exception
{};
struct GuiOpts
{
unsigned int width;
unsigned int height;
};
struct NetworkOpts
{
std::string address;
unsigned short port;
};
class OptionsHeirarchy
{
public:
// The constructor sets up all the various options that will be parsed
OptionsHeirarchy()
{
SetOptions();
}
// Parse options runs through the heirarchy doing all the parsing
void ParseOptions(int argc, char* argv[])
{
ParseCommandLine(argc, argv);
CheckForHelp();
CheckForVersion();
ParseEnvironment();
ParseConfigFiles();
ParseDefaultConfigFile();
}
// Below is the interface to access the data, once ParseOptions has been run
std::string Path()
{
return results["path"].as<std::string>();
}
std::string Verbosity()
{
return results["verbosity"].as<std::string>();
}
std::vector<std::string> IncludePath()
{
if (results.count("include-path"))
{
return results["include-path"].as<std::vector<std::string>>();
}
return std::vector<std::string>();
}
std::string MasterFile()
{
if (results.count("master-file"))
{
return results["master-file"].as<std::string>();
}
return "";
}
std::vector<std::string> Files()
{
if (results.count("file"))
{
return results["file"].as<std::vector<std::string>>();
}
return std::vector<std::string>();
}
bool GUI()
{
if (results["run-gui"].as<bool>())
{
return true;
}
return false;
}
GuiOpts GuiValues()
{
GuiOpts opts;
opts.width = results["gui.width"].as<unsigned int>();
opts.height = results["gui.height"].as<unsigned int>();
return opts;
}
NetworkOpts NetworkValues()
{
NetworkOpts opts;
opts.address = results["network.ip"].as<std::string>();
opts.port = results["network.port"].as<unsigned short>();
return opts;
}
private:
void SetOptions()
{
SetCommandLineOptions();
SetCommonOptions();
SetConfigOnlyOptions();
SetEnvMapping();
}
void SetCommandLineOptions()
{
command_line_options.add_options()
("help,h", "display this help message")
("version,v", "show program version")
("config,c", po::value<std::vector<std::string>>(),
"config files to parse (always parses default.cfg)")
;
hidden_command_line_options.add_options()
("master-file", po::value<std::string>())
("file", po::value<std::vector<std::string>>())
;
positional_options.add("master-file", 1);
positional_options.add("file", -1);
}
void SetCommonOptions()
{
common_options.add_options()
("path", po::value<std::string>()->default_value(""),
"the execution path to use (imports from environment if not specified)")
("verbosity", po::value<std::string>()->default_value("INFO"),
"set verbosity: DEBUG, INFO, WARN, ERROR, FATAL")
("include-path,I", po::value<std::vector<std::string>>()->composing(),
"paths to search for include files")
("run-gui", po::bool_switch(), "start the GUI")
;
}
void SetConfigOnlyOptions()
{
config_only_options.add_options()
("log-dir", po::value<std::string>()->default_value("log"))
("gui.height", po::value<unsigned int>()->default_value(100))
("gui.width", po::value<unsigned int>()->default_value(100))
("network.ip", po::value<std::string>()->default_value("127.0.0.1"))
("network.port", po::value<unsigned short>()->default_value(12345))
;
// Run a parser here (with no command line options) to add these defaults into
// results, this way they will be enabled even if no config files are parsed.
store(po::command_line_parser(0, 0).options(config_only_options).run(), results);
notify(results);
}
void SetEnvMapping()
{
env_to_option["PATH"] = "path";
env_to_option["EXAMPLE_VERBOSE"] = "verbosity";
}
void ParseCommandLine(int argc, char* argv[])
{
po::options_description cmd_opts;
cmd_opts.add(command_line_options).add(hidden_command_line_options).add(common_options);
store(po::command_line_parser(argc, argv).
options(cmd_opts).positional(positional_options).run(), results);
notify(results);
}
void CheckForHelp()
{
if (results.count("help"))
{
PrintHelp();
}
}
void PrintHelp()
{
std::cout << "Program Options Example" << std::endl;
std::cout << "Usage: example [OPTION]... MASTER-FILE [FILE]...\n";
std::cout << " or example [OPTION] --run-gui\n";
po::options_description help_opts;
help_opts.add(command_line_options).add(common_options);
std::cout << help_opts << std::endl;
throw OptionsExitsProgram();
}
void CheckForVersion()
{
if (results.count("version"))
{
PrintVersion();
}
}
void PrintVersion()
{
std::cout << "Program Options Example " << version << std::endl;
throw OptionsExitsProgram();
}
void ParseEnvironment()
{
store(po::parse_environment(common_options,
// The next two lines are the crazy syntax to use EnvironmentMapper as
// the lookup function for env->config name conversions
boost::function1<std::string, std::string>(
std::bind1st(std::mem_fun(&OptionsHeirarchy::EnvironmentMapper), this))),
results);
notify(results);
}
std::string EnvironmentMapper(std::string env_var)
{
// ensure the env_var is all caps
std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper);
auto entry = env_to_option.find(env_var);
if (entry != env_to_option.end())
{
return entry->second;
}
return "";
}
void ParseConfigFiles()
{
if (results.count("config"))
{
auto files = results["config"].as<std::vector<std::string>>();
for (auto file = files.begin(); file != files.end(); file++)
{
LoadAConfigFile(*file);
}
}
}
void LoadAConfigFile(std::string filename)
{
bool ALLOW_UNREGISTERED = true;
po::options_description config_opts;
config_opts.add(config_only_options).add(common_options);
std::ifstream cfg_file(filename.c_str());
if (cfg_file)
{
store(parse_config_file(cfg_file, config_opts, ALLOW_UNREGISTERED), results);
notify(results);
}
}
void ParseDefaultConfigFile()
{
LoadAConfigFile("default.cfg");
}
std::map<std::string, std::string> env_to_option;
po::options_description config_only_options;
po::options_description common_options;
po::options_description command_line_options;
po::options_description hidden_command_line_options;
po::positional_options_description positional_options;
po::variables_map results;
};
void get_env_options()
{
}
void PrintOptions(OptionsHeirarchy options)
{
auto path = options.Path();
if (path.length())
{
std::cout << "First 75 chars of the system path: \n";
std::cout << options.Path().substr(0, 75) << std::endl;
}
std::cout << "Verbosity: " << options.Verbosity() << std::endl;
std::cout << "Include Path:\n";
auto includePaths = options.IncludePath();
for (auto path = includePaths.begin(); path != includePaths.end(); path++)
{
std::cout << " " << *path << std::endl;
}
std::cout << "Master-File: " << options.MasterFile() << std::endl;
std::cout << "Additional Files:\n";
auto files = options.Files();
for (auto file = files.begin(); file != files.end(); file++)
{
std::cout << " " << *file << std::endl;
}
std::cout << "GUI Enabled: " << std::boolalpha << options.GUI() << std::endl;
if (options.GUI())
{
auto gui_values = options.GuiValues();
std::cout << "GUI Height: " << gui_values.height << std::endl;
std::cout << "GUI Width: " << gui_values.width << std::endl;
}
auto network_values = options.NetworkValues();
std::cout << "Network Address: " << network_values.address << std::endl;
std::cout << "Network Port: " << network_values.port << std::endl;
}
int main(int ac, char* av[])
{
OptionsHeirarchy options;
try
{
options.ParseOptions(ac, av);
PrintOptions(options);
}
catch (OptionsExitsProgram){}
return 0;
}
/*
Full Usage Examples
===================
These were run on windows, so some results may show that environment, but
results should be similar on POSIX platforms.
Help
----
To see the help screen, with the available options just pass the --help (or -h)
parameter. The program will then exit.
> example.exe --help
Program Options Example
Usage: example [OPTION]... MASTER-FILE [FILE]...
or example [OPTION] --run-gui
-h [ --help ] display this help message
-v [ --version ] show program version
-c [ --config ] arg config files to parse (always parses default.cfg)
--path arg the execution path to use (imports from
environment if not specified)
--verbosity arg (=INFO) set verbosity: DEBUG, INFO, WARN, ERROR, FATAL
-I [ --include-path ] arg paths to search for include files
--run-gui start the GUI
Version is similar to help (--version or -v).
> example.exe -v
Program Options Example 1.0
Basics
------
Running without any options will get the default values (path is set from the
environment):
> example.exe
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
We can easily override that environment path with a simple option:
> example.exe --path a/b/c;d/e/f
First 75 chars of the system path:
a/b/c;d/e/f
Verbosity: INFO
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
You can use a space or equals sign after long options, also backslashes are
treated literally on windows, on POSIX they need to be escaped.
> example.exe --path=a\b\c\;d\e\\f
First 75 chars of the system path:
a\b\c\;d\e\\f
Verbosity: INFO
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
For short options you can use a space:
> example.exe -I path/to/includes
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
path\to\includes
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
Or you can put the option immediately after it:
> example.exe -Ipath/to/includes
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
path\to\includes
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
The include path (--include-path or -I) option allows for multiple paths to be
specified (both on the command line and in config files) and combined into a
vector for use by the program.
> example.exe --include-path=a/b/c --include-path d/e/f -I g/h/i -Ij/k/l
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
a/b/c
d/e/f
g/h/i
j/k/l
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
There are also the option of flags that do not take parameters and just set a
boolean value to true. In this case, running the gui also causes default values
for the gui to be output to the screen.
> example.exe --run-gui
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
Master-File:
Additional Files:
GUI Enabled: true
GUI Height: 100
GUI Width: 100
Network Address: 127.0.0.1
Network Port: 12345
There are also "positional" options at the end of the command line. The first
one specifies the "master" file the others are additional files.
> example.exe --path=a-path -I an-include master.cpp additional1.cpp additional2.cpp
First 75 chars of the system path:
a-path
Verbosity: INFO
Include Path:
an-include
Master-File: master.cpp
Additional Files:
additional1.cpp
additional2.cpp
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
Environment Variables
---------------------
In addition to the PATH environment variable, it also knows how to read the
EXAMPLE_VERBOSE environmental variable and use that to set the verbosity
option/
> set EXAMPLE_VERBOSE=DEBUG
> example.exe
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: DEBUG
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
However, if the --verboseity flag is also set, it will override the env
variable. This illustrates an important example, the way program_options works,
is that a parser will not override a value that has previously been set by
another parser. Thus the env parser doesn't override the command line parser.
(We will see this again in config files.) Default values are seperate from this
heirarcy, they only apply if no parser has set the value and it is being read.
> set EXAMPLE_VERBOSE=DEBUG
> example.exe --verbosity=WARN
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: WARN
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
(You can unset an environmental variable with an empty set command)
> set EXAMPLE_VERBOSE=
> example.exe
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
Config Files
------------
Config files generally follow the [INI file format]
(https://en.wikipedia.org/wiki/INI_file) with a few exceptions.
Values can be simply added tp options with an equal sign. Here are two include
paths added via the default config file (default.cfg), you can have optional
spaces around the equal sign.
# You can use comments in a config file
include-path=first/default/path
include-path = second/default/path
Results in
> example.exe
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
first/default/path
second/default/path
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 127.0.0.1
Network Port: 12345
Values can also be in sections of the config file. Again, editing default.cfg
include-path=first/default/path
include-path = second/default/path
[network]
ip=1.2.3.4
port=3000
Results in
> example.exe
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: INFO
Include Path:
first/default/path
second/default/path
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 1.2.3.4
Network Port: 3000
This example is also setup to allow multiple config files to be specified on
the command line, which are checked before the default.cfg file is read (but
after the environment and command line parsing). Thus we can set the first.cfg
file to contain the following:
verbosity=ERROR
[network]
ip = 5.6.7.8
Results in:
> example.exe --config first.cfg
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: ERROR
Include Path:
first/default/path
second/default/path
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 5.6.7.8
Network Port: 3000
But since the config files are read after the command line, setting the
verbosity there causes the value in the file to be ignored.
> example.exe --config first.cfg --verbosity=WARN
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: WARN
Include Path:
first/default/path
second/default/path
Master-File:
Additional Files:
GUI Enabled: false
Network Address: 5.6.7.8
Network Port: 3000
The config files are parsed in the order they are received on the command line.
So adding the second.cfg file:
verbosity=FATAL
run-gui=true
[gui]
height=720
width=1280
Results in a combination of all three config files:
> example.exe --config first.cfg --config second.cfg
First 75 chars of the system path:
C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
Verbosity: ERROR
Include Path:
first/default/path
second/default/path
Master-File:
Additional Files:
GUI Enabled: true
GUI Height: 720
GUI Width: 1280
Network Address: 5.6.7.8
Network Port: 3000
Incidently the boolean run-gui option could have been set a number of ways
that all result in the C++ boolean value of true:
run-gui=true
run-gui=on
run-gui=1
run-gui=yes
run-gui=
Since run-gui is an option that was set with the bool_switch type, which
forces its use on the command line without a parameter (i.e. --run-gui instead
of --run-gui=true) it can't be given a "false" option, bool_switch values can
only be turned true. If instead we had a value ("my-switch", po::value<bool>())
that could be set at the command line --my-switch=true or --my-switch=false, or
any of the other types of boolean keywords true: true, on, 1, yes;
false: false, off, 0, no. In a config file this could look like:
my-switch=true
my-switch=on
my-switch=1
my-switch=yes
my-switch=
my-switch=false
my-switch=off
my-switch=0
my-switch=no
*/

View File

@@ -375,12 +375,15 @@ namespace boost { namespace program_options {
const std::string& option_name = "",
const std::string& original_token = "",
int option_style = 0):
error_with_option_name(get_template(kind), option_name, original_token, option_style)
error_with_option_name(get_template(kind), option_name, original_token, option_style),
m_kind(kind)
{
}
~validation_error() throw() {}
kind_t kind() const { return m_kind; }
protected:
/** Used to convert kind_t to a related error text */
std::string get_template(kind_t kind);

View File

@@ -22,6 +22,7 @@
#include <set>
#include <map>
#include <stdexcept>
#include <utility>
#include <iosfwd>
@@ -106,14 +107,16 @@ namespace program_options {
/** Returns the canonical name for the option description to enable the user to
recognised a matching option.
1) For short options ('-', '/'), returns the short name prefixed.
2) For long options ('--' / '-') returns the long name prefixed
3) All other cases, returns the long name (if present) or the short name,
unprefixed.
2) For long options ('--' / '-') returns the first long name prefixed
3) All other cases, returns the first long name (if present) or the short
name, unprefixed.
*/
std::string canonical_display_name(int canonical_option_style = 0) const;
const std::string& long_name() const;
const std::pair<const std::string*, std::size_t> long_names() const;
/// Explanation of this option
const std::string& description() const;
@@ -129,9 +132,24 @@ namespace program_options {
private:
option_description& set_name(const char* name);
option_description& set_names(const char* name);
/**
* a one-character "switch" name - with its prefix,
* so that this is either empty or has length 2 (e.g. "-c"
*/
std::string m_short_name;
/**
* one or more names by which this option may be specified
* on a command-line or in a config file, which are not
* a single-letter switch. The names here are _without_
* any prefix.
*/
std::vector<std::string> m_long_names;
std::string m_description;
std::string m_short_name, m_long_name, m_description;
// shared_ptr is needed to simplify memory management in
// copy ctor and destructor.
shared_ptr<const value_semantic> m_value_semantic;

View File

@@ -49,21 +49,21 @@ namespace boost { namespace program_options {
}
option_description::
option_description(const char* name,
option_description(const char* names,
const value_semantic* s)
: m_value_semantic(s)
{
this->set_name(name);
this->set_names(names);
}
option_description::
option_description(const char* name,
option_description(const char* names,
const value_semantic* s,
const char* description)
: m_description(description), m_value_semantic(s)
{
this->set_name(name);
this->set_names(names);
}
option_description::~option_description()
@@ -77,38 +77,42 @@ namespace boost { namespace program_options {
bool short_ignore_case) const
{
match_result result = no_match;
std::string local_option = (long_ignore_case ? tolower_(option) : option);
std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name));
if (!local_long_name.empty()) {
std::string local_option = (long_ignore_case ? tolower_(option) : option);
for(std::vector<std::string>::const_iterator it(m_long_names.begin()); it != m_long_names.end(); it++)
{
std::string local_long_name((long_ignore_case ? tolower_(*it) : *it));
if (*local_long_name.rbegin() == '*')
{
// The name ends with '*'. Any specified name with the given
// prefix is OK.
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
== 0)
result = approximate_match;
}
if (!local_long_name.empty()) {
if (local_long_name == local_option)
{
result = full_match;
}
else if (approx)
{
if (local_long_name.find(local_option) == 0)
if ((result == no_match) && (*local_long_name.rbegin() == '*'))
{
result = approximate_match;
// The name ends with '*'. Any specified name with the given
// prefix is OK.
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
== 0)
result = approximate_match;
}
if (local_long_name == local_option)
{
result = full_match;
break;
}
else if (approx)
{
if (local_long_name.find(local_option) == 0)
{
result = approximate_match;
}
}
}
}
if (result != full_match)
{
std::string local_option(short_ignore_case ? tolower_(option) : option);
std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);
if (local_short_name == local_option)
@@ -122,9 +126,12 @@ namespace boost { namespace program_options {
const std::string&
option_description::key(const std::string& option) const
{
if (!m_long_name.empty())
if (m_long_name.find('*') != string::npos)
{
// We make the arbitrary choise of using the first long
// name as the key, regardless of anything else
if (!m_long_names.empty()) {
const std::string& first_long_name = *m_long_names.begin();
if (first_long_name.find('*') != string::npos)
// The '*' character means we're long_name
// matches only part of the input. So, returning
// long name will remove some of the information,
@@ -132,7 +139,8 @@ namespace boost { namespace program_options {
// in the source.
return option;
else
return m_long_name;
return first_long_name;
}
else
return m_short_name;
}
@@ -140,12 +148,13 @@ namespace boost { namespace program_options {
std::string
option_description::canonical_display_name(int prefix_style) const
{
if (!m_long_name.empty())
// We prefer the first long name over any others
if (!m_long_names.empty())
{
if (prefix_style == command_line_style::allow_long)
return "--" + m_long_name;
return "--" + *m_long_names.begin();
if (prefix_style == command_line_style::allow_long_disguise)
return "-" + m_long_name;
return "-" + *m_long_names.begin();
}
// sanity check: m_short_name[0] should be '-' or '/'
if (m_short_name.length() == 2)
@@ -155,8 +164,8 @@ namespace boost { namespace program_options {
if (prefix_style == command_line_style::allow_dash_for_short)
return string("-") + m_short_name[1];
}
if (!m_long_name.empty())
return m_long_name;
if (!m_long_names.empty())
return *m_long_names.begin();
else
return m_short_name;
}
@@ -165,21 +174,46 @@ namespace boost { namespace program_options {
const std::string&
option_description::long_name() const
{
return m_long_name;
static std::string empty_string("");
return m_long_names.empty() ? empty_string : *m_long_names.begin();
}
const std::pair<const std::string*, std::size_t>
option_description::long_names() const
{
return (m_long_names.empty())
? std::pair<const std::string*, size_t>( NULL, 0 )
: std::pair<const std::string*, size_t>( &(*m_long_names.begin()), m_long_names.size());
}
option_description&
option_description::set_name(const char* _name)
option_description::set_names(const char* _names)
{
std::string name(_name);
string::size_type n = name.find(',');
if (n != string::npos) {
assert(n == name.size()-2);
m_long_name = name.substr(0, n);
m_short_name = '-' + name.substr(n+1,1);
} else {
m_long_name = name;
m_long_names.clear();
std::istringstream iss(_names);
std::string name;
while(std::getline(iss, name, ',')) {
m_long_names.push_back(name);
}
assert(!m_long_names.empty() && "No option names were specified");
bool try_interpreting_last_name_as_a_switch = m_long_names.size() > 1;
if (try_interpreting_last_name_as_a_switch) {
const std::string& last_name = *m_long_names.rbegin();
if (last_name.length() == 1) {
m_short_name = '-' + last_name;
m_long_names.pop_back();
// The following caters to the (valid) input of ",c" for some
// character c, where the caller only wants this option to have
// a short name.
if (m_long_names.size() == 1 && (*m_long_names.begin()).empty()) {
m_long_names.clear();
}
}
}
// We could theoretically also ensure no remaining long names
// are empty, or that none of them have length 1
return *this;
}
@@ -200,12 +234,12 @@ namespace boost { namespace program_options {
{
if (!m_short_name.empty())
{
return m_long_name.empty()
return m_long_names.empty()
? m_short_name
: string(m_short_name).append(" [ --").
append(m_long_name).append(" ]");
append(*m_long_names.begin()).append(" ]");
}
return string("--").append(m_long_name);
return string("--").append(*m_long_names.begin());
}
std::string

View File

@@ -463,11 +463,13 @@ void test_additional_parser()
desc.add_options()
("response-file", value<string>(), "response file")
("foo", value<int>(), "foo")
("bar,baz", value<int>(), "bar")
;
vector<string> input;
input.push_back("@config");
input.push_back("--foo=1");
input.push_back("--baz=11");
cmdline cmd(input);
cmd.set_options_description(desc);
@@ -475,11 +477,13 @@ void test_additional_parser()
vector<option> result = cmd.run();
BOOST_REQUIRE(result.size() == 2);
BOOST_REQUIRE(result.size() == 3);
BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
BOOST_CHECK_EQUAL(result[0].value[0], "config");
BOOST_CHECK_EQUAL(result[1].string_key, "foo");
BOOST_CHECK_EQUAL(result[1].value[0], "1");
BOOST_CHECK_EQUAL(result[2].string_key, "bar");
BOOST_CHECK_EQUAL(result[2].value[0], "11");
// Test that invalid options returned by additional style
// parser are detected.

View File

@@ -5,5 +5,5 @@ b = true
[m1]
v1 = 1
v2 = 2
v3 = 3

View File

@@ -45,6 +45,56 @@ void test_ambiguous()
}
void test_ambiguous_long()
{
options_description desc;
desc.add_options()
("cfgfile,c", value<string>()->multitoken(), "the config file")
("output,c", value<string>(), "the output file")
("output,o", value<string>(), "the output file")
;
const char* cmdline[] = {"program", "--cfgfile", "file", "--output", "anotherfile"};
variables_map vm;
try {
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
const_cast<char**>(cmdline), desc), vm);
}
catch (ambiguous_option& e)
{
BOOST_CHECK_EQUAL(e.alternatives().size(), 2);
BOOST_CHECK_EQUAL(e.get_option_name(), "--output");
BOOST_CHECK_EQUAL(e.alternatives()[0], "output");
BOOST_CHECK_EQUAL(e.alternatives()[1], "output");
}
}
void test_ambiguous_multiple_long_names()
{
options_description desc;
desc.add_options()
("cfgfile,foo,c", value<string>()->multitoken(), "the config file")
("output,foo,o", value<string>(), "the output file")
;
const char* cmdline[] = {"program", "--foo", "file"};
variables_map vm;
try {
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
const_cast<char**>(cmdline), desc), vm);
}
catch (ambiguous_option& e)
{
BOOST_CHECK_EQUAL(e.alternatives().size(), 2);
BOOST_CHECK_EQUAL(e.get_option_name(), "--foo");
BOOST_CHECK_EQUAL(e.alternatives()[0], "cfgfile");
BOOST_CHECK_EQUAL(e.alternatives()[1], "output");
}
}
void test_unknown_option()
{
@@ -100,7 +150,6 @@ void test_multiple_values()
}
void test_multiple_occurrences()
{
options_description desc;
@@ -109,20 +158,67 @@ void test_multiple_occurrences()
;
const char* cmdline[] = {"program", "--cfgfile", "file", "-c", "anotherfile"};
variables_map vm;
try {
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
const_cast<char**>(cmdline), desc), vm);
notify(vm);
}
catch (multiple_occurrences& e)
{
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
BOOST_CHECK_EQUAL(string(e.what()), "option '--cfgfile' cannot be specified more than once");
}
}
void test_multiple_occurrences_with_different_names()
{
options_description desc;
desc.add_options()
("cfgfile,config-file,c", value<string>(), "the configfile")
;
const char* cmdline[] = {"program", "--config-file", "file", "--cfgfile", "anotherfile"};
variables_map vm;
try {
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
const_cast<char**>(cmdline), desc), vm);
notify(vm);
}
catch (multiple_occurrences& e)
{
BOOST_CHECK( (e.get_option_name() == "--cfgfile") || (e.get_option_name() == "--configfile"));
BOOST_CHECK(
(string(e.what()) == "option '--cfgfile' cannot be specified more than once") ||
(string(e.what()) == "option '--configfile' cannot be specified more than once")
);
}
}
void test_multiple_occurrences_with_non_key_names()
{
options_description desc;
desc.add_options()
("cfgfile,config-file,c", value<string>(), "the configfile")
;
const char* cmdline[] = {"program", "--config-file", "file", "-c", "anotherfile"};
variables_map vm;
try {
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
const_cast<char**>(cmdline), desc), vm);
notify(vm);
}
catch (multiple_occurrences& e)
{
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
BOOST_CHECK_EQUAL(string(e.what()), "option '--cfgfile' cannot be specified more than once");
}
}
void test_missing_value()
@@ -154,9 +250,13 @@ void test_missing_value()
int main(int /*ac*/, char** /*av*/)
{
test_ambiguous();
test_ambiguous_long();
test_ambiguous_multiple_long_names();
test_unknown_option();
test_multiple_values();
test_multiple_occurrences();
test_multiple_occurrences_with_different_names();
test_multiple_occurrences_with_non_key_names();
test_missing_value();
return 0;

View File

@@ -74,6 +74,61 @@ void test_approximation()
// BOOST_CHECK(*(++a.begin()) == "foo");
}
void test_approximation_with_multiname_options()
{
options_description desc;
desc.add_options()
("foo", new untyped_value())
("fee", new untyped_value())
("fe,baz", new untyped_value())
("chroots,all-chroots", new untyped_value())
("sessions,all-sessions", new untyped_value())
("everything,all", new untyped_value())
("qux,fo", new untyped_value())
;
BOOST_CHECK_EQUAL(desc.find("fo", true).long_name(), "qux");
BOOST_CHECK_EQUAL(desc.find("all", true).long_name(), "everything");
BOOST_CHECK_EQUAL(desc.find("all-ch", true).long_name(), "chroots");
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().second, 1u);
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().first[0], "foo");
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().second, 2u);
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().first[0], "fe");
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().second, 2u);
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[0], "fizbaz");
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
}
void test_long_names_for_option_description()
{
options_description desc;
desc.add_options()
("foo", new untyped_value())
("fe,baz", new untyped_value())
("chroots,all-chroots", new untyped_value())
("sessions,all-sessions", new untyped_value())
("everything,all", new untyped_value())
("qux,fo,q", new untyped_value())
;
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().second, 1u);
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().first[0], "foo");
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().second, 2u);
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().first[0], "fe");
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().second, 2u);
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().first[0], "qux");
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().first[1], "fo");
}
void test_formatting()
{
// Long option descriptions used to crash on MSVC-8.0.
@@ -124,6 +179,21 @@ void test_formatting()
);
}
void test_multiname_option_formatting()
{
options_description desc;
desc.add_options()
("foo,bar", new untyped_value(), "a multiple-name option")
;
stringstream ss;
ss << desc;
BOOST_CHECK_EQUAL(ss.str(),
" --foo arg a multiple-name option\n"
);
}
void test_formatting_description_length()
{
{
@@ -245,12 +315,28 @@ void test_value_name()
);
}
void test_multiname_key_and_switch_selection()
{
// cases:
// foo,f -> f
// foo, c -> c
// foo,f,g -> g
// f,g,h -> h
// f,foo throws
// foo,bar -> no switch
// foo,f,bar -> no switch
// what about empty strings - consecutive ,'s ?
}
int main(int, char* [])
{
test_type();
test_approximation();
test_long_names_for_option_description();
test_formatting();
test_multiname_key_and_switch_selection();
test_multiname_option_formatting();
test_formatting_description_length();
test_long_default_value();
test_word_wrapping();

View File

@@ -16,6 +16,7 @@ using namespace boost;
#include <sstream>
#include <iostream>
#include <iomanip>
using namespace std;
#if defined(__sun)
@@ -70,20 +71,18 @@ pair<string, string> additional_parser(const std::string&)
return pair<string, string>();
}
void test_command_line()
{
// The following commented out blocks used to test parsing
// command line without syntax specification behaviour.
// It is disabled now and probably will never be enabled again:
// it is not possible to figure out what command line means without
// user's help.
#if 0
namespace command_line {
#if 0
// The following commented out blocks used to test parsing
// command line without syntax specification behaviour.
// It is disabled now and probably will never be enabled again:
// it is not possible to figure out what command line means without
// user's help.
void test_parsing_without_specifying_options() {
char* cmdline1[] = { "--a", "--b=12", "-f", "-g4", "-", "file" };
options_and_arguments a1 =
parse_command_line(cmdline1,
cmdline1 + sizeof(cmdline1)/sizeof(cmdline1[0]));
options_and_arguments a1 = parse_command_line(cmdline1,
cmdline1 + sizeof(cmdline1) / sizeof(cmdline1[0]));
BOOST_REQUIRE(a1.options().size() == 4);
BOOST_CHECK(a1.options()[0] == msp("a", ""));
BOOST_CHECK(a1.options()[1] == msp("b", "12"));
@@ -92,71 +91,80 @@ void test_command_line()
BOOST_REQUIRE(a1.arguments().size() == 2);
BOOST_CHECK(a1.arguments()[0] == "-");
BOOST_CHECK(a1.arguments()[1] == "file");
char* cmdline2[] = { "--a", "--", "file" };
options_and_arguments a2 =
parse_command_line(cmdline2,
cmdline2 + sizeof(cmdline2)/sizeof(cmdline2[0]));
options_and_arguments a2 = parse_command_line(cmdline2,
cmdline2 + sizeof(cmdline2) / sizeof(cmdline2[0]));
BOOST_REQUIRE(a2.options().size() == 1);
BOOST_CHECK(a2.options()[0] == msp("a", ""));
BOOST_CHECK(a2.arguments().size() == 1);
BOOST_CHECK(a2.arguments()[0] == "file");
#endif
}
#endif
void test_many_different_options() {
options_description desc;
desc.add_options()
("foo,f", new untyped_value(), "")
// Explicit qualification is a workaround for vc6
("bar,b", po::value<std::string>(), "")
( // Explicit qualification is a workaround for vc6
"bar,b", po::value<std::string>(), "")
("car,voiture", new untyped_value())
("dog,dawg", new untyped_value())
("baz", new untyped_value())
("plug*", new untyped_value())
;
("plug*", new untyped_value());
const char* cmdline3_[] = { "--foo=12", "-f4", "--bar=11", "-b4",
"--plug3=10"};
"--voiture=15", "--dawg=16", "--dog=17", "--plug3=10" };
vector<string> cmdline3 = sv(cmdline3_,
sizeof(cmdline3_)/sizeof(const char*));
vector<option> a3 =
command_line_parser(cmdline3).options(desc).run().options;
BOOST_CHECK_EQUAL(a3.size(), 5u);
sizeof(cmdline3_) / sizeof(const char*));
vector<option> a3 =
command_line_parser(cmdline3).options(desc).run().options;
BOOST_CHECK_EQUAL(a3.size(), 8u);
check_value(a3[0], "foo", "12");
check_value(a3[1], "foo", "4");
check_value(a3[2], "bar", "11");
check_value(a3[3], "bar", "4");
check_value(a3[4], "plug3", "10");
check_value(a3[4], "car", "15");
check_value(a3[5], "dog", "16");
check_value(a3[6], "dog", "17");
check_value(a3[7], "plug3", "10");
// Regression test: check that '0' as style is interpreted as
// 'default_style'
vector<option> a4 =
parse_command_line(sizeof(cmdline3_)/sizeof(const char*), cmdline3_,
desc, 0, additional_parser).options;
BOOST_CHECK_EQUAL(a4.size(), 4u);
vector<option> a4 = parse_command_line(
sizeof(cmdline3_) / sizeof(const char*), cmdline3_, desc, 0,
additional_parser).options;
// The default style is unix-style, where the first argument on the command-line
// is the name of a binary, not an option value, so that should be ignored
BOOST_CHECK_EQUAL(a4.size(), 7u);
check_value(a4[0], "foo", "4");
check_value(a4[1], "bar", "11");
check_value(a4[2], "bar", "4");
check_value(a4[3], "car", "15");
check_value(a4[4], "dog", "16");
check_value(a4[5], "dog", "17");
check_value(a4[6], "plug3", "10");
}
void test_not_crashing_with_empty_string_values() {
// Check that we don't crash on empty values of type 'string'
const char* cmdline4[] = {"", "--open", ""};
const char* cmdline4[] = { "", "--open", "" };
options_description desc2;
desc2.add_options()
("open", po::value<string>())
;
desc2.add_options()("open", po::value<string>());
variables_map vm;
po::store(po::parse_command_line(sizeof(cmdline4)/sizeof(const char*), const_cast<char**>(cmdline4), desc2), vm);
po::store(
po::parse_command_line(sizeof(cmdline4) / sizeof(const char*),
const_cast<char**>(cmdline4), desc2), vm);
}
const char* cmdline5[] = {"", "-p7", "-o", "1", "2", "3", "-x8"};
void test_multitoken() {
const char* cmdline5[] = { "", "-p7", "-o", "1", "2", "3", "-x8" };
options_description desc3;
desc3.add_options()
(",p", po::value<string>())
(",o", po::value<string>()->multitoken())
(",x", po::value<string>())
;
vector<option> a5 =
parse_command_line(sizeof(cmdline5)/sizeof(const char*), const_cast<char**>(cmdline5),
desc3, 0, additional_parser).options;
(",x", po::value<string>());
vector<option> a5 = parse_command_line(
sizeof(cmdline5) / sizeof(const char*),
const_cast<char**>(cmdline5), desc3, 0, additional_parser).options;
BOOST_CHECK_EQUAL(a5.size(), 3u);
check_value(a5[0], "-p", "7");
BOOST_REQUIRE(a5[1].value.size() == 3);
@@ -164,28 +172,60 @@ void test_command_line()
BOOST_CHECK_EQUAL(a5[1].value[0], "1");
BOOST_CHECK_EQUAL(a5[1].value[1], "2");
BOOST_CHECK_EQUAL(a5[1].value[2], "3");
check_value(a5[2], "-x", "8");
check_value(a5[2], "-x", "8");
}
void test_multitoken_and_multiname() {
const char* cmdline[] = { "program", "-fone", "-b", "two", "--foo", "three", "four", "-zfive", "--fee", "six" };
options_description desc;
desc.add_options()
("bar,b", po::value<string>())
("foo,fee,f", po::value<string>()->multitoken())
("fizbaz,baz,z", po::value<string>());
vector<option> parsed_options = parse_command_line(
sizeof(cmdline) / sizeof(const char*),
const_cast<char**>(cmdline), desc, 0, additional_parser).options;
BOOST_CHECK_EQUAL(parsed_options.size(), 5u);
check_value(parsed_options[0], "foo", "one");
check_value(parsed_options[1], "bar", "two");
BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo");
BOOST_REQUIRE(parsed_options[2].value.size() == 2);
BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three");
BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four");
check_value(parsed_options[3], "fizbaz", "five");
check_value(parsed_options[4], "foo", "six");
const char* cmdline_2[] = { "program", "-fone", "-b", "two", "--fee", "three", "four", "-zfive", "--foo", "six" };
parsed_options = parse_command_line(
sizeof(cmdline_2) / sizeof(const char*),
const_cast<char**>(cmdline_2), desc, 0, additional_parser).options;
BOOST_CHECK_EQUAL(parsed_options.size(), 5u);
check_value(parsed_options[0], "foo", "one");
check_value(parsed_options[1], "bar", "two");
BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo");
BOOST_REQUIRE(parsed_options[2].value.size() == 2);
BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three");
BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four");
check_value(parsed_options[3], "fizbaz", "five");
check_value(parsed_options[4], "foo", "six");
}
po::options_description desc4( "" );
void test_multitoken_vector_option() {
po::options_description desc4("");
desc4.add_options()
( "multitoken,m",
po::value< std::vector< std::string > >()->multitoken(),
"values"
)
( "file",
po::value< std::string >(),
"the file to process"
)
;
("multitoken,multi-token,m", po::value<std::vector<std::string> >()->multitoken(), "values")
("file", po::value<std::string>(), "the file to process");
po::positional_options_description p;
p.add( "file", 1 );
const char* cmdline6[] = {"", "-m", "token1", "token2", "--", "some_file"};
vector<option> a6 =
command_line_parser(sizeof(cmdline6)/sizeof(const char*), const_cast<char**>(cmdline6)).options(desc4).positional(p)
.run().options;
p.add("file", 1);
const char* cmdline6[] = { "", "-m", "token1", "token2", "--", "some_file" };
vector<option> a6 =
command_line_parser(sizeof(cmdline6) / sizeof(const char*),
const_cast<char**>(cmdline6)).options(desc4).positional(p).run().options;
BOOST_CHECK_EQUAL(a6.size(), 2u);
BOOST_REQUIRE(a6[0].value.size() == 2);
BOOST_CHECK_EQUAL(a6[0].string_key, "multitoken");
@@ -196,6 +236,21 @@ void test_command_line()
BOOST_CHECK_EQUAL(a6[1].value[0], "some_file");
}
} // namespace command_line
void test_command_line()
{
#if 0
command_line::test_parsing_without_specifying_options();
#endif
command_line::test_many_different_options();
// Check that we don't crash on empty values of type 'string'
command_line::test_not_crashing_with_empty_string_values();
command_line::test_multitoken();
command_line::test_multitoken_vector_option();
command_line::test_multitoken_and_multiname();
}
void test_config_file(const char* config_file)
{
options_description desc;
@@ -206,6 +261,7 @@ void test_config_file(const char* config_file)
("plug*", new untyped_value)
("m1.v1", new untyped_value)
("m1.v2", new untyped_value)
("m1.v3,alias3", new untyped_value)
("b", bool_switch())
;
@@ -218,27 +274,30 @@ void test_config_file(const char* config_file)
"v1 = 1\n"
"\n"
"v2 = 2\n"
"v3 = 3\n"
;
stringstream ss(content1);
vector<option> a1 = parse_config_file(ss, desc).options;
BOOST_REQUIRE(a1.size() == 6);
BOOST_REQUIRE(a1.size() == 7);
check_value(a1[0], "gv1", "0");
check_value(a1[1], "empty_value", "");
check_value(a1[2], "plug3", "7");
check_value(a1[3], "b", "true");
check_value(a1[4], "m1.v1", "1");
check_value(a1[5], "m1.v2", "2");
check_value(a1[6], "m1.v3", "3");
// same test, but now options come from file
vector<option> a2 = parse_config_file<char>(config_file, desc).options;
BOOST_REQUIRE(a2.size() == 6);
BOOST_REQUIRE(a2.size() == 7);
check_value(a2[0], "gv1", "0");
check_value(a2[1], "empty_value", "");
check_value(a2[2], "plug3", "7");
check_value(a2[3], "b", "true");
check_value(a2[4], "m1.v1", "1");
check_value(a2[5], "m1.v2", "2");
check_value(a2[6], "m1.v3", "3");
}
void test_environment()
@@ -249,7 +308,7 @@ void test_environment()
("bar", new untyped_value, "")
;
#if defined(_WIN32) && ! defined(__BORLANDC__)
#if (defined(_WIN32) && ! defined(__BORLANDC__)) || (defined(__CYGWIN__))
_putenv("PO_TEST_FOO=1");
#else
putenv(const_cast<char*>("PO_TEST_FOO=1"));
@@ -257,9 +316,9 @@ void test_environment()
parsed_options p = parse_environment(desc, "PO_TEST_");
BOOST_REQUIRE(p.options.size() == 1);
BOOST_CHECK(p.options[0].string_key == "foo");
BOOST_CHECK (p.options[0].string_key == "foo");
BOOST_REQUIRE(p.options[0].value.size() == 1);
BOOST_CHECK(p.options[0].value[0] == "1");
BOOST_CHECK (p.options[0].value[0] == "1");
//TODO: since 'bar' does not allow a value, it cannot appear in environemt,
// which already has a value.
@@ -316,6 +375,8 @@ void test_unregistered()
check_value(a3[1], "m1.v1", "1");
}
int main(int, char* av[])
{
test_command_line();

View File

@@ -23,36 +23,36 @@ void required_throw_test()
;
variables_map vm;
bool throwed = false;
bool thrown = false;
{
// This test must throw exception
string cmdline = "prg -f file.txt";
vector< string > tokens = split_unix(cmdline);
throwed = false;
thrown = false;
try {
store(command_line_parser(tokens).options(opts).run(), vm);
notify(vm);
}
catch (required_option& e) {
BOOST_CHECK_EQUAL(e.what(), string("the option '--cfgfile' is required but missing"));
throwed = true;
thrown = true;
}
BOOST_CHECK(throwed);
BOOST_CHECK(thrown);
}
{
// This test mustn't throw exception
string cmdline = "prg -c config.txt";
vector< string > tokens = split_unix(cmdline);
throwed = false;
thrown = false;
try {
store(command_line_parser(tokens).options(opts).run(), vm);
notify(vm);
}
catch (required_option& e) {
throwed = true;
thrown = true;
}
BOOST_CHECK(!throwed);
BOOST_CHECK(!thrown);
}
}
@@ -67,12 +67,12 @@ void simple_required_test(const char* config_file)
;
variables_map vm;
bool throwed = false;
bool thrown = false;
{
// This test must throw exception
string cmdline = "prg -f file.txt";
vector< string > tokens = split_unix(cmdline);
throwed = false;
thrown = false;
try {
// options coming from different sources
store(command_line_parser(tokens).options(opts).run(), vm);
@@ -80,9 +80,35 @@ void simple_required_test(const char* config_file)
notify(vm);
}
catch (required_option& e) {
throwed = true;
thrown = true;
}
BOOST_CHECK(!throwed);
BOOST_CHECK(!thrown);
}
}
void multiname_required_test()
{
options_description opts;
opts.add_options()
("foo,bar", value<string>()->required(), "the foo")
;
variables_map vm;
bool thrown = false;
{
// This test must throw exception
string cmdline = "prg --bar file.txt";
vector< string > tokens = split_unix(cmdline);
thrown = false;
try {
// options coming from different sources
store(command_line_parser(tokens).options(opts).run(), vm);
notify(vm);
}
catch (required_option& e) {
thrown = true;
}
BOOST_CHECK(!thrown);
}
}
@@ -92,6 +118,7 @@ int main(int /*argc*/, char* av[])
{
required_throw_test();
simple_required_test(av[1]);
multiname_required_test();
return 0;
}

View File

@@ -111,7 +111,7 @@ void test_command_line()
// Explicit qualification is a workaround for vc6
("bar,b", po::value<std::string>(), "")
("baz", new untyped_value())
("plug*", new untyped_value())
("qux,plug*", new untyped_value())
;
const wchar_t* cmdline4_[] = { L"--foo=1\u0FF52", L"-f4", L"--bar=11",
@@ -126,6 +126,7 @@ void test_command_line()
check_value(a4[0], "foo", L"1\u0FF52");
check_value(a4[1], "foo", L"4");
check_value(a4[2], "bar", L"11");
check_value(a4[4], "qux", L"10");
}
// Since we've already tested conversion between parser encoding and