Skip to content

Commit 41a9c29

Browse files
authored
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.
1 parent fff3350 commit 41a9c29

File tree

7 files changed

+140
-9
lines changed

7 files changed

+140
-9
lines changed

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ set_property(TEST simple_all PROPERTY PASS_REGULAR_EXPRESSION
5454
"Received flag: 2 (2) times"
5555
"Some value: 1.2")
5656

57+
add_test(NAME simple_version COMMAND simple --version)
58+
set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION
59+
"${CLI11_VERSION}")
5760

5861
add_cli_exe(subcommands subcommands.cpp)
5962
add_test(NAME subcommands_none COMMAND subcommands)

examples/simple.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
int main(int argc, char **argv) {
1212

1313
CLI::App app("K3Pi goofit fitter");
14-
14+
// add version output
15+
app.set_version_flag("--version", std::string(CLI11_VERSION));
1516
std::string file;
1617
CLI::Option *opt = app.add_option("-f,--file,file", file, "File name");
1718

include/CLI/App.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ class App {
139139
/// A pointer to the help all flag if there is one INHERITABLE
140140
Option *help_all_ptr_{nullptr};
141141

142+
/// A pointer to a version flag if there is one
143+
Option *version_ptr_{nullptr};
144+
142145
/// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
143146
std::shared_ptr<FormatterBase> formatter_{new Formatter()};
144147

@@ -703,6 +706,45 @@ class App {
703706
return help_all_ptr_;
704707
}
705708

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+
706748
private:
707749
/// Internal function for adding a flag
708750
Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
@@ -1345,6 +1387,11 @@ class App {
13451387
return e.get_exit_code();
13461388
}
13471389

1390+
if(dynamic_cast<const CLI::CallForVersion *>(&e) != nullptr) {
1391+
out << e.what() << std::endl;
1392+
return e.get_exit_code();
1393+
}
1394+
13481395
if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
13491396
if(failure_message_)
13501397
err << failure_message_(this, e) << std::flush;
@@ -1530,6 +1577,23 @@ class App {
15301577
return formatter_->make_help(this, prev, mode);
15311578
}
15321579

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+
}
15331597
///@}
15341598
/// @name Getters
15351599
///@{
@@ -1726,6 +1790,12 @@ class App {
17261790
/// Get a pointer to the config option. (const)
17271791
const Option *get_config_ptr() const { return config_ptr_; }
17281792

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+
17291799
/// Get the parent of this subcommand (or nullptr if master app)
17301800
App *get_parent() { return parent_; }
17311801

include/CLI/Error.hpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,18 +157,25 @@ class Success : public ParseError {
157157
};
158158

159159
/// -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)
162162
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
163163
};
164164

165165
/// 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)
168168
CallForAllHelp()
169169
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
170170
};
171171

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+
172179
/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
173180
class RuntimeError : public ParseError {
174181
CLI11_ERROR_DEF(ParseError, RuntimeError)

include/CLI/StringTools.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ inline bool valid_name_string(const std::string &str) {
190190
return true;
191191
}
192192

193-
/// check if a string is a container segment separator (empty or "%%"
193+
/// check if a string is a container segment separator (empty or "%%")
194194
inline bool is_separator(const std::string &str) {
195195
static const std::string sep("%%");
196196
return (str.empty() || str == sep);

include/CLI/TypeTools.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ struct is_mutable_container<
232232
// check to see if an object is a mutable container (fail by default)
233233
template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
234234

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
236236
/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from
237237
/// a std::string
238238
template <typename T>
@@ -244,7 +244,7 @@ struct is_readable_container<
244244
// check to see if an object is a wrapper (fail by default)
245245
template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
246246

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)
248248
template <typename T>
249249
struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
250250

@@ -344,7 +344,7 @@ auto value_string(const T &value) -> decltype(to_string(value)) {
344344
return to_string(value);
345345
}
346346

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
348348
template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
349349

350350
/// Type size for regular object types that do not look like a tuple

tests/HelpTest.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,53 @@ TEST(THelp, FunctionDefaultString) {
11651165

11661166
EXPECT_THAT(help, HasSubstr("INT=Powerful"));
11671167
}
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+
}

0 commit comments

Comments
 (0)