1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-01-15 14:48:00 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Philip Top
ee2b725019
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
2021-06-16 21:58:03 -04:00
Henry Schreiner
c4f1fc8ea7
refactor!: remove add_complex (#600) 2021-06-16 20:33:22 -04:00
9 changed files with 97 additions and 206 deletions

View File

@ -23,6 +23,7 @@
* The final "defaulted" bool has been removed, use `->capture_default_str()`
instead. Use `app.option_defaults()->always_capture_default()` to set this for
all future options. [#597][]
* Use `add_option` on a complex number instead of `add_complex`, which has been removed.
[#435]: https://github.com/CLIUtils/CLI11/pull/435

View File

@ -231,8 +231,6 @@ app.add_option_function<type>(option_name,
function <void(const type &value)>, // type can be any type supported by add_option
help_string="")
app.add_complex(... // Special case: support for complex numbers ⚠️. Complex numbers are now fully supported in the add_option so this function is redundant.
// char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values.
// 🆕 There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
@ -758,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

View File

@ -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"

50
examples/config_app.cpp Normal file
View File

@ -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 <CLI/CLI.hpp>
#include <iostream>
#include <string>
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<bool>()) { // 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;
}

View File

@ -896,56 +896,6 @@ class App {
}
#endif
/// Add a complex number DEPRECATED --use add_option instead
template <typename T, typename XC = double>
Option *add_complex(std::string option_name,
T &variable,
std::string option_description = "",
bool defaulted = false,
std::string label = "COMPLEX") {
CLI::callback_t fun = [&variable](const results_t &res) {
XC x, y;
bool worked;
if(res.size() >= 2 && !res[1].empty()) {
auto str1 = res[1];
if(str1.back() == 'i' || str1.back() == 'j')
str1.pop_back();
worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(str1, y);
} else {
auto str1 = res.front();
auto nloc = str1.find_last_of('-');
if(nloc != std::string::npos && nloc > 0) {
worked = detail::lexical_cast(str1.substr(0, nloc), x);
str1 = str1.substr(nloc);
if(str1.back() == 'i' || str1.back() == 'j')
str1.pop_back();
worked = worked && detail::lexical_cast(str1, y);
} else {
if(str1.back() == 'i' || str1.back() == 'j') {
str1.pop_back();
worked = detail::lexical_cast(str1, y);
x = XC{0};
} else {
worked = detail::lexical_cast(str1, x);
y = XC{0};
}
}
}
if(worked)
variable = T{x, y};
return worked;
};
auto default_function = [&variable]() { return CLI::detail::checked_to_string<T, T>(variable); };
CLI::Option *opt =
add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function);
opt->type_name(label)->type_size(1, 2)->delimiter('+')->run_callback_for_default();
return opt;
}
/// Set a configuration ini file option, or clear it if no name passed
Option *set_config(std::string option_name = "",
std::string default_filename = "",

View File

@ -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<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') {
inline std::string ini_join(const std::vector<std::string> &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<std::string> &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
}
}

View File

@ -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

View File

@ -809,7 +809,7 @@ TEST_CASE_METHOD(TApp, "TakeFirstOptMulti", "[app]") {
TEST_CASE_METHOD(TApp, "ComplexOptMulti", "[app]") {
std::complex<double> val;
app.add_complex("--long", val)->take_first()->allow_extra_args();
app.add_option("--long", val)->take_first()->allow_extra_args();
args = {"--long", "1", "2", "3", "4"};

View File

@ -13,26 +13,6 @@ using Catch::Matchers::Contains;
using cx = std::complex<double>;
TEST_CASE_METHOD(TApp, "Complex", "[newparse]") {
cx comp{1, 2};
app.add_complex("-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str();
@ -53,26 +33,6 @@ TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") {
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexFloat", "[newparse]") {
std::complex<float> comp{1, 2};
app.add_complex<std::complex<float>, float>("-c,--complex", comp)->capture_default_str();
args = {"-c", "4", "3"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") {
std::complex<float> comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str();
@ -93,38 +53,6 @@ TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") {
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexWithDelimiter", "[newparse]") {
cx comp{1, 2};
app.add_complex("-c,--complex", comp)->capture_default_str()->delimiter('+');
args = {"-c", "4+3i"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
args = {"-c", "5+-3i"};
run();
CHECK(comp.real() == Approx(5));
CHECK(comp.imag() == Approx(-3));
args = {"-c", "6", "-4i"};
run();
CHECK(comp.real() == Approx(6));
CHECK(comp.imag() == Approx(-4));
}
TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str()->delimiter('+');
@ -157,18 +85,6 @@ TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") {
CHECK(comp.imag() == Approx(-4));
}
TEST_CASE_METHOD(TApp, "ComplexIgnoreI", "[newparse]") {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
args = {"-c", "4", "3i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
@ -181,40 +97,6 @@ TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") {
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexSingleArg", "[newparse]") {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
args = {"-c", "4"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(0));
args = {"-c", "4-2i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(-2));
args = {"-c", "4+2i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(2));
args = {"-c", "-4+2j"};
run();
CHECK(comp.real() == Approx(-4));
CHECK(comp.imag() == Approx(2));
args = {"-c", "-4.2-2j"};
run();
CHECK(comp.real() == Approx(-4.2));
CHECK(comp.imag() == Approx(-2));
args = {"-c", "-4.2-2.7i"};
run();
CHECK(comp.real() == Approx(-4.2));
CHECK(comp.imag() == Approx(-2.7));
}
TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
@ -249,29 +131,6 @@ TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") {
CHECK(comp.imag() == Approx(-2.7));
}
TEST_CASE_METHOD(TApp, "ComplexSingleImag", "[newparse]") {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
args = {"-c", "4j"};
run();
CHECK(comp.real() == Approx(0));
CHECK(comp.imag() == Approx(4));
args = {"-c", "-4j"};
run();
CHECK(comp.real() == Approx(0));
CHECK(comp.imag() == Approx(-4));
args = {"-c", "-4"};
run();
CHECK(comp.real() == Approx(-4));
CHECK(comp.imag() == Approx(0));
args = {"-c", "+4"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(0));
}
TEST_CASE_METHOD(TApp, "ComplexSingleImagOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);