Commit fd0ca5aa69b2490466c5568be7b7f7aad884d472

Authored by Henry Fredrick Schreiner
2 parents f7cf8905 21559bd1

Merge branch 'master' into basic-enum

CHANGELOG.md
1 1 ## Version 1.1 (in progress)
2 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 5 ## Version 1.0
5 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 31 * Work with standard types, simple custom types, and extendible to exotic types.
32 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 54 None of these libraries fulfill all the above requirements. As you probably have already guessed, CLI11 does.
45 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 164 * `--file=filename` (equals)
155 165  
156 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 168 If `--` is present in the command line,
159 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 316 [DIANA/HEP]: http://diana-hep.org
307 317 [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736
308 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 14 endif()
15 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 81 /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
82 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 88 /// @name Subcommands
86 89 ///@{
... ... @@ -291,7 +294,7 @@ class App {
291 294 variable.emplace_back();
292 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 300 Option *opt = add_option(name, fun, description, defaulted);
... ... @@ -805,6 +808,9 @@ class App {
805 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 816 protected:
... ... @@ -1043,6 +1049,7 @@ class App {
1043 1049 (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) {
1044 1050  
1045 1051 opt->add_result(positional);
  1052 + parse_order_.push_back(opt.get());
1046 1053 args.pop_back();
1047 1054 return;
1048 1055 }
... ... @@ -1105,18 +1112,21 @@ class App {
1105 1112  
1106 1113 int num = op->get_expected();
1107 1114  
1108   - if(num == 0)
  1115 + if(num == 0) {
1109 1116 op->add_result("");
1110   - else if(rest != "") {
  1117 + parse_order_.push_back(op.get());
  1118 + } else if(rest != "") {
1111 1119 if(num > 0)
1112 1120 num--;
1113 1121 op->add_result(rest);
  1122 + parse_order_.push_back(op.get());
1114 1123 rest = "";
1115 1124 }
1116 1125  
1117 1126 if(num == -1) {
1118 1127 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1119 1128 op->add_result(args.back());
  1129 + parse_order_.push_back(op.get());
1120 1130 args.pop_back();
1121 1131 }
1122 1132 } else
... ... @@ -1125,6 +1135,7 @@ class App {
1125 1135 std::string current_ = args.back();
1126 1136 args.pop_back();
1127 1137 op->add_result(current_);
  1138 + parse_order_.push_back(op.get());
1128 1139 }
1129 1140  
1130 1141 if(rest != "") {
... ... @@ -1169,19 +1180,23 @@ class App {
1169 1180 if(num != -1)
1170 1181 num--;
1171 1182 op->add_result(value);
  1183 + parse_order_.push_back(op.get());
1172 1184 } else if(num == 0) {
1173 1185 op->add_result("");
  1186 + parse_order_.push_back(op.get());
1174 1187 }
1175 1188  
1176 1189 if(num == -1) {
1177 1190 while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
1178 1191 op->add_result(args.back());
  1192 + parse_order_.push_back(op.get());
1179 1193 args.pop_back();
1180 1194 }
1181 1195 } else
1182 1196 while(num > 0 && !args.empty()) {
1183 1197 num--;
1184 1198 op->add_result(args.back());
  1199 + parse_order_.push_back(op.get());
1185 1200 args.pop_back();
1186 1201 }
1187 1202 return;
... ...
tests/AppTest.cpp
... ... @@ -530,6 +530,22 @@ TEST_F(TApp, VectorFancyOpts) {
530 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 549 TEST_F(TApp, RequiresFlags) {
534 550 CLI::Option *opt = app.add_flag("-s,--string");
535 551 app.add_flag("--both")->requires(opt);
... ...