Commit 17e7d60c18473c3c74902c61b548f94b9db40c6a

Authored by Philip Top
Committed by GitHub
1 parent eba619fc

fix: several small fixes and added tests (#666)

* add a few tests related to github issues

* change how the default is displayed in the help message prev was =XXXX,  this was confusing in some cases particularly with flags or with multiple option names.    Now is [default=XXXX]  which makes it clearer what the value represents.

* Try to fix RTTI issue

* style: pre-commit.ci fixes

* Fix subcommand callbacks being called multiple times if in an option group

* style: pre-commit.ci fixes

* remove extra group call

* change [default=XXXXD] to just [XXXXX] for the default specification

* update changelog

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
CHANGELOG.md
@@ -45,6 +45,16 @@ is not passed, or every time the option is parsed. @@ -45,6 +45,16 @@ is not passed, or every time the option is parsed.
45 [#656]: https://github.com/CLIUtils/CLI11/pull/656 45 [#656]: https://github.com/CLIUtils/CLI11/pull/656
46 [#657]: https://github.com/CLIUtils/CLI11/pull/657 46 [#657]: https://github.com/CLIUtils/CLI11/pull/657
47 47
  48 +## Version 2.1.3: Bug Fixes and Tweaks
  49 +
  50 +* Change the way the default value is displayed in the included help text generation from `=XXXXX` to `[XXXXX]` to clean up some situations in which the text looked awkward and unclear [#666][]
  51 +* Fix a bug where a subcommand callback could be executed multiple times if it was a member of an option group [#666][]
  52 +* Fix an issue where the detection of RTTI being disabled on certain visual studio platforms did not disable the use of dynamic cast calls [#666][]
  53 +* Add additional tests concerning the use of aliases for option groups in config files [#666][]
  54 +* Resolve strict-overflow warning on some GCC compilers [#666][]
  55 +
  56 +[#666]: https://github.com/CLIUtils/CLI11/pull/666
  57 +
48 ## Version 2.0: Simplification 58 ## Version 2.0: Simplification
49 59
50 This version focuses on cleaning up deprecated functionality, and some minor 60 This version focuses on cleaning up deprecated functionality, and some minor
azure-pipelines.yml
@@ -102,6 +102,7 @@ jobs: @@ -102,6 +102,7 @@ jobs:
102 gcc9: 102 gcc9:
103 containerImage: gcc:9 103 containerImage: gcc:9
104 cli11.std: 17 104 cli11.std: 17
  105 + cli11.options: -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5"
105 gcc11: 106 gcc11:
106 containerImage: gcc:11 107 containerImage: gcc:11
107 cli11.std: 20 108 cli11.std: 20
include/CLI/App.hpp
@@ -1213,8 +1213,8 @@ class App { @@ -1213,8 +1213,8 @@ class App {
1213 } 1213 }
1214 1214
1215 std::vector<std::string> args; 1215 std::vector<std::string> args;
1216 - args.reserve(static_cast<std::size_t>(argc) - 1);  
1217 - for(int i = argc - 1; i > 0; i--) 1216 + args.reserve(static_cast<std::size_t>(argc) - 1U);
  1217 + for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i)
1218 args.emplace_back(argv[i]); 1218 args.emplace_back(argv[i]);
1219 parse(std::move(args)); 1219 parse(std::move(args));
1220 } 1220 }
@@ -1543,7 +1543,7 @@ class App { @@ -1543,7 +1543,7 @@ class App {
1543 /// Access the config formatter as a configBase pointer 1543 /// Access the config formatter as a configBase pointer
1544 std::shared_ptr<ConfigBase> get_config_formatter_base() const { 1544 std::shared_ptr<ConfigBase> get_config_formatter_base() const {
1545 // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase 1545 // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase
1546 -#if defined(__cpp_rtti) || (defined(__GXX_RTTI) && __GXX_RTTI) || (defined(_HAS_STATIC_RTTI) && (_HAS_STATIC_RTTI == 0)) 1546 +#if CLI11_USE_STATIC_RTTI == 0
1547 return std::dynamic_pointer_cast<ConfigBase>(config_formatter_); 1547 return std::dynamic_pointer_cast<ConfigBase>(config_formatter_);
1548 #else 1548 #else
1549 return std::static_pointer_cast<ConfigBase>(config_formatter_); 1549 return std::static_pointer_cast<ConfigBase>(config_formatter_);
@@ -1945,7 +1945,9 @@ class App { @@ -1945,7 +1945,9 @@ class App {
1945 } 1945 }
1946 // run the callbacks for the received subcommands 1946 // run the callbacks for the received subcommands
1947 for(App *subc : get_subcommands()) { 1947 for(App *subc : get_subcommands()) {
1948 - subc->run_callback(true, suppress_final_callback); 1948 + if(subc->parent_ == this) {
  1949 + subc->run_callback(true, suppress_final_callback);
  1950 + }
1949 } 1951 }
1950 // now run callbacks for option_groups 1952 // now run callbacks for option_groups
1951 for(auto &subc : subcommands_) { 1953 for(auto &subc : subcommands_) {
include/CLI/Formatter.hpp
@@ -249,7 +249,7 @@ inline std::string Formatter::make_option_opts(const Option *opt) const { @@ -249,7 +249,7 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
249 if(!opt->get_type_name().empty()) 249 if(!opt->get_type_name().empty())
250 out << " " << get_label(opt->get_type_name()); 250 out << " " << get_label(opt->get_type_name());
251 if(!opt->get_default_str().empty()) 251 if(!opt->get_default_str().empty())
252 - out << "=" << opt->get_default_str(); 252 + out << " [" << opt->get_default_str() << "] ";
253 if(opt->get_expected_max() == detail::expected_max_vector_size) 253 if(opt->get_expected_max() == detail::expected_max_vector_size)
254 out << " ..."; 254 out << " ...";
255 else if(opt->get_expected_min() > 1) 255 else if(opt->get_expected_min() > 1)
include/CLI/Macros.hpp
@@ -41,4 +41,20 @@ @@ -41,4 +41,20 @@
41 #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) 41 #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
42 #endif 42 #endif
43 43
  44 +/** detection of rtti */
  45 +#ifndef CLI11_USE_STATIC_RTTI
  46 +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
  47 +#define CLI11_USE_STATIC_RTTI 1
  48 +#elif defined(__cpp_rtti)
  49 +#if(defined(_CPPRTTI) && _CPPRTTI == 0)
  50 +#define CLI11_USE_STATIC_RTTI 1
  51 +#else
  52 +#define CLI11_USE_STATIC_RTTI 0
  53 +#endif
  54 +#elif(defined(__GCC_RTTI) && __GXX_RTTI)
  55 +#define CLI11_USE_STATIC_RTTI 0
  56 +#else
  57 +#define CLI11_USE_STATIC_RTTI 1
  58 +#endif
  59 +#endif
44 // [CLI11:macros_hpp:end] 60 // [CLI11:macros_hpp:end]
tests/ConfigFileTest.cpp
@@ -1216,6 +1216,30 @@ TEST_CASE_METHOD(TApp, &quot;IniLayeredCustomSectionSeparator&quot;, &quot;[config]&quot;) { @@ -1216,6 +1216,30 @@ TEST_CASE_METHOD(TApp, &quot;IniLayeredCustomSectionSeparator&quot;, &quot;[config]&quot;) {
1216 CHECK(!*subcom); 1216 CHECK(!*subcom);
1217 } 1217 }
1218 1218
  1219 +TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") {
  1220 +
  1221 + TempFile tmpini{"TestIniTmp.ini"};
  1222 +
  1223 + app.set_config("--config", tmpini);
  1224 +
  1225 + {
  1226 + std::ofstream out{tmpini};
  1227 + out << "[default]" << std::endl;
  1228 + out << "val=1" << std::endl;
  1229 + out << "[ogroup]" << std::endl;
  1230 + out << "val2=2" << std::endl;
  1231 + }
  1232 + int one{0}, two{0};
  1233 + app.add_option("--val", one);
  1234 + auto subcom = app.add_option_group("ogroup")->alias("ogroup");
  1235 + subcom->add_option("--val2", two);
  1236 +
  1237 + run();
  1238 +
  1239 + CHECK(one == 1);
  1240 + CHECK(two == 2);
  1241 +}
  1242 +
1219 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { 1243 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") {
1220 1244
1221 TempFile tmpini{"TestIniTmp.ini"}; 1245 TempFile tmpini{"TestIniTmp.ini"};
tests/HelpTest.cpp
@@ -289,7 +289,8 @@ TEST_CASE(&quot;THelp: VectorOpts&quot;, &quot;[help]&quot;) { @@ -289,7 +289,8 @@ TEST_CASE(&quot;THelp: VectorOpts&quot;, &quot;[help]&quot;) {
289 289
290 std::string help = app.help(); 290 std::string help = app.help();
291 291
292 - CHECK_THAT(help, Contains("INT=[1,2] ...")); 292 + CHECK_THAT(help, Contains("[1,2]"));
  293 + CHECK_THAT(help, Contains(" ..."));
293 } 294 }
294 295
295 TEST_CASE("THelp: MultiPosOpts", "[help]") { 296 TEST_CASE("THelp: MultiPosOpts", "[help]") {
@@ -394,18 +395,18 @@ TEST_CASE(&quot;THelp: ManualSetters&quot;, &quot;[help]&quot;) { @@ -394,18 +395,18 @@ TEST_CASE(&quot;THelp: ManualSetters&quot;, &quot;[help]&quot;) {
394 395
395 std::string help = app.help(); 396 std::string help = app.help();
396 397
397 - CHECK_THAT(help, Contains("=12")); 398 + CHECK_THAT(help, Contains("[12]"));
398 CHECK_THAT(help, Contains("BIGGLES")); 399 CHECK_THAT(help, Contains("BIGGLES"));
399 400
400 op1->default_val("14"); 401 op1->default_val("14");
401 CHECK(14 == x); 402 CHECK(14 == x);
402 help = app.help(); 403 help = app.help();
403 - CHECK_THAT(help, Contains("=14")); 404 + CHECK_THAT(help, Contains("[14]"));
404 405
405 op1->default_val(12); 406 op1->default_val(12);
406 CHECK(12 == x); 407 CHECK(12 == x);
407 help = app.help(); 408 help = app.help();
408 - CHECK_THAT(help, Contains("=12")); 409 + CHECK_THAT(help, Contains("[12]"));
409 410
410 CHECK(op1->get_run_callback_for_default()); 411 CHECK(op1->get_run_callback_for_default());
411 op1->run_callback_for_default(false); 412 op1->run_callback_for_default(false);
@@ -415,7 +416,7 @@ TEST_CASE(&quot;THelp: ManualSetters&quot;, &quot;[help]&quot;) { @@ -415,7 +416,7 @@ TEST_CASE(&quot;THelp: ManualSetters&quot;, &quot;[help]&quot;) {
415 // x should not be modified in this case 416 // x should not be modified in this case
416 CHECK(12 == x); 417 CHECK(12 == x);
417 help = app.help(); 418 help = app.help();
418 - CHECK_THAT(help, Contains("=18")); 419 + CHECK_THAT(help, Contains("[18]"));
419 } 420 }
420 421
421 TEST_CASE("THelp: ManualSetterOverFunction", "[help]") { 422 TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
@@ -432,7 +433,7 @@ TEST_CASE(&quot;THelp: ManualSetterOverFunction&quot;, &quot;[help]&quot;) { @@ -432,7 +433,7 @@ TEST_CASE(&quot;THelp: ManualSetterOverFunction&quot;, &quot;[help]&quot;) {
432 CHECK(1 == x); 433 CHECK(1 == x);
433 434
434 std::string help = app.help(); 435 std::string help = app.help();
435 - CHECK_THAT(help, Contains("=12")); 436 + CHECK_THAT(help, Contains("[12]"));
436 CHECK_THAT(help, Contains("BIGGLES")); 437 CHECK_THAT(help, Contains("BIGGLES"));
437 CHECK_THAT(help, Contains("QUIGGLES")); 438 CHECK_THAT(help, Contains("QUIGGLES"));
438 CHECK_THAT(help, Contains("{1,2}")); 439 CHECK_THAT(help, Contains("{1,2}"));
@@ -518,7 +519,7 @@ TEST_CASE(&quot;THelp: IntDefaults&quot;, &quot;[help]&quot;) { @@ -518,7 +519,7 @@ TEST_CASE(&quot;THelp: IntDefaults&quot;, &quot;[help]&quot;) {
518 CHECK_THAT(help, Contains("--one")); 519 CHECK_THAT(help, Contains("--one"));
519 CHECK_THAT(help, Contains("--set")); 520 CHECK_THAT(help, Contains("--set"));
520 CHECK_THAT(help, Contains("1")); 521 CHECK_THAT(help, Contains("1"));
521 - CHECK_THAT(help, Contains("=2")); 522 + CHECK_THAT(help, Contains("[2]"));
522 CHECK_THAT(help, Contains("2,3,4")); 523 CHECK_THAT(help, Contains("2,3,4"));
523 } 524 }
524 525
@@ -532,7 +533,7 @@ TEST_CASE(&quot;THelp: SetLower&quot;, &quot;[help]&quot;) { @@ -532,7 +533,7 @@ TEST_CASE(&quot;THelp: SetLower&quot;, &quot;[help]&quot;) {
532 std::string help = app.help(); 533 std::string help = app.help();
533 534
534 CHECK_THAT(help, Contains("--set")); 535 CHECK_THAT(help, Contains("--set"));
535 - CHECK_THAT(help, Contains("=One")); 536 + CHECK_THAT(help, Contains("[One]"));
536 CHECK_THAT(help, Contains("oNe")); 537 CHECK_THAT(help, Contains("oNe"));
537 CHECK_THAT(help, Contains("twO")); 538 CHECK_THAT(help, Contains("twO"));
538 CHECK_THAT(help, Contains("THREE")); 539 CHECK_THAT(help, Contains("THREE"));
@@ -893,6 +894,14 @@ TEST_CASE(&quot;THelp: CheckEmptyTypeName&quot;, &quot;[help]&quot;) { @@ -893,6 +894,14 @@ TEST_CASE(&quot;THelp: CheckEmptyTypeName&quot;, &quot;[help]&quot;) {
893 CHECK(name.empty()); 894 CHECK(name.empty());
894 } 895 }
895 896
  897 +TEST_CASE("THelp: FlagDefaults", "[help]") {
  898 + CLI::App app;
  899 +
  900 + app.add_flag("-t,--not{false}")->default_str("false");
  901 + auto str = app.help();
  902 + CHECK_THAT(str, Contains("--not{false}"));
  903 +}
  904 +
896 TEST_CASE("THelp: AccessDescription", "[help]") { 905 TEST_CASE("THelp: AccessDescription", "[help]") {
897 CLI::App app{"My description goes here"}; 906 CLI::App app{"My description goes here"};
898 907
@@ -1162,7 +1171,9 @@ TEST_CASE(&quot;THelp: ChangingDefaults&quot;, &quot;[help]&quot;) { @@ -1162,7 +1171,9 @@ TEST_CASE(&quot;THelp: ChangingDefaults&quot;, &quot;[help]&quot;) {
1162 x = {5, 6}; 1171 x = {5, 6};
1163 std::string help = app.help(); 1172 std::string help = app.help();
1164 1173
1165 - CHECK_THAT(help, Contains("INT=[3,4] ...")); 1174 + CHECK_THAT(help, Contains("[[3,4]]"));
  1175 + CHECK_THAT(help, Contains("..."));
  1176 + CHECK_THAT(help, Contains("INT"));
1166 CHECK(x[0] == 5); 1177 CHECK(x[0] == 5);
1167 } 1178 }
1168 1179
@@ -1179,7 +1190,8 @@ TEST_CASE(&quot;THelp: ChangingDefaultsWithAutoCapture&quot;, &quot;[help]&quot;) { @@ -1179,7 +1190,8 @@ TEST_CASE(&quot;THelp: ChangingDefaultsWithAutoCapture&quot;, &quot;[help]&quot;) {
1179 1190
1180 std::string help = app.help(); 1191 std::string help = app.help();
1181 1192
1182 - CHECK_THAT(help, Contains("INT=[1,2] ...")); 1193 + CHECK_THAT(help, Contains("[[1,2]]"));
  1194 + CHECK_THAT(help, Contains("..."));
1183 } 1195 }
1184 1196
1185 TEST_CASE("THelp: FunctionDefaultString", "[help]") { 1197 TEST_CASE("THelp: FunctionDefaultString", "[help]") {
@@ -1194,7 +1206,7 @@ TEST_CASE(&quot;THelp: FunctionDefaultString&quot;, &quot;[help]&quot;) { @@ -1194,7 +1206,7 @@ TEST_CASE(&quot;THelp: FunctionDefaultString&quot;, &quot;[help]&quot;) {
1194 1206
1195 std::string help = app.help(); 1207 std::string help = app.help();
1196 1208
1197 - CHECK_THAT(help, Contains("INT=Powerful")); 1209 + CHECK_THAT(help, Contains("[Powerful]"));
1198 } 1210 }
1199 1211
1200 TEST_CASE("TVersion: simple_flag", "[help]") { 1212 TEST_CASE("TVersion: simple_flag", "[help]") {
tests/SubcommandTest.cpp
@@ -1955,3 +1955,16 @@ TEST_CASE_METHOD(TApp, &quot;MultiFinalCallbackCounts&quot;, &quot;[subcom]&quot;) { @@ -1955,3 +1955,16 @@ TEST_CASE_METHOD(TApp, &quot;MultiFinalCallbackCounts&quot;, &quot;[subcom]&quot;) {
1955 CHECK(subsub_final == 1); 1955 CHECK(subsub_final == 1);
1956 } 1956 }
1957 } 1957 }
  1958 +
  1959 +// From gitter issue
  1960 +TEST_CASE_METHOD(TApp, "SubcommandInOptionGroupCallbackCount", "[subcom]") {
  1961 +
  1962 + int subcount{0};
  1963 + auto group1 = app.add_option_group("FirstGroup");
  1964 +
  1965 + group1->add_subcommand("g1c1")->callback([&subcount]() { ++subcount; });
  1966 +
  1967 + args = {"g1c1"};
  1968 + run();
  1969 + CHECK(subcount == 1);
  1970 +}