diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2359d5cd8..8cea1581d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -54,6 +54,9 @@ set_property(TEST simple_all PROPERTY PASS_REGULAR_EXPRESSION "Received flag: 2 (2) times" "Some value: 1.2") +add_test(NAME simple_version COMMAND simple --version) +set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION + "${CLI11_VERSION}") add_cli_exe(subcommands subcommands.cpp) add_test(NAME subcommands_none COMMAND subcommands) diff --git a/examples/simple.cpp b/examples/simple.cpp index 2d465cb22..c1354eaf3 100644 --- a/examples/simple.cpp +++ b/examples/simple.cpp @@ -11,7 +11,8 @@ int main(int argc, char **argv) { CLI::App app("K3Pi goofit fitter"); - + // add version output + app.set_version_flag("--version", std::string(CLI11_VERSION)); std::string file; CLI::Option *opt = app.add_option("-f,--file,file", file, "File name"); diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index d7c9f3f1d..ad4d950ca 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -139,6 +139,9 @@ class App { /// A pointer to the help all flag if there is one INHERITABLE Option *help_all_ptr_{nullptr}; + /// A pointer to a version flag if there is one + Option *version_ptr_{nullptr}; + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) std::shared_ptr formatter_{new Formatter()}; @@ -703,6 +706,45 @@ class App { return help_all_ptr_; } + /// Set a version flag and version display string, replace the existing one if present + Option *set_version_flag(std::string flag_name = "", const std::string &versionString = "") { + // take flag_description by const reference otherwise add_flag tries to assign to version_description + if(version_ptr_ != nullptr) { + remove_option(version_ptr_); + version_ptr_ = nullptr; + } + + // Empty name will simply remove the version flag + if(!flag_name.empty()) { + version_ptr_ = add_flag_callback( + flag_name, + [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, + "display program version information and exit"); + version_ptr_->configurable(false); + } + + return version_ptr_; + } + /// Generate the version string through a callback function + Option *set_version_flag(std::string flag_name, std::function vfunc) { + // take flag_description by const reference otherwise add_flag tries to assign to version_description + if(version_ptr_ != nullptr) { + remove_option(version_ptr_); + version_ptr_ = nullptr; + } + + // Empty name will simply remove the version flag + if(!flag_name.empty()) { + version_ptr_ = add_flag_callback( + flag_name, + [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, + "display program version information and exit"); + version_ptr_->configurable(false); + } + + return version_ptr_; + } + private: /// Internal function for adding a flag Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { @@ -1345,6 +1387,11 @@ class App { return e.get_exit_code(); } + if(dynamic_cast(&e) != nullptr) { + out << e.what() << std::endl; + return e.get_exit_code(); + } + if(e.get_exit_code() != static_cast(ExitCodes::Success)) { if(failure_message_) err << failure_message_(this, e) << std::flush; @@ -1530,6 +1577,23 @@ class App { return formatter_->make_help(this, prev, mode); } + /// Displays a version string + std::string version() const { + std::string val; + if(version_ptr_ != nullptr) { + auto rv = version_ptr_->results(); + version_ptr_->clear(); + version_ptr_->add_result("true"); + try { + version_ptr_->run_callback(); + } catch(const CLI::CallForVersion &cfv) { + val = cfv.what(); + } + version_ptr_->clear(); + version_ptr_->add_result(rv); + } + return val; + } ///@} /// @name Getters ///@{ @@ -1726,6 +1790,12 @@ class App { /// Get a pointer to the config option. (const) const Option *get_config_ptr() const { return config_ptr_; } + /// Get a pointer to the version option. + Option *get_version_ptr() { return version_ptr_; } + + /// Get a pointer to the version option. (const) + const Option *get_version_ptr() const { return version_ptr_; } + /// Get the parent of this subcommand (or nullptr if master app) App *get_parent() { return parent_; } diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index 0adf15c50..7841c92b7 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -157,18 +157,25 @@ class Success : public ParseError { }; /// -h or --help on command line -class CallForHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForHelp) +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; /// Usually something like --help-all on command line -class CallForAllHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForAllHelp) +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) CallForAllHelp() : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index cd861ba2d..70f202aa7 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -190,7 +190,7 @@ inline bool valid_name_string(const std::string &str) { return true; } -/// check if a string is a container segment separator (empty or "%%" +/// check if a string is a container segment separator (empty or "%%") inline bool is_separator(const std::string &str) { static const std::string sep("%%"); return (str.empty() || str == sep); diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index a4297b2ff..4b85f05c5 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -232,7 +232,7 @@ struct is_mutable_container< // check to see if an object is a mutable container (fail by default) template struct is_readable_container : std::false_type {}; -/// 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 +/// 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 /// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from /// a std::string template @@ -244,7 +244,7 @@ struct is_readable_container< // check to see if an object is a wrapper (fail by default) template struct is_wrapper : std::false_type {}; -// check if an object is a is a wrapper (it has a value_type defined) +// check if an object is a wrapper (it has a value_type defined) template struct is_wrapper, void>> : public std::true_type {}; @@ -344,7 +344,7 @@ auto value_string(const T &value) -> decltype(to_string(value)) { return to_string(value); } -/// temple to get the underlying value type if it exists or use a default +/// template to get the underlying value type if it exists or use a default template struct wrapped_type { using type = def; }; /// Type size for regular object types that do not look like a tuple diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp index 68d96d13b..161b0b9ee 100644 --- a/tests/HelpTest.cpp +++ b/tests/HelpTest.cpp @@ -1165,3 +1165,53 @@ TEST(THelp, FunctionDefaultString) { EXPECT_THAT(help, HasSubstr("INT=Powerful")); } + +TEST(TVersion, simple_flag) { + + CLI::App app; + + app.set_version_flag("-v,--version", "VERSION " CLI11_VERSION); + + auto vers = app.version(); + EXPECT_THAT(vers, HasSubstr("VERSION")); + + app.set_version_flag(); + EXPECT_TRUE(app.version().empty()); +} + +TEST(TVersion, callback_flag) { + + CLI::App app; + + app.set_version_flag("-v,--version", []() { return std::string("VERSION " CLI11_VERSION); }); + + auto vers = app.version(); + EXPECT_THAT(vers, HasSubstr("VERSION")); + + app.set_version_flag("-v", []() { return std::string("VERSION2 " CLI11_VERSION); }); + vers = app.version(); + EXPECT_THAT(vers, HasSubstr("VERSION")); +} + +TEST(TVersion, parse_throw) { + + CLI::App app; + + app.set_version_flag("--version", CLI11_VERSION); + + EXPECT_THROW(app.parse("--version"), CLI::CallForVersion); + EXPECT_THROW(app.parse("--version --arg2 5"), CLI::CallForVersion); + + auto ptr = app.get_version_ptr(); + + ptr->ignore_case(); + try { + app.parse("--Version"); + } catch(const CLI::CallForVersion &v) { + EXPECT_STREQ(v.what(), CLI11_VERSION); + EXPECT_EQ(v.get_exit_code(), 0); + const auto &appc = app; + auto cptr = appc.get_version_ptr(); + EXPECT_EQ(cptr->count(), 1U); + } +}