Commit 76d2cde6568c9c8870b728aa9bc64b70b29127fd

Authored by Philip Top
Committed by Henry Schreiner
1 parent a1c18e05

Positional argument checks (#262)

* some tweaks with optional

* remove set_results function that was bypassing some of the result processing in some cases of config files.

* add positional Validator example and tests add CLI::Number validator.

* add positional Validator example and tests add CLI::Number validator.

* do some reformatting for style checks and remove auto in test lambda.
README.md
@@ -342,8 +342,9 @@ CLI11 has several Validators built-in that perform some common checks @@ -342,8 +342,9 @@ CLI11 has several Validators built-in that perform some common checks
342 - `CLI::ExistingPath`: Requires that the path (file or directory) exists. 342 - `CLI::ExistingPath`: Requires that the path (file or directory) exists.
343 - `CLI::NonexistentPath`: Requires that the path does not exist. 343 - `CLI::NonexistentPath`: Requires that the path does not exist.
344 - `CLI::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. 344 - `CLI::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
345 -- `CLI::Bounded(min,max)`: ๐Ÿšง Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an Error if conversion is not possible.  
346 -- `CLI::PositiveNumber`: ๐Ÿšง Requires the number be greater or equal to 0. 345 +- `CLI::Bounded(min,max)`: ๐Ÿšง Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an error if conversion is not possible.
  346 +- `CLI::PositiveNumber`: ๐Ÿšง Requires the number be greater or equal to 0
  347 +- `CLI::Number`: ๐Ÿšง Requires the input be a number.
347 - `CLI::ValidIPV4`: ๐Ÿšง Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. 348 - `CLI::ValidIPV4`: ๐Ÿšง Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
348 349
349 These Validators can be used by simply passing the name into the `check` or `transform` methods on an option 350 These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
@@ -467,6 +468,7 @@ There are several options that are supported on the main app and subcommands and @@ -467,6 +468,7 @@ There are several options that are supported on the main app and subcommands and
467 - `.disable()`: ๐Ÿšง Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. 468 - `.disable()`: ๐Ÿšง Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
468 - `.disabled_by_default()`:๐Ÿšง Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. 469 - `.disabled_by_default()`:๐Ÿšง Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
469 - `.enabled_by_default()`: ๐Ÿšง Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. 470 - `.enabled_by_default()`: ๐Ÿšง Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
  471 +- `.validate_positionals()`:๐Ÿšง Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
470 - `.excludes(option_or_subcommand)`: ๐Ÿšง If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. 472 - `.excludes(option_or_subcommand)`: ๐Ÿšง If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
471 - `.require_option()`: ๐Ÿšง Require 1 or more options or option groups be used. 473 - `.require_option()`: ๐Ÿšง Require 1 or more options or option groups be used.
472 - `.require_option(N)`: ๐Ÿšง Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. 474 - `.require_option(N)`: ๐Ÿšง Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
examples/CMakeLists.txt
@@ -109,6 +109,22 @@ add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two) @@ -109,6 +109,22 @@ add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two)
109 set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION 109 set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION
110 "Could not convert") 110 "Could not convert")
111 111
  112 + add_cli_exe(positional_validation positional_validation.cpp)
  113 +add_test(NAME positional_validation1 COMMAND positional_validation one )
  114 +set_property(TEST positional_validation1 PROPERTY PASS_REGULAR_EXPRESSION
  115 + "File 1 = one")
  116 +add_test(NAME positional_validation2 COMMAND positional_validation one 1 2 two )
  117 +set_property(TEST positional_validation2 PROPERTY PASS_REGULAR_EXPRESSION
  118 + "File 1 = one"
  119 + "File 2 = two")
  120 +add_test(NAME positional_validation3 COMMAND positional_validation 1 2 one)
  121 +set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION
  122 + "File 1 = one")
  123 +add_test(NAME positional_validation4 COMMAND positional_validation 1 one two 2)
  124 +set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION
  125 + "File 1 = one"
  126 + "File 2 = two")
  127 +
112 add_cli_exe(shapes shapes.cpp) 128 add_cli_exe(shapes shapes.cpp)
113 add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle 4.5 ++ rectangle 2.1 ++ circle 234.675) 129 add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle 4.5 ++ rectangle 2.1 ++ circle 234.675)
114 set_property(TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION 130 set_property(TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION
examples/positional_validation.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app("test for positional validation");
  6 +
  7 + int num1 = -1, num2 = -1;
  8 + app.add_option("num1", num1, "first number")->check(CLI::Number);
  9 + app.add_option("num2", num2, "second number")->check(CLI::Number);
  10 + std::string file1, file2;
  11 + app.add_option("file1", file1, "first file")->required();
  12 + app.add_option("file2", file2, "second file");
  13 + app.validate_positionals();
  14 +
  15 + CLI11_PARSE(app, argc, argv);
  16 +
  17 + if(num1 != -1)
  18 + std::cout << "Num1 = " << num1 << '\n';
  19 +
  20 + if(num2 != -1)
  21 + std::cout << "Num2 = " << num2 << '\n';
  22 +
  23 + std::cout << "File 1 = " << file1 << '\n';
  24 + if(!file2.empty()) {
  25 + std::cout << "File 2 = " << file2 << '\n';
  26 + }
  27 +
  28 + return 0;
  29 +}
include/CLI/App.hpp
@@ -187,7 +187,8 @@ class App { @@ -187,7 +187,8 @@ class App {
187 bool disabled_by_default_{false}; 187 bool disabled_by_default_{false};
188 /// If set to true the subcommand will be reenabled at the start of each parse 188 /// If set to true the subcommand will be reenabled at the start of each parse
189 bool enabled_by_default_{false}; 189 bool enabled_by_default_{false};
190 - 190 + /// If set to true positional options are validated before assigning INHERITABLE
  191 + bool validate_positionals_{false};
191 /// A pointer to the parent if this is a subcommand 192 /// A pointer to the parent if this is a subcommand
192 App *parent_{nullptr}; 193 App *parent_{nullptr};
193 194
@@ -250,6 +251,7 @@ class App { @@ -250,6 +251,7 @@ class App {
250 ignore_case_ = parent_->ignore_case_; 251 ignore_case_ = parent_->ignore_case_;
251 ignore_underscore_ = parent_->ignore_underscore_; 252 ignore_underscore_ = parent_->ignore_underscore_;
252 fallthrough_ = parent_->fallthrough_; 253 fallthrough_ = parent_->fallthrough_;
  254 + validate_positionals_ = parent_->validate_positionals_;
253 allow_windows_style_options_ = parent_->allow_windows_style_options_; 255 allow_windows_style_options_ = parent_->allow_windows_style_options_;
254 group_ = parent_->group_; 256 group_ = parent_->group_;
255 footer_ = parent_->footer_; 257 footer_ = parent_->footer_;
@@ -334,6 +336,12 @@ class App { @@ -334,6 +336,12 @@ class App {
334 return this; 336 return this;
335 } 337 }
336 338
  339 + /// Set the subcommand to validate positional arguments before assigning
  340 + App *validate_positionals(bool validate = true) {
  341 + validate_positionals_ = validate;
  342 + return this;
  343 + }
  344 +
337 /// Remove the error when extras are left over on the command line. 345 /// Remove the error when extras are left over on the command line.
338 /// Will also call App::allow_extras(). 346 /// Will also call App::allow_extras().
339 App *allow_config_extras(bool allow = true) { 347 App *allow_config_extras(bool allow = true) {
@@ -489,18 +497,19 @@ class App { @@ -489,18 +497,19 @@ class App {
489 return add_option(option_name, CLI::callback_t(), option_description, false); 497 return add_option(option_name, CLI::callback_t(), option_description, false);
490 } 498 }
491 499
492 - /// Add option for non-vectors with a default print 500 + /// Add option for non-vectors with a default print, allow template to specify conversion type
493 template <typename T, 501 template <typename T,
494 - enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> 502 + typename XC = T,
  503 + enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
495 Option *add_option(std::string option_name, 504 Option *add_option(std::string option_name,
496 T &variable, ///< The variable to set 505 T &variable, ///< The variable to set
497 std::string option_description, 506 std::string option_description,
498 bool defaulted) { 507 bool defaulted) {
499 -  
500 - CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; 508 + static_assert(std::is_constructible<T, XC>::value, "assign type must be assignable from conversion type");
  509 + CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast<XC>(res[0], variable); };
501 510
502 Option *opt = add_option(option_name, fun, option_description, defaulted); 511 Option *opt = add_option(option_name, fun, option_description, defaulted);
503 - opt->type_name(detail::type_name<T>()); 512 + opt->type_name(detail::type_name<XC>());
504 if(defaulted) { 513 if(defaulted) {
505 std::stringstream out; 514 std::stringstream out;
506 out << variable; 515 out << variable;
@@ -1654,6 +1663,8 @@ class App { @@ -1654,6 +1663,8 @@ class App {
1654 1663
1655 /// Get the status of disabled by default 1664 /// Get the status of disabled by default
1656 bool get_enabled_by_default() const { return enabled_by_default_; } 1665 bool get_enabled_by_default() const { return enabled_by_default_; }
  1666 + /// Get the status of validating positionals
  1667 + bool get_validate_positionals() const { return validate_positionals_; }
1657 1668
1658 /// Get the status of allow extras 1669 /// Get the status of allow extras
1659 bool get_allow_config_extras() const { return allow_config_extras_; } 1670 bool get_allow_config_extras() const { return allow_config_extras_; }
@@ -2192,7 +2203,7 @@ class App { @@ -2192,7 +2203,7 @@ class App {
2192 op->add_result(res); 2203 op->add_result(res);
2193 2204
2194 } else { 2205 } else {
2195 - op->set_results(item.inputs); 2206 + op->add_result(item.inputs);
2196 op->run_callback(); 2207 op->run_callback();
2197 } 2208 }
2198 } 2209 }
@@ -2274,7 +2285,13 @@ class App { @@ -2274,7 +2285,13 @@ class App {
2274 // Eat options, one by one, until done 2285 // Eat options, one by one, until done
2275 if(opt->get_positional() && 2286 if(opt->get_positional() &&
2276 (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { 2287 (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) {
2277 - 2288 + if(validate_positionals_) {
  2289 + std::string pos = positional;
  2290 + pos = opt->_validate(pos);
  2291 + if(!pos.empty()) {
  2292 + continue;
  2293 + }
  2294 + }
2278 opt->add_result(positional); 2295 opt->add_result(positional);
2279 parse_order_.push_back(opt.get()); 2296 parse_order_.push_back(opt.get());
2280 args.pop_back(); 2297 args.pop_back();
include/CLI/Option.hpp
@@ -666,19 +666,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -666,19 +666,11 @@ class Option : public OptionBase&lt;Option&gt; {
666 666
667 // Run the validators (can change the string) 667 // Run the validators (can change the string)
668 if(!validators_.empty()) { 668 if(!validators_.empty()) {
669 - for(std::string &result : results_)  
670 - for(const auto &vali : validators_) {  
671 - std::string err_msg;  
672 -  
673 - try {  
674 - err_msg = vali(result);  
675 - } catch(const ValidationError &err) {  
676 - throw ValidationError(get_name(), err.what());  
677 - }  
678 -  
679 - if(!err_msg.empty())  
680 - throw ValidationError(get_name(), err_msg);  
681 - } 669 + for(std::string &result : results_) {
  670 + auto err_msg = _validate(result);
  671 + if(!err_msg.empty())
  672 + throw ValidationError(get_name(), err_msg);
  673 + }
682 } 674 }
683 if(!(callback_)) { 675 if(!(callback_)) {
684 return; 676 return;
@@ -842,13 +834,6 @@ class Option : public OptionBase&lt;Option&gt; { @@ -842,13 +834,6 @@ class Option : public OptionBase&lt;Option&gt; {
842 return this; 834 return this;
843 } 835 }
844 836
845 - /// Set the results vector all at once  
846 - Option *set_results(std::vector<std::string> result_vector) {  
847 - results_ = std::move(result_vector);  
848 - callback_run_ = false;  
849 - return this;  
850 - }  
851 -  
852 /// Get a copy of the results 837 /// Get a copy of the results
853 std::vector<std::string> results() const { return results_; } 838 std::vector<std::string> results() const { return results_; }
854 839
@@ -963,6 +948,21 @@ class Option : public OptionBase&lt;Option&gt; { @@ -963,6 +948,21 @@ class Option : public OptionBase&lt;Option&gt; {
963 } 948 }
964 949
965 private: 950 private:
  951 + // run through the validators
  952 + std::string _validate(std::string &result) {
  953 + std::string err_msg;
  954 + for(const auto &vali : validators_) {
  955 + try {
  956 + err_msg = vali(result);
  957 + } catch(const ValidationError &err) {
  958 + err_msg = err.what();
  959 + }
  960 + if(!err_msg.empty())
  961 + break;
  962 + }
  963 + return err_msg;
  964 + }
  965 +
966 int _add_result(std::string &&result) { 966 int _add_result(std::string &&result) {
967 int result_count = 0; 967 int result_count = 0;
968 if(delimiter_ == '\0') { 968 if(delimiter_ == '\0') {
include/CLI/Validators.hpp
@@ -321,6 +321,20 @@ class PositiveNumber : public Validator { @@ -321,6 +321,20 @@ class PositiveNumber : public Validator {
321 } 321 }
322 }; 322 };
323 323
  324 +/// Validate the argument is a number and greater than or equal to 0
  325 +class Number : public Validator {
  326 + public:
  327 + Number() : Validator("NUMBER") {
  328 + func_ = [](std::string &number_str) {
  329 + double number;
  330 + if(!detail::lexical_cast(number_str, number)) {
  331 + return "Failed parsing as a number " + number_str;
  332 + }
  333 + return std::string();
  334 + };
  335 + }
  336 +};
  337 +
324 } // namespace detail 338 } // namespace detail
325 339
326 // Static is not needed here, because global const implies static. 340 // Static is not needed here, because global const implies static.
@@ -343,6 +357,9 @@ const detail::IPV4Validator ValidIPV4; @@ -343,6 +357,9 @@ const detail::IPV4Validator ValidIPV4;
343 /// Check for a positive number 357 /// Check for a positive number
344 const detail::PositiveNumber PositiveNumber; 358 const detail::PositiveNumber PositiveNumber;
345 359
  360 +/// Check for a number
  361 +const detail::Number Number;
  362 +
346 /// Produce a range (factory). Min and max are inclusive. 363 /// Produce a range (factory). Min and max are inclusive.
347 class Range : public Validator { 364 class Range : public Validator {
348 public: 365 public:
tests/AppTest.cpp
@@ -962,6 +962,27 @@ TEST_F(TApp, PositionalAtEnd) { @@ -962,6 +962,27 @@ TEST_F(TApp, PositionalAtEnd) {
962 EXPECT_THROW(run(), CLI::ExtrasError); 962 EXPECT_THROW(run(), CLI::ExtrasError);
963 } 963 }
964 964
  965 +// Tests positionals at end
  966 +TEST_F(TApp, PositionalValidation) {
  967 + std::string options;
  968 + std::string foo;
  969 +
  970 + app.add_option("bar", options)->check(CLI::Number);
  971 + app.add_option("foo", foo);
  972 + app.validate_positionals();
  973 + args = {"1", "param1"};
  974 + run();
  975 +
  976 + EXPECT_EQ(options, "1");
  977 + EXPECT_EQ(foo, "param1");
  978 +
  979 + args = {"param1", "1"};
  980 + run();
  981 +
  982 + EXPECT_EQ(options, "1");
  983 + EXPECT_EQ(foo, "param1");
  984 +}
  985 +
965 TEST_F(TApp, PositionalNoSpaceLong) { 986 TEST_F(TApp, PositionalNoSpaceLong) {
966 std::vector<std::string> options; 987 std::vector<std::string> options;
967 std::string foo, bar; 988 std::string foo, bar;
tests/CreationTest.cpp
@@ -467,7 +467,7 @@ TEST_F(TApp, GetNameCheck) { @@ -467,7 +467,7 @@ TEST_F(TApp, GetNameCheck) {
467 } 467 }
468 468
469 TEST_F(TApp, SubcommandDefaults) { 469 TEST_F(TApp, SubcommandDefaults) {
470 - // allow_extras, prefix_command, ignore_case, fallthrough, group, min/max subcommand 470 + // allow_extras, prefix_command, ignore_case, fallthrough, group, min/max subcommand, validate_positionals
471 471
472 // Initial defaults 472 // Initial defaults
473 EXPECT_FALSE(app.get_allow_extras()); 473 EXPECT_FALSE(app.get_allow_extras());
@@ -481,6 +481,8 @@ TEST_F(TApp, SubcommandDefaults) { @@ -481,6 +481,8 @@ TEST_F(TApp, SubcommandDefaults) {
481 EXPECT_FALSE(app.get_allow_windows_style_options()); 481 EXPECT_FALSE(app.get_allow_windows_style_options());
482 #endif 482 #endif
483 EXPECT_FALSE(app.get_fallthrough()); 483 EXPECT_FALSE(app.get_fallthrough());
  484 + EXPECT_FALSE(app.get_validate_positionals());
  485 +
484 EXPECT_EQ(app.get_footer(), ""); 486 EXPECT_EQ(app.get_footer(), "");
485 EXPECT_EQ(app.get_group(), "Subcommands"); 487 EXPECT_EQ(app.get_group(), "Subcommands");
486 EXPECT_EQ(app.get_require_subcommand_min(), 0u); 488 EXPECT_EQ(app.get_require_subcommand_min(), 0u);
@@ -498,6 +500,7 @@ TEST_F(TApp, SubcommandDefaults) { @@ -498,6 +500,7 @@ TEST_F(TApp, SubcommandDefaults) {
498 #endif 500 #endif
499 501
500 app.fallthrough(); 502 app.fallthrough();
  503 + app.validate_positionals();
501 app.footer("footy"); 504 app.footer("footy");
502 app.group("Stuff"); 505 app.group("Stuff");
503 app.require_subcommand(2, 3); 506 app.require_subcommand(2, 3);
@@ -516,6 +519,7 @@ TEST_F(TApp, SubcommandDefaults) { @@ -516,6 +519,7 @@ TEST_F(TApp, SubcommandDefaults) {
516 EXPECT_TRUE(app2->get_allow_windows_style_options()); 519 EXPECT_TRUE(app2->get_allow_windows_style_options());
517 #endif 520 #endif
518 EXPECT_TRUE(app2->get_fallthrough()); 521 EXPECT_TRUE(app2->get_fallthrough());
  522 + EXPECT_TRUE(app2->get_validate_positionals());
519 EXPECT_EQ(app2->get_footer(), "footy"); 523 EXPECT_EQ(app2->get_footer(), "footy");
520 EXPECT_EQ(app2->get_group(), "Stuff"); 524 EXPECT_EQ(app2->get_group(), "Stuff");
521 EXPECT_EQ(app2->get_require_subcommand_min(), 0u); 525 EXPECT_EQ(app2->get_require_subcommand_min(), 0u);
tests/HelpersTest.cpp
@@ -239,6 +239,21 @@ TEST(Validators, PositiveValidator) { @@ -239,6 +239,21 @@ TEST(Validators, PositiveValidator) {
239 EXPECT_FALSE(CLI::PositiveNumber(num).empty()); 239 EXPECT_FALSE(CLI::PositiveNumber(num).empty());
240 } 240 }
241 241
  242 +TEST(Validators, NumberValidator) {
  243 + std::string num = "1.1.1.1";
  244 + EXPECT_FALSE(CLI::Number(num).empty());
  245 + num = "1.7";
  246 + EXPECT_TRUE(CLI::Number(num).empty());
  247 + num = "10000";
  248 + EXPECT_TRUE(CLI::Number(num).empty());
  249 + num = "-0.000";
  250 + EXPECT_TRUE(CLI::Number(num).empty());
  251 + num = "+1.55";
  252 + EXPECT_TRUE(CLI::Number(num).empty());
  253 + num = "a";
  254 + EXPECT_FALSE(CLI::Number(num).empty());
  255 +}
  256 +
242 TEST(Validators, CombinedAndRange) { 257 TEST(Validators, CombinedAndRange) {
243 auto crange = CLI::Range(0, 12) & CLI::Range(4, 16); 258 auto crange = CLI::Range(0, 12) & CLI::Range(4, 16);
244 EXPECT_TRUE(crange("4").empty()); 259 EXPECT_TRUE(crange("4").empty());
tests/OptionalTest.cpp
@@ -62,6 +62,20 @@ TEST_F(TApp, BoostOptionalTest) { @@ -62,6 +62,20 @@ TEST_F(TApp, BoostOptionalTest) {
62 EXPECT_EQ(*opt, 3); 62 EXPECT_EQ(*opt, 3);
63 } 63 }
64 64
  65 +TEST_F(TApp, BoostOptionalVector) {
  66 + boost::optional<std::vector<int>> opt;
  67 + app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
  68 + ->expected(3);
  69 + run();
  70 + EXPECT_FALSE(opt);
  71 +
  72 + args = {"-v", "1", "4", "5"};
  73 + run();
  74 + EXPECT_TRUE(opt);
  75 + std::vector<int> expV{1, 4, 5};
  76 + EXPECT_EQ(*opt, expV);
  77 +}
  78 +
65 #endif 79 #endif
66 80
67 #if !CLI11_OPTIONAL 81 #if !CLI11_OPTIONAL