Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion examples/simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
70 changes: 70 additions & 0 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()};

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
///@{
Expand Down Expand Up @@ -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_; }

Expand Down
15 changes: 11 additions & 4 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions include/CLI/TypeTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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 {};

Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions tests/HelpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}