Commit 4368e2976cfc9032e5435cf7bcf213a0798b4113
Merge branch 'master' into prefix_program
Showing
9 changed files
with
114 additions
and
17 deletions
CHANGELOG.md
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
| ... | ... | @@ -84,6 +84,9 @@ class App { |
| 84 | 84 | /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. |
| 85 | 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 | 91 | /// @name Subcommands |
| 89 | 92 | ///@{ |
| ... | ... | @@ -260,7 +263,7 @@ class App { |
| 260 | 263 | variable.emplace_back(); |
| 261 | 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 | 269 | Option *opt = add_option(name, fun, description, defaulted); |
| ... | ... | @@ -720,6 +723,9 @@ class App { |
| 720 | 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 | 731 | protected: |
| ... | ... | @@ -958,6 +964,7 @@ class App { |
| 958 | 964 | (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { |
| 959 | 965 | |
| 960 | 966 | opt->add_result(positional); |
| 967 | + parse_order_.push_back(opt.get()); | |
| 961 | 968 | args.pop_back(); |
| 962 | 969 | return; |
| 963 | 970 | } |
| ... | ... | @@ -1028,18 +1035,21 @@ class App { |
| 1028 | 1035 | |
| 1029 | 1036 | int num = op->get_expected(); |
| 1030 | 1037 | |
| 1031 | - if(num == 0) | |
| 1038 | + if(num == 0) { | |
| 1032 | 1039 | op->add_result(""); |
| 1033 | - else if(rest != "") { | |
| 1040 | + parse_order_.push_back(op.get()); | |
| 1041 | + } else if(rest != "") { | |
| 1034 | 1042 | if(num > 0) |
| 1035 | 1043 | num--; |
| 1036 | 1044 | op->add_result(rest); |
| 1045 | + parse_order_.push_back(op.get()); | |
| 1037 | 1046 | rest = ""; |
| 1038 | 1047 | } |
| 1039 | 1048 | |
| 1040 | 1049 | if(num == -1) { |
| 1041 | 1050 | while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { |
| 1042 | 1051 | op->add_result(args.back()); |
| 1052 | + parse_order_.push_back(op.get()); | |
| 1043 | 1053 | args.pop_back(); |
| 1044 | 1054 | } |
| 1045 | 1055 | } else |
| ... | ... | @@ -1048,6 +1058,7 @@ class App { |
| 1048 | 1058 | std::string current_ = args.back(); |
| 1049 | 1059 | args.pop_back(); |
| 1050 | 1060 | op->add_result(current_); |
| 1061 | + parse_order_.push_back(op.get()); | |
| 1051 | 1062 | } |
| 1052 | 1063 | |
| 1053 | 1064 | if(rest != "") { |
| ... | ... | @@ -1092,19 +1103,23 @@ class App { |
| 1092 | 1103 | if(num != -1) |
| 1093 | 1104 | num--; |
| 1094 | 1105 | op->add_result(value); |
| 1106 | + parse_order_.push_back(op.get()); | |
| 1095 | 1107 | } else if(num == 0) { |
| 1096 | 1108 | op->add_result(""); |
| 1109 | + parse_order_.push_back(op.get()); | |
| 1097 | 1110 | } |
| 1098 | 1111 | |
| 1099 | 1112 | if(num == -1) { |
| 1100 | 1113 | while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { |
| 1101 | 1114 | op->add_result(args.back()); |
| 1115 | + parse_order_.push_back(op.get()); | |
| 1102 | 1116 | args.pop_back(); |
| 1103 | 1117 | } |
| 1104 | 1118 | } else |
| 1105 | 1119 | while(num > 0 && !args.empty()) { |
| 1106 | 1120 | num--; |
| 1107 | 1121 | op->add_result(args.back()); |
| 1122 | + parse_order_.push_back(op.get()); | |
| 1108 | 1123 | args.pop_back(); |
| 1109 | 1124 | } |
| 1110 | 1125 | return; | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -501,6 +501,22 @@ TEST_F(TApp, VectorFancyOpts) { |
| 501 | 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 | 520 | TEST_F(TApp, RequiresFlags) { |
| 505 | 521 | CLI::Option *opt = app.add_flag("-s,--string"); |
| 506 | 522 | app.add_flag("--both")->requires(opt); | ... | ... |