mirror of
https://github.com/boostorg/program_options.git
synced 2026-01-19 04:22:15 +00:00
Merge branch 'develop'
This commit is contained in:
26
.travis.yml
26
.travis.yml
@@ -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:
|
||||
|
||||
10
README.md
10
README.md
@@ -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) | [](https://travis-ci.org/boostorg/program_options) | [](https://ci.appveyor.com/project/jeking3/date-time-1evbf) | [](https://scan.coverity.com/projects/boostorg-program_options) | [](https://codecov.io/gh/boostorg/program_options/branch/master) | [](https://pdimov.github.io/boostdep-report/master/program_options.html) | [](http://www.boost.org/doc/libs/master/doc/html/program_options.html) | [](http://www.boost.org/development/tests/master/developer/program_options.html)
|
||||
|[`develop`](https://github.com/boostorg/program_options/tree/develop) | [](https://travis-ci.org/boostorg/program_options) | [](https://ci.appveyor.com/project/vprus/program-options/branch/develop) | [](https://scan.coverity.com/projects/boostorg-program_options) | [](https://codecov.io/gh/boostorg/program_options/branch/develop) | [](https://pdimov.github.io/boostdep-report/develop/program_options.html) | [](http://www.boost.org/doc/libs/develop/doc/html/program_options.html) | [](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) | [](https://travis-ci.org/boostorg/program_options) | [](https://ci.appveyor.com/project/vprus/program-options/branch/master) | [](https://codecov.io/gh/boostorg/program_options/branch/master) | [](https://pdimov.github.io/boostdep-report/master/program_options.html) | [](http://www.boost.org/doc/libs/master/doc/html/program_options.html) | [](http://www.boost.org/development/tests/master/developer/program_options.html)
|
||||
|[`develop`](https://github.com/boostorg/program_options/tree/develop) | [](https://travis-ci.org/boostorg/program_options) | [](https://ci.appveyor.com/project/vprus/program-options/branch/develop) | [](https://codecov.io/gh/boostorg/program_options/branch/develop) | [](https://pdimov.github.io/boostdep-report/develop/program_options.html) | [](http://www.boost.org/doc/libs/develop/doc/html/program_options.html) | [](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.
|
||||
|
||||
@@ -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<string>(), "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<bool>())</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:
|
||||
-->
|
||||
-->
|
||||
|
||||
@@ -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>">
|
||||
|
||||
@@ -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:
|
||||
-->
|
||||
-->
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
242
example/config_file_types.cpp
Normal file
242
example/config_file_types.cpp
Normal 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
47
example/env_options.cpp
Normal 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;
|
||||
}
|
||||
690
example/options_heirarchy.cpp
Normal file
690
example/options_heirarchy.cpp
Normal 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
|
||||
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,5 +5,5 @@ b = true
|
||||
|
||||
[m1]
|
||||
v1 = 1
|
||||
|
||||
v2 = 2
|
||||
v3 = 3
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user