Commit ee2b725019442c5ba76eae28cd7b17e6516b6b72

Authored by Philip Top
Committed by GitHub
1 parent c4f1fc8e

fix: a few issues with config file output (#599)

* fix a few issues with config file output, namely an ability to print a default empty string, and to adjust the quote characters used when quoting strings or characters.

* fix formatting

* Update CMakeLists.txt

* Update README.md
README.md
... ... @@ -756,7 +756,7 @@ If it is desired that multiple configuration be allowed. Use
756 756 app.set_config("--config")->expected(1, X);
757 757 ```
758 758  
759   -Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments.
  759 +Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. Value strings with quote characters in it will be printed with a single quote.๐Ÿšง All other arguments will use double quote. Empty strings will use a double quoted argument.๐Ÿšง Numerical or boolean values are not quoted. ๐Ÿšง
760 760  
761 761 ### Inheriting defaults
762 762  
... ...
examples/CMakeLists.txt
... ... @@ -81,6 +81,22 @@ add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help)
81 81 set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
82 82 "-f,--file TEXT REQUIRED" "-d,--double FLOAT")
83 83  
  84 +####################################################
  85 +add_cli_exe(config_app config_app.cpp)
  86 +add_test(NAME config_app1 COMMAND config_app -p)
  87 +set_property(TEST config_app1 PROPERTY PASS_REGULAR_EXPRESSION "file=")
  88 +
  89 +add_test(NAME config_app2 COMMAND config_app -p -f /)
  90 +set_property(TEST config_app2 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
  91 +
  92 +add_test(NAME config_app3 COMMAND config_app -f "" -p)
  93 +set_property(TEST config_app3 PROPERTY PASS_REGULAR_EXPRESSION "file=\"\"")
  94 +
  95 +add_test(NAME config_app4 COMMAND config_app -f "/" -p)
  96 +set_property(TEST config_app4 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
  97 +
  98 +####################################################
  99 +
84 100 add_cli_exe(option_groups option_groups.cpp)
85 101 add_test(NAME option_groups_missing COMMAND option_groups)
86 102 set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from"
... ...
examples/config_app.cpp 0 โ†’ 100644
  1 +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
  2 +// under NSF AWARD 1414736 and by the respective contributors.
  3 +// All rights reserved.
  4 +//
  5 +// SPDX-License-Identifier: BSD-3-Clause
  6 +
  7 +#include <CLI/CLI.hpp>
  8 +#include <iostream>
  9 +#include <string>
  10 +
  11 +int main(int argc, char **argv) {
  12 +
  13 + CLI::App app("configuration print example");
  14 +
  15 + app.add_flag("-p,--print", "Print configuration and exit")->configurable(false); // NEW: print flag
  16 +
  17 + std::string file;
  18 + CLI::Option *opt = app.add_option("-f,--file,file", file, "File name")
  19 + ->capture_default_str()
  20 + ->run_callback_for_default(); // NEW: capture_default_str()
  21 +
  22 + int count{0};
  23 + CLI::Option *copt =
  24 + app.add_option("-c,--count", count, "Counter")->capture_default_str(); // NEW: capture_default_str()
  25 +
  26 + int v{0};
  27 + CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times")
  28 + ->capture_default_str(); // NEW: capture_default_str()
  29 +
  30 + double value{0.0}; // = 3.14;
  31 + app.add_option("-d,--double", value, "Some Value")->capture_default_str(); // NEW: capture_default_str()
  32 +
  33 + app.get_config_formatter_base()->quoteCharacter('"', '"');
  34 +
  35 + CLI11_PARSE(app, argc, argv);
  36 +
  37 + if(app.get_option("--print")->as<bool>()) { // NEW: print configuration and exit
  38 + std::cout << app.config_to_str(true, false);
  39 + return 0;
  40 + }
  41 +
  42 + std::cout << "Working on file: " << file << ", direct count: " << app.count("--file")
  43 + << ", opt count: " << opt->count() << std::endl;
  44 + std::cout << "Working on count: " << count << ", direct count: " << app.count("--count")
  45 + << ", opt count: " << copt->count() << std::endl;
  46 + std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n";
  47 + std::cout << "Some value: " << value << std::endl;
  48 +
  49 + return 0;
  50 +}
... ...
include/CLI/Config.hpp
... ... @@ -23,9 +23,9 @@ namespace CLI {
23 23 // [CLI11:config_hpp:verbatim]
24 24 namespace detail {
25 25  
26   -inline std::string convert_arg_for_ini(const std::string &arg) {
  26 +inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
27 27 if(arg.empty()) {
28   - return std::string(2, '"');
  28 + return std::string(2, stringQuote);
29 29 }
30 30 // some specifically supported strings
31 31 if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
... ... @@ -40,7 +40,7 @@ inline std::string convert_arg_for_ini(const std::string &amp;arg) {
40 40 }
41 41 // just quote a single non numeric character
42 42 if(arg.size() == 1) {
43   - return std::string("'") + arg + '\'';
  43 + return std::string(1, characterQuote) + arg + characterQuote;
44 44 }
45 45 // handle hex, binary or octal arguments
46 46 if(arg.front() == '0') {
... ... @@ -60,16 +60,20 @@ inline std::string convert_arg_for_ini(const std::string &amp;arg) {
60 60 }
61 61 }
62 62 }
63   - if(arg.find_first_of('"') == std::string::npos) {
64   - return std::string("\"") + arg + '"';
  63 + if(arg.find_first_of(stringQuote) == std::string::npos) {
  64 + return std::string(1, stringQuote) + arg + stringQuote;
65 65 } else {
66   - return std::string("'") + arg + '\'';
  66 + return characterQuote + arg + characterQuote;
67 67 }
68 68 }
69 69  
70 70 /// Comma separated join, adds quotes if needed
71   -inline std::string
72   -ini_join(const std::vector<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') {
  71 +inline std::string ini_join(const std::vector<std::string> &args,
  72 + char sepChar = ',',
  73 + char arrayStart = '[',
  74 + char arrayEnd = ']',
  75 + char stringQuote = '"',
  76 + char characterQuote = '\'') {
73 77 std::string joined;
74 78 if(args.size() > 1 && arrayStart != '\0') {
75 79 joined.push_back(arrayStart);
... ... @@ -82,7 +86,7 @@ ini_join(const std::vector&lt;std::string&gt; &amp;args, char sepChar = &#39;,&#39;, char arraySta
82 86 joined.push_back(' ');
83 87 }
84 88 }
85   - joined.append(convert_arg_for_ini(arg));
  89 + joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
86 90 }
87 91 if(args.size() > 1 && arrayEnd != '\0') {
88 92 joined.push_back(arrayEnd);
... ... @@ -296,13 +300,16 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
296 300 }
297 301 }
298 302 std::string name = prefix + opt->get_single_name();
299   - std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd);
  303 + std::string value = detail::ini_join(
  304 + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
300 305  
301 306 if(value.empty() && default_also) {
302 307 if(!opt->get_default_str().empty()) {
303   - value = detail::convert_arg_for_ini(opt->get_default_str());
  308 + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
304 309 } else if(opt->get_expected_min() == 0) {
305 310 value = "false";
  311 + } else if(opt->get_run_callback_for_default()) {
  312 + value = "\"\""; // empty string default value
306 313 }
307 314 }
308 315  
... ...
include/CLI/ConfigFwd.hpp
... ... @@ -87,6 +87,10 @@ class ConfigBase : public Config {
87 87 char arraySeparator = ',';
88 88 /// the character used separate the name from the value
89 89 char valueDelimiter = '=';
  90 + /// the character to use around strings
  91 + char stringQuote = '"';
  92 + /// the character to use around single characters
  93 + char characterQuote = '\'';
90 94  
91 95 public:
92 96 std::string
... ... @@ -114,6 +118,12 @@ class ConfigBase : public Config {
114 118 valueDelimiter = vSep;
115 119 return this;
116 120 }
  121 + /// Specify the quote characters used around strings and characters
  122 + ConfigBase *quoteCharacter(char qString, char qChar) {
  123 + stringQuote = qString;
  124 + characterQuote = qChar;
  125 + return this;
  126 + }
117 127 };
118 128  
119 129 /// the default Config is the TOML file format
... ...