From f955bd6b9f2b57acdcc2a3a539a72358bae048cf Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Thu, 15 Mar 2018 22:52:02 +0300 Subject: [PATCH 1/9] Update appveyor master badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c21fd2..2aed3a7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Distributed under the [Boost Software License, Version 1.0](http://www.boost.org |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) +|[`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) | [![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) ### Directories From d295fcca69cdbff8f02b9d2aa5e4473e874dbe84 Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Thu, 15 Mar 2018 22:58:22 +0300 Subject: [PATCH 2/9] Disable coverity, since they appear to have some problems. --- .travis.yml | 11 ----------- README.md | 8 ++++---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 651835a..47aab2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -154,17 +154,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: diff --git a/README.md b/README.md index 2aed3a7..3353507 100644 --- a/README.md +++ b/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) | [![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) | [![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 From 491cb17e3abd7f006ac343eb7789fcd8e141751a Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Fri, 16 Mar 2018 12:35:57 +0300 Subject: [PATCH 3/9] Fix uninitialized validation_error::m_kind. --- include/boost/program_options/errors.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/boost/program_options/errors.hpp b/include/boost/program_options/errors.hpp index 2df4096..6f3da0d 100644 --- a/include/boost/program_options/errors.hpp +++ b/include/boost/program_options/errors.hpp @@ -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); From 9bf97405dcfedeea24410769c891e6849fd18e46 Mon Sep 17 00:00:00 2001 From: Tom Kent Date: Thu, 22 Mar 2018 10:29:58 -0500 Subject: [PATCH 4/9] Additional examples (#21) * Added multiple inputs to example In the section after the introduction of using value> to store multiple values, I updated the example to show multiple input values and their results. * Make it clear how to enable sections for ini files It was unclear how the user should support sections, should they have nested options_description's (no), nested variables_map's (no), it is just a dotted string that is input! This adds a snippet showing that. * Added an example for environment options This example shows how to use program_options to pull environmental options into a program. This instance uses a function to map env options to config options. * Added an example showing different types in a config file I went through a lot of the common types that a user may want to include in a config file (especially the boolean options) and showed an example with them all. With some minor modifications, this could also be added to the tests directory as there are several cases in here that I didn't see checked anywhere else in the code. * Added explanation comments to new examples * Added an example with a heirarchy of inputs This file shows an example program that can get inputs from the command line, environmental variables, multiple config files specified on the command line, and a default config file. There are multiple usage examples at the bottom in the comments. * Reference to example showing environment options * Added section detailing type conversion. Added explicity acknowledging that hex/oct/bin formatted strings aren't allowed. Detailed the bool_switch value and what strings evaluate true/false. * Added a global to the config file example * Semicolon typo * Split components into seperate functions * Added unregistered entry and flag to prevent error * Added logic to capture unregistered value * Build new examples * Backslashes need escaping on unix * match permissions --- doc/overview.xml | 49 ++- doc/tutorial.xml | 8 +- example/Jamfile.v2 | 4 + example/config_file_types.cpp | 242 ++++++++++++ example/env_options.cpp | 47 +++ example/options_heirarchy.cpp | 690 ++++++++++++++++++++++++++++++++++ 6 files changed, 1033 insertions(+), 7 deletions(-) create mode 100644 example/config_file_types.cpp create mode 100644 example/env_options.cpp create mode 100644 example/options_heirarchy.cpp diff --git a/doc/overview.xml b/doc/overview.xml index f7de296..b6b0f35 100644 --- a/doc/overview.xml +++ b/doc/overview.xml @@ -512,7 +512,13 @@ visual_bell=yes gui.accessibility.visual_bell=yes - + When the option "gui.accessibility.visual_bell" has been added to the options + +options_description desc; +desc.add_options() + ("gui.accessibility.visual_bell", value<string>(), "flash screen for bell") + ; +
@@ -559,12 +565,49 @@ gui.accessibility.visual_bell=yes function, any function taking a std::string and returning std::string. 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".
+
+ Types + + 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. + + 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. + + Booleans a special in that there are multiple ways to come at them. + Similar to another value type, it can be specified as ("my-option", + value()), and then set as: + +example --my-option=true + + However, more typical is that boolean values are set by the simple + presence of a switch. This is enabled by &bool_switch; as in + ("other-option", bool_switch()). This will cause the value to + default to false and it will become true if the switch is found: + +example --other-switch + + 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. +
+
Annotated List of Symbols @@ -649,4 +692,4 @@ gui.accessibility.visual_bell=yes sgml-parent-document: ("program_options.xml" "section") sgml-set-face: t End: ---> \ No newline at end of file +--> diff --git a/doc/tutorial.xml b/doc/tutorial.xml index d3e447a..b937576 100644 --- a/doc/tutorial.xml +++ b/doc/tutorial.xml @@ -208,9 +208,9 @@ Allowed options: --input-file arg : input file $ bin/gcc/debug/options_description Optimization level is 10 -$ bin/gcc/debug/options_description --optimization 4 -I foo a.cpp -Include paths are: foo -Input files are: a.cpp +$ bin/gcc/debug/options_description --optimization 4 -I foo -I another/path --include-path third/include/path a.cpp b.cpp +Include paths are: foo another/path third/include/path +Input files are: a.cpp b.cpp Optimization level is 4 @@ -350,4 +350,4 @@ Optimization level is 4 sgml-parent-document: ("program_options.xml" "section") sgml-set-face: t End: ---> \ No newline at end of file +--> diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 index d669d3e..8bd9d51 100644 --- a/example/Jamfile.v2 +++ b/example/Jamfile.v2 @@ -12,3 +12,7 @@ exe custom_syntax : custom_syntax.cpp ; exe real : real.cpp ; exe regex : regex.cpp /boost/regex//boost_regex ; + +exe config_file_types : config_file_types.cpp ; +exe env_options : env_options.cpp ; +exe options_heirarchy : options_heirarchy.cpp ; diff --git a/example/config_file_types.cpp b/example/config_file_types.cpp new file mode 100644 index 0000000..e466e94 --- /dev/null +++ b/example/config_file_types.cpp @@ -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 +namespace po = boost::program_options; + +#include +#include +#include +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()) + + ("strings.word", po::value()) + ("strings.phrase", po::value()) + ("strings.quoted", po::value()) + + ("ints.positive", po::value()) + ("ints.negative", po::value()) + ("ints.hex", po::value()) + ("ints.oct", po::value()) + ("ints.bin", po::value()) + + ("floats.positive", po::value()) + ("floats.negative", po::value()) + ("floats.double", po::value()) + ("floats.int", po::value()) + ("floats.int_dot", po::value()) + ("floats.dot", po::value()) + ("floats.exp_lower", po::value()) + ("floats.exp_upper", po::value()) + ("floats.exp_decimal", po::value()) + ("floats.exp_negative", po::value()) + ("floats.exp_negative_val", po::value()) + ("floats.exp_negative_negative_val", po::value()) + + // Load booleans as value, so they will require a --option=value on the command line + //("booleans.number_true", po::value()) + //("booleans.number_false", po::value()) + //("booleans.yn_true", po::value()) + //("booleans.yn_false", po::value()) + //("booleans.tf_true", po::value()) + //("booleans.tf_false", po::value()) + //("booleans.onoff_true", po::value()) + //("booleans.onoff_false", po::value()) + //("booleans.present_equal_true", po::value()) + //("booleans.present_no_equal_true", po::value()) + + // 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 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 unregistered = po::collect_unrecognized(parsed.options, po::exclude_positional); + notify(vm); + + return unregistered; +} + +void check_results(po::variables_map &vm, vector 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() == expected_global_string); + + assert(unregistered[0] == expected_unreg_option); + assert(unregistered[1] == expected_unreg_value); + + assert(vm["strings.word"].as() == expected_strings_word); + assert(vm["strings.phrase"].as() == expected_strings_phrase); + assert(vm["strings.quoted"].as() == expected_strings_quoted); + + assert(vm["ints.positive"].as() == expected_int_postitive); + assert(vm["ints.negative"].as() == expected_int_negative); + //assert(vm["ints.hex"].as() == expected_int_hex); + //assert(vm["ints.oct"].as() == expected_int_oct); + //assert(vm["ints.bin"].as() == expected_int_bin); + + assert(check_float(vm["floats.positive"].as(), expected_float_positive)); + assert(check_float(vm["floats.negative"].as(), expected_float_negative)); + assert(check_float(vm["floats.double"].as(), expected_float_double)); + assert(check_float(vm["floats.int"].as(), expected_float_int)); + assert(check_float(vm["floats.int_dot"].as(), expected_float_int_dot)); + assert(check_float(vm["floats.dot"].as(), expected_float_dot)); + assert(check_float(vm["floats.exp_lower"].as(), expected_float_exp_lower)); + assert(check_float(vm["floats.exp_upper"].as(), expected_float_exp_upper)); + assert(check_float(vm["floats.exp_decimal"].as(), expected_float_exp_decimal)); + assert(check_float(vm["floats.exp_negative"].as(), expected_float_exp_negative)); + assert(check_float(vm["floats.exp_negative_val"].as(), expected_float_exp_negative_val)); + assert(check_float(vm["floats.exp_negative_negative_val"].as(), expected_float_exp_negative_negative_val)); + + assert(vm["booleans.number_true"].as() == expected_number_true); + assert(vm["booleans.number_false"].as() == expected_number_false); + assert(vm["booleans.yn_true"].as() == expected_yn_true); + assert(vm["booleans.yn_false"].as() == expected_yn_false); + assert(vm["booleans.tf_true"].as() == expected_tf_true); + assert(vm["booleans.tf_false"].as() == expected_tf_false); + assert(vm["booleans.onoff_true"].as() == expected_onoff_true); + assert(vm["booleans.onoff_false"].as() == expected_onoff_false); + assert(vm["booleans.present_equal_true"].as() == expected_present_equal_true); + //assert(vm["booleans.present_no_equal_true"].as() == 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; +} diff --git a/example/env_options.cpp b/example/env_options.cpp new file mode 100644 index 0000000..bab37e2 --- /dev/null +++ b/example/env_options.cpp @@ -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 +namespace po = boost::program_options; +#include +#include + +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()->default_value("INFO"), "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL") + ; + + po::variables_map vm; + store(po::parse_environment(config, boost::function1(mapper)), vm); + notify(vm); + + if (vm.count("path")) + { + std::cout << "First 75 chars of the system path: \n"; + std::cout << vm["path"].as().substr(0, 75) << std::endl; + } + + std::cout << "Verbosity: " << vm["verbosity"].as() << std::endl; +} + +int main(int ac, char* av[]) +{ + get_env_options(); + + return 0; +} diff --git a/example/options_heirarchy.cpp b/example/options_heirarchy.cpp new file mode 100644 index 0000000..c913b14 --- /dev/null +++ b/example/options_heirarchy.cpp @@ -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 +namespace po = boost::program_options; +#include +#include +#include +#include +#include + +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 Verbosity() + { + return results["verbosity"].as(); + } + std::vector IncludePath() + { + if (results.count("include-path")) + { + return results["include-path"].as>(); + } + return std::vector(); + } + std::string MasterFile() + { + if (results.count("master-file")) + { + return results["master-file"].as(); + } + return ""; + } + std::vector Files() + { + if (results.count("file")) + { + return results["file"].as>(); + } + return std::vector(); + } + bool GUI() + { + if (results["run-gui"].as()) + { + return true; + } + return false; + } + GuiOpts GuiValues() + { + GuiOpts opts; + opts.width = results["gui.width"].as(); + opts.height = results["gui.height"].as(); + return opts; + } + NetworkOpts NetworkValues() + { + NetworkOpts opts; + opts.address = results["network.ip"].as(); + opts.port = results["network.port"].as(); + 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>(), + "config files to parse (always parses default.cfg)") + ; + hidden_command_line_options.add_options() + ("master-file", po::value()) + ("file", po::value>()) + ; + positional_options.add("master-file", 1); + positional_options.add("file", -1); + } + void SetCommonOptions() + { + common_options.add_options() + ("path", po::value()->default_value(""), + "the execution path to use (imports from environment if not specified)") + ("verbosity", po::value()->default_value("INFO"), + "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL") + ("include-path,I", po::value>()->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()->default_value("log")) + ("gui.height", po::value()->default_value(100)) + ("gui.width", po::value()->default_value(100)) + ("network.ip", po::value()->default_value("127.0.0.1")) + ("network.port", po::value()->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::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>(); + 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 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()) +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 + +*/ From 646cc20a543017a456ab36b566197be66281a2e1 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Fri, 23 Mar 2018 08:10:46 +0000 Subject: [PATCH 5/9] Fix the documentation build --- doc/overview.xml | 6 +++--- doc/program_options.ent | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/overview.xml b/doc/overview.xml index b6b0f35..52fe9b9 100644 --- a/doc/overview.xml +++ b/doc/overview.xml @@ -585,12 +585,12 @@ desc.add_options() 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. + representations that are available in C++ literals are not supported by + lexical_cast, and thus will not work with program_options. Booleans a special in that there are multiple ways to come at them. Similar to another value type, it can be specified as ("my-option", - value()), and then set as: + value<bool>()), and then set as: example --my-option=true diff --git a/doc/program_options.ent b/doc/program_options.ent index 084f605..cd6504b 100644 --- a/doc/program_options.ent +++ b/doc/program_options.ent @@ -44,3 +44,5 @@ basic_option"> +bool_switch"> From 7d495841eda85def2f0faec611ea0921f5996949 Mon Sep 17 00:00:00 2001 From: "James E. King, III" Date: Sun, 29 Apr 2018 08:12:38 -0400 Subject: [PATCH 6/9] Fix mention of date_time in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3353507..b2bd595 100644 --- a/README.md +++ b/README.md @@ -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. From bacc983235060498078cf41855d785c43e05999e Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Thu, 14 Jun 2018 13:57:17 +0300 Subject: [PATCH 7/9] Disable examples that use C++11 features. --- example/Jamfile.v2 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 index 8bd9d51..9f0b1d8 100644 --- a/example/Jamfile.v2 +++ b/example/Jamfile.v2 @@ -13,6 +13,9 @@ exe custom_syntax : custom_syntax.cpp ; exe real : real.cpp ; exe regex : regex.cpp /boost/regex//boost_regex ; -exe config_file_types : config_file_types.cpp ; -exe env_options : env_options.cpp ; -exe options_heirarchy : options_heirarchy.cpp ; +# 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 ; From 4b81beaaa995026ebdc032bfe809f64a5056ce88 Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Thu, 14 Jun 2018 15:02:07 +0300 Subject: [PATCH 8/9] Disable usbsan. It presently fails like this: /usr/bin/ld: unrecognized option '--push-state--no-as-needed' /usr/bin/ld: use the --help option for usage information collect2: error: ld returned 1 exit status "g++-7" -o "../../bin.v2/libs/program_options/test/gcc-7/debug/cxxstd-03-iso/link-static/test_convert" -Wl,--start-group "../../bin.v2/libs/program_options/test/gcc-7/debug/cxxstd-03-iso/link-static/test_convert.o" "../../bin.v2/libs/program_options/build/gcc-7/debug/cxxstd-03-iso/link-static/libboost_program_options.a" -Wl,-Bstatic -Wl,-Bdynamic -Wl,--end-group -m64 -std=c++03 -g -fno-sanitize-recover=undefined -fsanitize=undefined This error seems like some misconfiguration of compiler install, not something I can fix. --- .travis.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 47aab2d..d906b49 100644 --- a/.travis.yml +++ b/.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 From 589d55837e6c0d7a629a669cdb20c319bdcc19ea Mon Sep 17 00:00:00 2001 From: Eyal Rozenberg Date: Fri, 15 Jun 2018 16:51:37 +0200 Subject: [PATCH 9/9] Fixes #1: Support for multiple long names per option An unintrusive implementation - no existing interfaces changed, and a single addition for obtaining all of the different long names. Notes: * Tests added for the new functionality, and existing tests expanded to take it into account. * It is now impossible to specify long names with commas in them (but then, that wasn't properly supported before either, more of an oversight). * The multiple long options are not included in the usage information - just the first one of them is printed --- .../program_options/options_description.hpp | 28 ++- src/options_description.cpp | 132 ++++++----- test/cmdline_test.cpp | 6 +- test/config_test.cfg | 2 +- test/exception_test.cpp | 108 ++++++++- test/options_description_test.cpp | 86 ++++++++ test/parsers_test.cpp | 205 ++++++++++++------ test/required_test.cpp | 49 ++++- test/unicode_test.cpp | 3 +- 9 files changed, 475 insertions(+), 144 deletions(-) diff --git a/include/boost/program_options/options_description.hpp b/include/boost/program_options/options_description.hpp index fac6acc..90d913d 100644 --- a/include/boost/program_options/options_description.hpp +++ b/include/boost/program_options/options_description.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -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 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 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 m_value_semantic; diff --git a/src/options_description.cpp b/src/options_description.cpp index 6592a5d..d4fcfc8 100644 --- a/src/options_description.cpp +++ b/src/options_description.cpp @@ -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::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 + option_description::long_names() const + { + return (m_long_names.empty()) + ? std::pair( NULL, 0 ) + : std::pair( &(*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 diff --git a/test/cmdline_test.cpp b/test/cmdline_test.cpp index e971819..1fc0af8 100644 --- a/test/cmdline_test.cpp +++ b/test/cmdline_test.cpp @@ -463,11 +463,13 @@ void test_additional_parser() desc.add_options() ("response-file", value(), "response file") ("foo", value(), "foo") + ("bar,baz", value(), "bar") ; vector 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