diff --git a/README.md b/README.md index 8f4a298..a9f157a 100644 --- a/README.md +++ b/README.md @@ -756,7 +756,7 @@ If it is desired that multiple configuration be allowed. Use app.set_config("--config")->expected(1, X); ``` -Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. +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. 🚧 ### Inheriting defaults diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1433219..aee80f8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -81,6 +81,22 @@ add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help) set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION "-f,--file TEXT REQUIRED" "-d,--double FLOAT") +#################################################### +add_cli_exe(config_app config_app.cpp) +add_test(NAME config_app1 COMMAND config_app -p) +set_property(TEST config_app1 PROPERTY PASS_REGULAR_EXPRESSION "file=") + +add_test(NAME config_app2 COMMAND config_app -p -f /) +set_property(TEST config_app2 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"") + +add_test(NAME config_app3 COMMAND config_app -f "" -p) +set_property(TEST config_app3 PROPERTY PASS_REGULAR_EXPRESSION "file=\"\"") + +add_test(NAME config_app4 COMMAND config_app -f "/" -p) +set_property(TEST config_app4 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"") + +#################################################### + add_cli_exe(option_groups option_groups.cpp) add_test(NAME option_groups_missing COMMAND option_groups) set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from" diff --git a/examples/config_app.cpp b/examples/config_app.cpp new file mode 100644 index 0000000..381c147 --- /dev/null +++ b/examples/config_app.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include + +int main(int argc, char **argv) { + + CLI::App app("configuration print example"); + + app.add_flag("-p,--print", "Print configuration and exit")->configurable(false); // NEW: print flag + + std::string file; + CLI::Option *opt = app.add_option("-f,--file,file", file, "File name") + ->capture_default_str() + ->run_callback_for_default(); // NEW: capture_default_str() + + int count{0}; + CLI::Option *copt = + app.add_option("-c,--count", count, "Counter")->capture_default_str(); // NEW: capture_default_str() + + int v{0}; + CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times") + ->capture_default_str(); // NEW: capture_default_str() + + double value{0.0}; // = 3.14; + app.add_option("-d,--double", value, "Some Value")->capture_default_str(); // NEW: capture_default_str() + + app.get_config_formatter_base()->quoteCharacter('"', '"'); + + CLI11_PARSE(app, argc, argv); + + if(app.get_option("--print")->as()) { // NEW: print configuration and exit + std::cout << app.config_to_str(true, false); + return 0; + } + + std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") + << ", opt count: " << opt->count() << std::endl; + std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") + << ", opt count: " << copt->count() << std::endl; + std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n"; + std::cout << "Some value: " << value << std::endl; + + return 0; +} diff --git a/include/CLI/Config.hpp b/include/CLI/Config.hpp index 725872d..17b816a 100644 --- a/include/CLI/Config.hpp +++ b/include/CLI/Config.hpp @@ -23,9 +23,9 @@ namespace CLI { // [CLI11:config_hpp:verbatim] namespace detail { -inline std::string convert_arg_for_ini(const std::string &arg) { +inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') { if(arg.empty()) { - return std::string(2, '"'); + return std::string(2, stringQuote); } // some specifically supported strings if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { @@ -40,7 +40,7 @@ inline std::string convert_arg_for_ini(const std::string &arg) { } // just quote a single non numeric character if(arg.size() == 1) { - return std::string("'") + arg + '\''; + return std::string(1, characterQuote) + arg + characterQuote; } // handle hex, binary or octal arguments if(arg.front() == '0') { @@ -60,16 +60,20 @@ inline std::string convert_arg_for_ini(const std::string &arg) { } } } - if(arg.find_first_of('"') == std::string::npos) { - return std::string("\"") + arg + '"'; + if(arg.find_first_of(stringQuote) == std::string::npos) { + return std::string(1, stringQuote) + arg + stringQuote; } else { - return std::string("'") + arg + '\''; + return characterQuote + arg + characterQuote; } } /// Comma separated join, adds quotes if needed -inline std::string -ini_join(const std::vector &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') { +inline std::string ini_join(const std::vector &args, + char sepChar = ',', + char arrayStart = '[', + char arrayEnd = ']', + char stringQuote = '"', + char characterQuote = '\'') { std::string joined; if(args.size() > 1 && arrayStart != '\0') { joined.push_back(arrayStart); @@ -82,7 +86,7 @@ ini_join(const std::vector &args, char sepChar = ',', char arraySta joined.push_back(' '); } } - joined.append(convert_arg_for_ini(arg)); + joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); } if(args.size() > 1 && arrayEnd != '\0') { joined.push_back(arrayEnd); @@ -296,13 +300,16 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, } } std::string name = prefix + opt->get_single_name(); - std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd); + std::string value = detail::ini_join( + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); if(value.empty() && default_also) { if(!opt->get_default_str().empty()) { - value = detail::convert_arg_for_ini(opt->get_default_str()); + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); } else if(opt->get_expected_min() == 0) { value = "false"; + } else if(opt->get_run_callback_for_default()) { + value = "\"\""; // empty string default value } } diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 2d9f9a3..2fd255c 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -87,6 +87,10 @@ class ConfigBase : public Config { char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; public: std::string @@ -114,6 +118,12 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; + } }; /// the default Config is the TOML file format