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.
This commit is contained in:
Philip Top 2020-05-24 20:18:44 -07:00 committed by GitHub
parent fff3350254
commit 41a9c294d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 9 deletions

View File

@ -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)

View File

@ -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");

View File

@ -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<FormatterBase> 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<std::string()> 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<const CLI::CallForVersion *>(&e) != nullptr) {
out << e.what() << std::endl;
return e.get_exit_code();
}
if(e.get_exit_code() != static_cast<int>(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_; }

View File

@ -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)

View File

@ -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);

View File

@ -232,7 +232,7 @@ struct is_mutable_container<
// check to see if an object is a mutable container (fail by default)
template <typename T, typename _ = void> 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 <typename T>
@ -244,7 +244,7 @@ struct is_readable_container<
// check to see if an object is a wrapper (fail by default)
template <typename T, typename _ = void> 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 <typename T>
struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, 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 <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
/// Type size for regular object types that do not look like a tuple

View File

@ -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);
}
}