Commit 4368e2976cfc9032e5435cf7bcf213a0798b4113

Authored by Henry Fredrick Schreiner
2 parents 34860a6f 21559bd1

Merge branch 'master' into prefix_program

CHANGELOG.md
  1 +## Version 1.1 (in progress)
  2 +* Added `app.parse_order()` with original parse order
  3 +
1 ## Version 1.0 4 ## Version 1.0
2 * Cleanup using `clang-tidy` and `clang-format` 5 * Cleanup using `clang-tidy` and `clang-format`
3 * Small improvements to Timers, easier to subclass Error 6 * Small improvements to Timers, easier to subclass Error
README.md
@@ -31,15 +31,25 @@ An acceptable CLI parser library should be all of the following: @@ -31,15 +31,25 @@ An acceptable CLI parser library should be all of the following:
31 * Work with standard types, simple custom types, and extendible to exotic types. 31 * Work with standard types, simple custom types, and extendible to exotic types.
32 * Permissively licenced. 32 * Permissively licenced.
33 33
34 -The major CLI parsers for C++ include:  
35 -  
36 -* [Boost Program Options]: A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO.  
37 -* [The Lean Mean C++ Option Parser]: One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well.  
38 -* [TCLAP]: The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values.  
39 -* [Cxxopts]: C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar.  
40 -* [DocOpt]: Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized.  
41 -* [GFlags]: The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars.  
42 -* [GetOpt]: Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). 34 +The major CLI parsers for C++ include (with my biased opinions):
  35 +
  36 +| Library | My biased opinion |
  37 +|---------|-------------------|
  38 +| [Boost Program Options] | A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO. |
  39 +| [The Lean Mean C++ Option Parser] | One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well. |
  40 +| [TCLAP] | The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values. |
  41 +| [Cxxopts] | C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar. |
  42 +| [DocOpt] | Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. |
  43 +
  44 +After I wrote this, I also found the following libraries:
  45 +
  46 +| Library | My biased opinion |
  47 +|---------|-------------------|
  48 +| [GFlags] | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. |
  49 +| [GetOpt] | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). |
  50 +| [ProgramOptions.hxx] | Intresting library, less powerful and no subcommands. |
  51 +| [Args] | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose. |
  52 +| [Argument Aggregator] | I'm a big fan of the [fmt] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands. |
43 53
44 None of these libraries fulfill all the above requirements. As you probably have already guessed, CLI11 does. 54 None of these libraries fulfill all the above requirements. As you probably have already guessed, CLI11 does.
45 So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss. 55 So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss.
@@ -154,7 +164,7 @@ On the command line, options can be given as: @@ -154,7 +164,7 @@ On the command line, options can be given as:
154 * `--file=filename` (equals) 164 * `--file=filename` (equals)
155 165
156 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. 166 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
157 -If you set `.allow_extras()` on the main `App`, the parse function will return the left over arguments instead of throwing an error. 167 +If you set `.allow_extras()` on the main `App`, the parse function will return the left over arguments instead of throwing an error. You can access a vector of pointers to the parsed options in the original order using `parse_order()`.
158 If `--` is present in the command line, 168 If `--` is present in the command line,
159 everything after that is positional only. 169 everything after that is positional only.
160 170
@@ -306,4 +316,8 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] @@ -306,4 +316,8 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit]
306 [DIANA/HEP]: http://diana-hep.org 316 [DIANA/HEP]: http://diana-hep.org
307 [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 317 [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736
308 [University of Cincinnati]: http://www.uc.edu 318 [University of Cincinnati]: http://www.uc.edu
309 -[GitBook]: https://henryiii.gitbooks.io/cli11/content 319 +[GitBook]: https://henryiii.gitbooks.io/cli11/content
  320 +[ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx
  321 +[Argument Aggregator]: https://github.com/vietjtnguyen/argagg
  322 +[Args]: https://github.com/Taywee/args
  323 +[fmt]: https://github.com/fmtlib/fmt
examples/CMakeLists.txt
@@ -14,6 +14,7 @@ function(add_cli_exe T) @@ -14,6 +14,7 @@ function(add_cli_exe T)
14 endif() 14 endif()
15 endfunction() 15 endfunction()
16 16
17 -add_cli_exe(try try.cpp)  
18 -add_cli_exe(try1 try1.cpp)  
19 -add_cli_exe(try2 try2.cpp) 17 +add_cli_exe(simple simple.cpp)
  18 +add_cli_exe(subcommands subcommands.cpp)
  19 +add_cli_exe(groups groups.cpp)
  20 +add_cli_exe(inter_argument_order inter_argument_order.cpp)
examples/try2.cpp renamed to examples/groups.cpp
examples/inter_argument_order.cpp 0 โ†’ 100644
  1 +#include <CLI/CLI.hpp>
  2 +#include <iostream>
  3 +#include <vector>
  4 +#include <tuple>
  5 +
  6 +int main(int argc, char **argv) {
  7 + CLI::App app;
  8 +
  9 + std::vector<int> foos;
  10 + auto foo = app.add_option("--foo,-f", foos);
  11 +
  12 + std::vector<int> bars;
  13 + auto bar = app.add_option("--bar", bars);
  14 +
  15 + app.add_flag("--z,--x"); // Random other flags
  16 +
  17 + // Standard parsing lines (copy and paste in)
  18 + try {
  19 + app.parse(argc, argv);
  20 + } catch(const CLI::ParseError &e) {
  21 + return app.exit(e);
  22 + }
  23 +
  24 + // I perfer using the back and popping
  25 + std::reverse(std::begin(foos), std::end(foos));
  26 + std::reverse(std::begin(bars), std::end(bars));
  27 +
  28 + std::vector<std::tuple<std::string, int>> keyval;
  29 + for(auto option : app.parse_order()) {
  30 + if(option == foo) {
  31 + keyval.emplace_back("foo", foos.back());
  32 + foos.pop_back();
  33 + }
  34 + if(option == bar) {
  35 + keyval.emplace_back("bar", bars.back());
  36 + bars.pop_back();
  37 + }
  38 + }
  39 +
  40 + // Prove the vector is correct
  41 + std::string name;
  42 + int value;
  43 +
  44 + for(auto &tuple : keyval) {
  45 + std::tie(name, value) = tuple;
  46 + std::cout << name << " : " << value << std::endl;
  47 + }
  48 +}
examples/try.cpp renamed to examples/simple.cpp
examples/try1.cpp renamed to examples/subcommands.cpp
include/CLI/App.hpp
@@ -84,6 +84,9 @@ class App { @@ -84,6 +84,9 @@ class App {
84 /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. 84 /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
85 missing_t missing_; 85 missing_t missing_;
86 86
  87 + /// This is a list of pointers to options with the orignal parse order
  88 + std::vector<Option *> parse_order_;
  89 +
87 ///@} 90 ///@}
88 /// @name Subcommands 91 /// @name Subcommands
89 ///@{ 92 ///@{
@@ -260,7 +263,7 @@ class App { @@ -260,7 +263,7 @@ class App {
260 variable.emplace_back(); 263 variable.emplace_back();
261 retval &= detail::lexical_cast(a, variable.back()); 264 retval &= detail::lexical_cast(a, variable.back());
262 } 265 }
263 - return variable.size() > 0 && retval; 266 + return (!variable.empty()) && retval;
264 }; 267 };
265 268
266 Option *opt = add_option(name, fun, description, defaulted); 269 Option *opt = add_option(name, fun, description, defaulted);
@@ -720,6 +723,9 @@ class App { @@ -720,6 +723,9 @@ class App {
720 return local_name == name_to_check; 723 return local_name == name_to_check;
721 } 724 }
722 725
  726 + /// This gets a vector of pointers with the original parse order
  727 + const std::vector<Option *> &parse_order() const { return parse_order_; }
  728 +
723 ///@} 729 ///@}
724 730
725 protected: 731 protected:
@@ -958,6 +964,7 @@ class App { @@ -958,6 +964,7 @@ class App {
958 (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { 964 (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) {
959 965
960 opt->add_result(positional); 966 opt->add_result(positional);
  967 + parse_order_.push_back(opt.get());
961 args.pop_back(); 968 args.pop_back();
962 return; 969 return;
963 } 970 }
@@ -1028,18 +1035,21 @@ class App { @@ -1028,18 +1035,21 @@ class App {
1028 1035
1029 int num = op->get_expected(); 1036 int num = op->get_expected();
1030 1037
1031 - if(num == 0) 1038 + if(num == 0) {
1032 op->add_result(""); 1039 op->add_result("");
1033 - else if(rest != "") { 1040 + parse_order_.push_back(op.get());
  1041 + } else if(rest != "") {
1034 if(num > 0) 1042 if(num > 0)
1035 num--; 1043 num--;
1036 op->add_result(rest); 1044 op->add_result(rest);
  1045 + parse_order_.push_back(op.get());
1037 rest = ""; 1046 rest = "";
1038 } 1047 }
1039 1048
1040 if(num == -1) { 1049 if(num == -1) {
1041 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { 1050 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1042 op->add_result(args.back()); 1051 op->add_result(args.back());
  1052 + parse_order_.push_back(op.get());
1043 args.pop_back(); 1053 args.pop_back();
1044 } 1054 }
1045 } else 1055 } else
@@ -1048,6 +1058,7 @@ class App { @@ -1048,6 +1058,7 @@ class App {
1048 std::string current_ = args.back(); 1058 std::string current_ = args.back();
1049 args.pop_back(); 1059 args.pop_back();
1050 op->add_result(current_); 1060 op->add_result(current_);
  1061 + parse_order_.push_back(op.get());
1051 } 1062 }
1052 1063
1053 if(rest != "") { 1064 if(rest != "") {
@@ -1092,19 +1103,23 @@ class App { @@ -1092,19 +1103,23 @@ class App {
1092 if(num != -1) 1103 if(num != -1)
1093 num--; 1104 num--;
1094 op->add_result(value); 1105 op->add_result(value);
  1106 + parse_order_.push_back(op.get());
1095 } else if(num == 0) { 1107 } else if(num == 0) {
1096 op->add_result(""); 1108 op->add_result("");
  1109 + parse_order_.push_back(op.get());
1097 } 1110 }
1098 1111
1099 if(num == -1) { 1112 if(num == -1) {
1100 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { 1113 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1101 op->add_result(args.back()); 1114 op->add_result(args.back());
  1115 + parse_order_.push_back(op.get());
1102 args.pop_back(); 1116 args.pop_back();
1103 } 1117 }
1104 } else 1118 } else
1105 while(num > 0 && !args.empty()) { 1119 while(num > 0 && !args.empty()) {
1106 num--; 1120 num--;
1107 op->add_result(args.back()); 1121 op->add_result(args.back());
  1122 + parse_order_.push_back(op.get());
1108 args.pop_back(); 1123 args.pop_back();
1109 } 1124 }
1110 return; 1125 return;
tests/AppTest.cpp
@@ -501,6 +501,22 @@ TEST_F(TApp, VectorFancyOpts) { @@ -501,6 +501,22 @@ TEST_F(TApp, VectorFancyOpts) {
501 EXPECT_THROW(run(), CLI::ParseError); 501 EXPECT_THROW(run(), CLI::ParseError);
502 } 502 }
503 503
  504 +TEST_F(TApp, OriginalOrder) {
  505 + std::vector<int> st1;
  506 + CLI::Option *op1 = app.add_option("-a", st1);
  507 + std::vector<int> st2;
  508 + CLI::Option *op2 = app.add_option("-b", st2);
  509 +
  510 + args = {"-a", "1", "-b", "2", "-a3", "-a", "4"};
  511 +
  512 + run();
  513 +
  514 + EXPECT_EQ(st1, std::vector<int>({1, 3, 4}));
  515 + EXPECT_EQ(st2, std::vector<int>({2}));
  516 +
  517 + EXPECT_EQ(app.parse_order(), std::vector<CLI::Option *>({op1, op2, op1, op1}));
  518 +}
  519 +
504 TEST_F(TApp, RequiresFlags) { 520 TEST_F(TApp, RequiresFlags) {
505 CLI::Option *opt = app.add_flag("-s,--string"); 521 CLI::Option *opt = app.add_flag("-s,--string");
506 app.add_flag("--both")->requires(opt); 522 app.add_flag("--both")->requires(opt);