Commit 41a9c294d0cfb51efad2ca875e6c994911b5bb5c
Committed by
GitHub
1 parent
fff33502
Version add (#452)
* Add a dedicated version option to CLI11 to facilitate use of version flags, similar to help flags * add some test for the version flag * update errors and formatting * clear up gcc 4.8 warnings * add a few more tests * fix compiler error * fix a few comments, and change default version flag to only use "--version" * remove `version` calls and tests * formatting and add `std::string version()` back in.
Showing
7 changed files
with
140 additions
and
9 deletions
examples/CMakeLists.txt
| ... | ... | @@ -54,6 +54,9 @@ set_property(TEST simple_all PROPERTY PASS_REGULAR_EXPRESSION |
| 54 | 54 | "Received flag: 2 (2) times" |
| 55 | 55 | "Some value: 1.2") |
| 56 | 56 | |
| 57 | +add_test(NAME simple_version COMMAND simple --version) | |
| 58 | +set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION | |
| 59 | + "${CLI11_VERSION}") | |
| 57 | 60 | |
| 58 | 61 | add_cli_exe(subcommands subcommands.cpp) |
| 59 | 62 | add_test(NAME subcommands_none COMMAND subcommands) | ... | ... |
examples/simple.cpp
| ... | ... | @@ -11,7 +11,8 @@ |
| 11 | 11 | int main(int argc, char **argv) { |
| 12 | 12 | |
| 13 | 13 | CLI::App app("K3Pi goofit fitter"); |
| 14 | - | |
| 14 | + // add version output | |
| 15 | + app.set_version_flag("--version", std::string(CLI11_VERSION)); | |
| 15 | 16 | std::string file; |
| 16 | 17 | CLI::Option *opt = app.add_option("-f,--file,file", file, "File name"); |
| 17 | 18 | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -139,6 +139,9 @@ class App { |
| 139 | 139 | /// A pointer to the help all flag if there is one INHERITABLE |
| 140 | 140 | Option *help_all_ptr_{nullptr}; |
| 141 | 141 | |
| 142 | + /// A pointer to a version flag if there is one | |
| 143 | + Option *version_ptr_{nullptr}; | |
| 144 | + | |
| 142 | 145 | /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) |
| 143 | 146 | std::shared_ptr<FormatterBase> formatter_{new Formatter()}; |
| 144 | 147 | |
| ... | ... | @@ -703,6 +706,45 @@ class App { |
| 703 | 706 | return help_all_ptr_; |
| 704 | 707 | } |
| 705 | 708 | |
| 709 | + /// Set a version flag and version display string, replace the existing one if present | |
| 710 | + Option *set_version_flag(std::string flag_name = "", const std::string &versionString = "") { | |
| 711 | + // take flag_description by const reference otherwise add_flag tries to assign to version_description | |
| 712 | + if(version_ptr_ != nullptr) { | |
| 713 | + remove_option(version_ptr_); | |
| 714 | + version_ptr_ = nullptr; | |
| 715 | + } | |
| 716 | + | |
| 717 | + // Empty name will simply remove the version flag | |
| 718 | + if(!flag_name.empty()) { | |
| 719 | + version_ptr_ = add_flag_callback( | |
| 720 | + flag_name, | |
| 721 | + [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, | |
| 722 | + "display program version information and exit"); | |
| 723 | + version_ptr_->configurable(false); | |
| 724 | + } | |
| 725 | + | |
| 726 | + return version_ptr_; | |
| 727 | + } | |
| 728 | + /// Generate the version string through a callback function | |
| 729 | + Option *set_version_flag(std::string flag_name, std::function<std::string()> vfunc) { | |
| 730 | + // take flag_description by const reference otherwise add_flag tries to assign to version_description | |
| 731 | + if(version_ptr_ != nullptr) { | |
| 732 | + remove_option(version_ptr_); | |
| 733 | + version_ptr_ = nullptr; | |
| 734 | + } | |
| 735 | + | |
| 736 | + // Empty name will simply remove the version flag | |
| 737 | + if(!flag_name.empty()) { | |
| 738 | + version_ptr_ = add_flag_callback( | |
| 739 | + flag_name, | |
| 740 | + [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, | |
| 741 | + "display program version information and exit"); | |
| 742 | + version_ptr_->configurable(false); | |
| 743 | + } | |
| 744 | + | |
| 745 | + return version_ptr_; | |
| 746 | + } | |
| 747 | + | |
| 706 | 748 | private: |
| 707 | 749 | /// Internal function for adding a flag |
| 708 | 750 | Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { |
| ... | ... | @@ -1345,6 +1387,11 @@ class App { |
| 1345 | 1387 | return e.get_exit_code(); |
| 1346 | 1388 | } |
| 1347 | 1389 | |
| 1390 | + if(dynamic_cast<const CLI::CallForVersion *>(&e) != nullptr) { | |
| 1391 | + out << e.what() << std::endl; | |
| 1392 | + return e.get_exit_code(); | |
| 1393 | + } | |
| 1394 | + | |
| 1348 | 1395 | if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) { |
| 1349 | 1396 | if(failure_message_) |
| 1350 | 1397 | err << failure_message_(this, e) << std::flush; |
| ... | ... | @@ -1530,6 +1577,23 @@ class App { |
| 1530 | 1577 | return formatter_->make_help(this, prev, mode); |
| 1531 | 1578 | } |
| 1532 | 1579 | |
| 1580 | + /// Displays a version string | |
| 1581 | + std::string version() const { | |
| 1582 | + std::string val; | |
| 1583 | + if(version_ptr_ != nullptr) { | |
| 1584 | + auto rv = version_ptr_->results(); | |
| 1585 | + version_ptr_->clear(); | |
| 1586 | + version_ptr_->add_result("true"); | |
| 1587 | + try { | |
| 1588 | + version_ptr_->run_callback(); | |
| 1589 | + } catch(const CLI::CallForVersion &cfv) { | |
| 1590 | + val = cfv.what(); | |
| 1591 | + } | |
| 1592 | + version_ptr_->clear(); | |
| 1593 | + version_ptr_->add_result(rv); | |
| 1594 | + } | |
| 1595 | + return val; | |
| 1596 | + } | |
| 1533 | 1597 | ///@} |
| 1534 | 1598 | /// @name Getters |
| 1535 | 1599 | ///@{ |
| ... | ... | @@ -1726,6 +1790,12 @@ class App { |
| 1726 | 1790 | /// Get a pointer to the config option. (const) |
| 1727 | 1791 | const Option *get_config_ptr() const { return config_ptr_; } |
| 1728 | 1792 | |
| 1793 | + /// Get a pointer to the version option. | |
| 1794 | + Option *get_version_ptr() { return version_ptr_; } | |
| 1795 | + | |
| 1796 | + /// Get a pointer to the version option. (const) | |
| 1797 | + const Option *get_version_ptr() const { return version_ptr_; } | |
| 1798 | + | |
| 1729 | 1799 | /// Get the parent of this subcommand (or nullptr if master app) |
| 1730 | 1800 | App *get_parent() { return parent_; } |
| 1731 | 1801 | ... | ... |
include/CLI/Error.hpp
| ... | ... | @@ -157,18 +157,25 @@ class Success : public ParseError { |
| 157 | 157 | }; |
| 158 | 158 | |
| 159 | 159 | /// -h or --help on command line |
| 160 | -class CallForHelp : public ParseError { | |
| 161 | - CLI11_ERROR_DEF(ParseError, CallForHelp) | |
| 160 | +class CallForHelp : public Success { | |
| 161 | + CLI11_ERROR_DEF(Success, CallForHelp) | |
| 162 | 162 | CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} |
| 163 | 163 | }; |
| 164 | 164 | |
| 165 | 165 | /// Usually something like --help-all on command line |
| 166 | -class CallForAllHelp : public ParseError { | |
| 167 | - CLI11_ERROR_DEF(ParseError, CallForAllHelp) | |
| 166 | +class CallForAllHelp : public Success { | |
| 167 | + CLI11_ERROR_DEF(Success, CallForAllHelp) | |
| 168 | 168 | CallForAllHelp() |
| 169 | 169 | : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} |
| 170 | 170 | }; |
| 171 | 171 | |
| 172 | +/// -v or --version on command line | |
| 173 | +class CallForVersion : public Success { | |
| 174 | + CLI11_ERROR_DEF(Success, CallForVersion) | |
| 175 | + CallForVersion() | |
| 176 | + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} | |
| 177 | +}; | |
| 178 | + | |
| 172 | 179 | /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. |
| 173 | 180 | class RuntimeError : public ParseError { |
| 174 | 181 | CLI11_ERROR_DEF(ParseError, RuntimeError) | ... | ... |
include/CLI/StringTools.hpp
| ... | ... | @@ -190,7 +190,7 @@ inline bool valid_name_string(const std::string &str) { |
| 190 | 190 | return true; |
| 191 | 191 | } |
| 192 | 192 | |
| 193 | -/// check if a string is a container segment separator (empty or "%%" | |
| 193 | +/// check if a string is a container segment separator (empty or "%%") | |
| 194 | 194 | inline bool is_separator(const std::string &str) { |
| 195 | 195 | static const std::string sep("%%"); |
| 196 | 196 | return (str.empty() || str == sep); | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -232,7 +232,7 @@ struct is_mutable_container< |
| 232 | 232 | // check to see if an object is a mutable container (fail by default) |
| 233 | 233 | template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; |
| 234 | 234 | |
| 235 | -/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end | |
| 235 | +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end | |
| 236 | 236 | /// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from |
| 237 | 237 | /// a std::string |
| 238 | 238 | template <typename T> |
| ... | ... | @@ -244,7 +244,7 @@ struct is_readable_container< |
| 244 | 244 | // check to see if an object is a wrapper (fail by default) |
| 245 | 245 | template <typename T, typename _ = void> struct is_wrapper : std::false_type {}; |
| 246 | 246 | |
| 247 | -// check if an object is a is a wrapper (it has a value_type defined) | |
| 247 | +// check if an object is a wrapper (it has a value_type defined) | |
| 248 | 248 | template <typename T> |
| 249 | 249 | struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {}; |
| 250 | 250 | |
| ... | ... | @@ -344,7 +344,7 @@ auto value_string(const T &value) -> decltype(to_string(value)) { |
| 344 | 344 | return to_string(value); |
| 345 | 345 | } |
| 346 | 346 | |
| 347 | -/// temple to get the underlying value type if it exists or use a default | |
| 347 | +/// template to get the underlying value type if it exists or use a default | |
| 348 | 348 | template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; }; |
| 349 | 349 | |
| 350 | 350 | /// Type size for regular object types that do not look like a tuple | ... | ... |
tests/HelpTest.cpp
| ... | ... | @@ -1165,3 +1165,53 @@ TEST(THelp, FunctionDefaultString) { |
| 1165 | 1165 | |
| 1166 | 1166 | EXPECT_THAT(help, HasSubstr("INT=Powerful")); |
| 1167 | 1167 | } |
| 1168 | + | |
| 1169 | +TEST(TVersion, simple_flag) { | |
| 1170 | + | |
| 1171 | + CLI::App app; | |
| 1172 | + | |
| 1173 | + app.set_version_flag("-v,--version", "VERSION " CLI11_VERSION); | |
| 1174 | + | |
| 1175 | + auto vers = app.version(); | |
| 1176 | + EXPECT_THAT(vers, HasSubstr("VERSION")); | |
| 1177 | + | |
| 1178 | + app.set_version_flag(); | |
| 1179 | + EXPECT_TRUE(app.version().empty()); | |
| 1180 | +} | |
| 1181 | + | |
| 1182 | +TEST(TVersion, callback_flag) { | |
| 1183 | + | |
| 1184 | + CLI::App app; | |
| 1185 | + | |
| 1186 | + app.set_version_flag("-v,--version", []() { return std::string("VERSION " CLI11_VERSION); }); | |
| 1187 | + | |
| 1188 | + auto vers = app.version(); | |
| 1189 | + EXPECT_THAT(vers, HasSubstr("VERSION")); | |
| 1190 | + | |
| 1191 | + app.set_version_flag("-v", []() { return std::string("VERSION2 " CLI11_VERSION); }); | |
| 1192 | + vers = app.version(); | |
| 1193 | + EXPECT_THAT(vers, HasSubstr("VERSION")); | |
| 1194 | +} | |
| 1195 | + | |
| 1196 | +TEST(TVersion, parse_throw) { | |
| 1197 | + | |
| 1198 | + CLI::App app; | |
| 1199 | + | |
| 1200 | + app.set_version_flag("--version", CLI11_VERSION); | |
| 1201 | + | |
| 1202 | + EXPECT_THROW(app.parse("--version"), CLI::CallForVersion); | |
| 1203 | + EXPECT_THROW(app.parse("--version --arg2 5"), CLI::CallForVersion); | |
| 1204 | + | |
| 1205 | + auto ptr = app.get_version_ptr(); | |
| 1206 | + | |
| 1207 | + ptr->ignore_case(); | |
| 1208 | + try { | |
| 1209 | + app.parse("--Version"); | |
| 1210 | + } catch(const CLI::CallForVersion &v) { | |
| 1211 | + EXPECT_STREQ(v.what(), CLI11_VERSION); | |
| 1212 | + EXPECT_EQ(v.get_exit_code(), 0); | |
| 1213 | + const auto &appc = app; | |
| 1214 | + auto cptr = appc.get_version_ptr(); | |
| 1215 | + EXPECT_EQ(cptr->count(), 1U); | |
| 1216 | + } | |
| 1217 | +} | ... | ... |