diff --git a/CHANGELOG.md b/CHANGELOG.md index e35ca386..7653f318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,16 @@ is not passed, or every time the option is parsed. [#656]: https://github.com/CLIUtils/CLI11/pull/656 [#657]: https://github.com/CLIUtils/CLI11/pull/657 +## Version 2.1.3: Bug Fixes and Tweaks + +* 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][] +* Fix a bug where a subcommand callback could be executed multiple times if it was a member of an option group [#666][] +* 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][] +* Add additional tests concerning the use of aliases for option groups in config files [#666][] +* Resolve strict-overflow warning on some GCC compilers [#666][] + +[#666]: https://github.com/CLIUtils/CLI11/pull/666 + ## Version 2.0: Simplification This version focuses on cleaning up deprecated functionality, and some minor diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c7557709..e0543f3a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -102,6 +102,7 @@ jobs: gcc9: containerImage: gcc:9 cli11.std: 17 + cli11.options: -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5" gcc11: containerImage: gcc:11 cli11.std: 20 diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 06737e98..1ef86a62 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -1213,8 +1213,8 @@ class App { } std::vector args; - args.reserve(static_cast(argc) - 1); - for(int i = argc - 1; i > 0; i--) + args.reserve(static_cast(argc) - 1U); + for(auto i = static_cast(argc) - 1U; i > 0U; --i) args.emplace_back(argv[i]); parse(std::move(args)); } @@ -1543,7 +1543,7 @@ class App { /// Access the config formatter as a configBase pointer std::shared_ptr get_config_formatter_base() const { // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase -#if defined(__cpp_rtti) || (defined(__GXX_RTTI) && __GXX_RTTI) || (defined(_HAS_STATIC_RTTI) && (_HAS_STATIC_RTTI == 0)) +#if CLI11_USE_STATIC_RTTI == 0 return std::dynamic_pointer_cast(config_formatter_); #else return std::static_pointer_cast(config_formatter_); @@ -1945,7 +1945,9 @@ class App { } // run the callbacks for the received subcommands for(App *subc : get_subcommands()) { - subc->run_callback(true, suppress_final_callback); + if(subc->parent_ == this) { + subc->run_callback(true, suppress_final_callback); + } } // now run callbacks for option_groups for(auto &subc : subcommands_) { diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index e45aa25d..5d78fca7 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -249,7 +249,7 @@ inline std::string Formatter::make_option_opts(const Option *opt) const { if(!opt->get_type_name().empty()) out << " " << get_label(opt->get_type_name()); if(!opt->get_default_str().empty()) - out << "=" << opt->get_default_str(); + out << " [" << opt->get_default_str() << "] "; if(opt->get_expected_max() == detail::expected_max_vector_size) out << " ..."; else if(opt->get_expected_min() > 1) diff --git a/include/CLI/Macros.hpp b/include/CLI/Macros.hpp index f228c918..d5c76d67 100644 --- a/include/CLI/Macros.hpp +++ b/include/CLI/Macros.hpp @@ -41,4 +41,20 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif // [CLI11:macros_hpp:end] diff --git a/tests/ConfigFileTest.cpp b/tests/ConfigFileTest.cpp index 6db466a0..658aae08 100644 --- a/tests/ConfigFileTest.cpp +++ b/tests/ConfigFileTest.cpp @@ -1216,6 +1216,30 @@ TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { CHECK(!*subcom); } +TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + out << "[ogroup]" << std::endl; + out << "val2=2" << std::endl; + } + int one{0}, two{0}; + app.add_option("--val", one); + auto subcom = app.add_option_group("ogroup")->alias("ogroup"); + subcom->add_option("--val2", two); + + run(); + + CHECK(one == 1); + CHECK(two == 2); +} + TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp index cb8c81ab..fcb84f73 100644 --- a/tests/HelpTest.cpp +++ b/tests/HelpTest.cpp @@ -289,7 +289,8 @@ TEST_CASE("THelp: VectorOpts", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("INT=[1,2] ...")); + CHECK_THAT(help, Contains("[1,2]")); + CHECK_THAT(help, Contains(" ...")); } TEST_CASE("THelp: MultiPosOpts", "[help]") { @@ -394,18 +395,18 @@ TEST_CASE("THelp: ManualSetters", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("=12")); + CHECK_THAT(help, Contains("[12]")); CHECK_THAT(help, Contains("BIGGLES")); op1->default_val("14"); CHECK(14 == x); help = app.help(); - CHECK_THAT(help, Contains("=14")); + CHECK_THAT(help, Contains("[14]")); op1->default_val(12); CHECK(12 == x); help = app.help(); - CHECK_THAT(help, Contains("=12")); + CHECK_THAT(help, Contains("[12]")); CHECK(op1->get_run_callback_for_default()); op1->run_callback_for_default(false); @@ -415,7 +416,7 @@ TEST_CASE("THelp: ManualSetters", "[help]") { // x should not be modified in this case CHECK(12 == x); help = app.help(); - CHECK_THAT(help, Contains("=18")); + CHECK_THAT(help, Contains("[18]")); } TEST_CASE("THelp: ManualSetterOverFunction", "[help]") { @@ -432,7 +433,7 @@ TEST_CASE("THelp: ManualSetterOverFunction", "[help]") { CHECK(1 == x); std::string help = app.help(); - CHECK_THAT(help, Contains("=12")); + CHECK_THAT(help, Contains("[12]")); CHECK_THAT(help, Contains("BIGGLES")); CHECK_THAT(help, Contains("QUIGGLES")); CHECK_THAT(help, Contains("{1,2}")); @@ -518,7 +519,7 @@ TEST_CASE("THelp: IntDefaults", "[help]") { CHECK_THAT(help, Contains("--one")); CHECK_THAT(help, Contains("--set")); CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, Contains("=2")); + CHECK_THAT(help, Contains("[2]")); CHECK_THAT(help, Contains("2,3,4")); } @@ -532,7 +533,7 @@ TEST_CASE("THelp: SetLower", "[help]") { std::string help = app.help(); CHECK_THAT(help, Contains("--set")); - CHECK_THAT(help, Contains("=One")); + CHECK_THAT(help, Contains("[One]")); CHECK_THAT(help, Contains("oNe")); CHECK_THAT(help, Contains("twO")); CHECK_THAT(help, Contains("THREE")); @@ -893,6 +894,14 @@ TEST_CASE("THelp: CheckEmptyTypeName", "[help]") { CHECK(name.empty()); } +TEST_CASE("THelp: FlagDefaults", "[help]") { + CLI::App app; + + app.add_flag("-t,--not{false}")->default_str("false"); + auto str = app.help(); + CHECK_THAT(str, Contains("--not{false}")); +} + TEST_CASE("THelp: AccessDescription", "[help]") { CLI::App app{"My description goes here"}; @@ -1162,7 +1171,9 @@ TEST_CASE("THelp: ChangingDefaults", "[help]") { x = {5, 6}; std::string help = app.help(); - CHECK_THAT(help, Contains("INT=[3,4] ...")); + CHECK_THAT(help, Contains("[[3,4]]")); + CHECK_THAT(help, Contains("...")); + CHECK_THAT(help, Contains("INT")); CHECK(x[0] == 5); } @@ -1179,7 +1190,8 @@ TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("INT=[1,2] ...")); + CHECK_THAT(help, Contains("[[1,2]]")); + CHECK_THAT(help, Contains("...")); } TEST_CASE("THelp: FunctionDefaultString", "[help]") { @@ -1194,7 +1206,7 @@ TEST_CASE("THelp: FunctionDefaultString", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("INT=Powerful")); + CHECK_THAT(help, Contains("[Powerful]")); } TEST_CASE("TVersion: simple_flag", "[help]") { diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index e1ffded6..fbf82a9f 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -1955,3 +1955,16 @@ TEST_CASE_METHOD(TApp, "MultiFinalCallbackCounts", "[subcom]") { CHECK(subsub_final == 1); } } + +// From gitter issue +TEST_CASE_METHOD(TApp, "SubcommandInOptionGroupCallbackCount", "[subcom]") { + + int subcount{0}; + auto group1 = app.add_option_group("FirstGroup"); + + group1->add_subcommand("g1c1")->callback([&subcount]() { ++subcount; }); + + args = {"g1c1"}; + run(); + CHECK(subcount == 1); +}