Commit fd0ca5aa69b2490466c5568be7b7f7aad884d472

Authored by Henry Fredrick Schreiner
2 parents f7cf8905 21559bd1

Merge branch 'master' into basic-enum

CHANGELOG.md
1 ## Version 1.1 (in progress) 1 ## Version 1.1 (in progress)
2 * Added support for basic enumerations [#12](https://github.com/CLIUtils/CLI11/issues/12) 2 * Added support for basic enumerations [#12](https://github.com/CLIUtils/CLI11/issues/12)
  3 +* Added `app.parse_order()` with original parse order
3 4
4 ## Version 1.0 5 ## Version 1.0
5 * Cleanup using `clang-tidy` and `clang-format` 6 * Cleanup using `clang-tidy` and `clang-format`
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
@@ -81,6 +81,9 @@ class App { @@ -81,6 +81,9 @@ class App {
81 /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. 81 /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
82 missing_t missing_; 82 missing_t missing_;
83 83
  84 + /// This is a list of pointers to options with the orignal parse order
  85 + std::vector<Option *> parse_order_;
  86 +
84 ///@} 87 ///@}
85 /// @name Subcommands 88 /// @name Subcommands
86 ///@{ 89 ///@{
@@ -291,7 +294,7 @@ class App { @@ -291,7 +294,7 @@ class App {
291 variable.emplace_back(); 294 variable.emplace_back();
292 retval &= detail::lexical_cast(a, variable.back()); 295 retval &= detail::lexical_cast(a, variable.back());
293 } 296 }
294 - return variable.size() > 0 && retval; 297 + return (!variable.empty()) && retval;
295 }; 298 };
296 299
297 Option *opt = add_option(name, fun, description, defaulted); 300 Option *opt = add_option(name, fun, description, defaulted);
@@ -805,6 +808,9 @@ class App { @@ -805,6 +808,9 @@ class App {
805 return local_name == name_to_check; 808 return local_name == name_to_check;
806 } 809 }
807 810
  811 + /// This gets a vector of pointers with the original parse order
  812 + const std::vector<Option *> &parse_order() const { return parse_order_; }
  813 +
808 ///@} 814 ///@}
809 815
810 protected: 816 protected:
@@ -1043,6 +1049,7 @@ class App { @@ -1043,6 +1049,7 @@ class App {
1043 (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { 1049 (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) {
1044 1050
1045 opt->add_result(positional); 1051 opt->add_result(positional);
  1052 + parse_order_.push_back(opt.get());
1046 args.pop_back(); 1053 args.pop_back();
1047 return; 1054 return;
1048 } 1055 }
@@ -1105,18 +1112,21 @@ class App { @@ -1105,18 +1112,21 @@ class App {
1105 1112
1106 int num = op->get_expected(); 1113 int num = op->get_expected();
1107 1114
1108 - if(num == 0) 1115 + if(num == 0) {
1109 op->add_result(""); 1116 op->add_result("");
1110 - else if(rest != "") { 1117 + parse_order_.push_back(op.get());
  1118 + } else if(rest != "") {
1111 if(num > 0) 1119 if(num > 0)
1112 num--; 1120 num--;
1113 op->add_result(rest); 1121 op->add_result(rest);
  1122 + parse_order_.push_back(op.get());
1114 rest = ""; 1123 rest = "";
1115 } 1124 }
1116 1125
1117 if(num == -1) { 1126 if(num == -1) {
1118 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { 1127 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1119 op->add_result(args.back()); 1128 op->add_result(args.back());
  1129 + parse_order_.push_back(op.get());
1120 args.pop_back(); 1130 args.pop_back();
1121 } 1131 }
1122 } else 1132 } else
@@ -1125,6 +1135,7 @@ class App { @@ -1125,6 +1135,7 @@ class App {
1125 std::string current_ = args.back(); 1135 std::string current_ = args.back();
1126 args.pop_back(); 1136 args.pop_back();
1127 op->add_result(current_); 1137 op->add_result(current_);
  1138 + parse_order_.push_back(op.get());
1128 } 1139 }
1129 1140
1130 if(rest != "") { 1141 if(rest != "") {
@@ -1169,19 +1180,23 @@ class App { @@ -1169,19 +1180,23 @@ class App {
1169 if(num != -1) 1180 if(num != -1)
1170 num--; 1181 num--;
1171 op->add_result(value); 1182 op->add_result(value);
  1183 + parse_order_.push_back(op.get());
1172 } else if(num == 0) { 1184 } else if(num == 0) {
1173 op->add_result(""); 1185 op->add_result("");
  1186 + parse_order_.push_back(op.get());
1174 } 1187 }
1175 1188
1176 if(num == -1) { 1189 if(num == -1) {
1177 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { 1190 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1178 op->add_result(args.back()); 1191 op->add_result(args.back());
  1192 + parse_order_.push_back(op.get());
1179 args.pop_back(); 1193 args.pop_back();
1180 } 1194 }
1181 } else 1195 } else
1182 while(num > 0 && !args.empty()) { 1196 while(num > 0 && !args.empty()) {
1183 num--; 1197 num--;
1184 op->add_result(args.back()); 1198 op->add_result(args.back());
  1199 + parse_order_.push_back(op.get());
1185 args.pop_back(); 1200 args.pop_back();
1186 } 1201 }
1187 return; 1202 return;
tests/AppTest.cpp
@@ -530,6 +530,22 @@ TEST_F(TApp, VectorFancyOpts) { @@ -530,6 +530,22 @@ TEST_F(TApp, VectorFancyOpts) {
530 EXPECT_THROW(run(), CLI::ParseError); 530 EXPECT_THROW(run(), CLI::ParseError);
531 } 531 }
532 532
  533 +TEST_F(TApp, OriginalOrder) {
  534 + std::vector<int> st1;
  535 + CLI::Option *op1 = app.add_option("-a", st1);
  536 + std::vector<int> st2;
  537 + CLI::Option *op2 = app.add_option("-b", st2);
  538 +
  539 + args = {"-a", "1", "-b", "2", "-a3", "-a", "4"};
  540 +
  541 + run();
  542 +
  543 + EXPECT_EQ(st1, std::vector<int>({1, 3, 4}));
  544 + EXPECT_EQ(st2, std::vector<int>({2}));
  545 +
  546 + EXPECT_EQ(app.parse_order(), std::vector<CLI::Option *>({op1, op2, op1, op1}));
  547 +}
  548 +
533 TEST_F(TApp, RequiresFlags) { 549 TEST_F(TApp, RequiresFlags) {
534 CLI::Option *opt = app.add_flag("-s,--string"); 550 CLI::Option *opt = app.add_flag("-s,--string");
535 app.add_flag("--both")->requires(opt); 551 app.add_flag("--both")->requires(opt);