Commit 41a9c294d0cfb51efad2ca875e6c994911b5bb5c

Authored by Philip Top
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.
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 &amp;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&lt;
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&lt;
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 &amp;value) -&gt; 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 +}
... ...