From 3b04cd62af5ff2b29c65e561e0c51511ac390aa3 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 12:51:27 -0400 Subject: [PATCH 1/6] Adding parse order capture --- include/CLI/App.hpp | 21 +++++++++++++++++++-- tests/AppTest.cpp | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index bae90197..8a17c80b 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -80,6 +80,9 @@ class App { /// /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. missing_t missing_; + + /// This is a list of pointers to options with the orignal parse order + std::vector parse_order_; ///@} /// @name Subcommands @@ -710,6 +713,11 @@ class App { return local_name == name_to_check; } + + /// This gets a vector of pointers with the original parse order + const std::vector &parse_order() const { + return parse_order_; + } ///@} @@ -949,6 +957,7 @@ class App { (static_cast(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { opt->add_result(positional); + parse_order_.push_back(opt.get()); args.pop_back(); return; } @@ -1011,18 +1020,21 @@ class App { int num = op->get_expected(); - if(num == 0) + if(num == 0) { op->add_result(""); - else if(rest != "") { + parse_order_.push_back(op.get()); + } else if(rest != "") { if(num > 0) num--; op->add_result(rest); + parse_order_.push_back(op.get()); rest = ""; } if(num == -1) { while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { op->add_result(args.back()); + parse_order_.push_back(op.get()); args.pop_back(); } } else @@ -1031,6 +1043,7 @@ class App { std::string current_ = args.back(); args.pop_back(); op->add_result(current_); + parse_order_.push_back(op.get()); } if(rest != "") { @@ -1075,19 +1088,23 @@ class App { if(num != -1) num--; op->add_result(value); + parse_order_.push_back(op.get()); } else if(num == 0) { op->add_result(""); + parse_order_.push_back(op.get()); } if(num == -1) { while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { op->add_result(args.back()); + parse_order_.push_back(op.get()); args.pop_back(); } } else while(num > 0 && !args.empty()) { num--; op->add_result(args.back()); + parse_order_.push_back(op.get()); args.pop_back(); } return; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index ca19f33a..283054dd 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -501,6 +501,23 @@ TEST_F(TApp, VectorFancyOpts) { EXPECT_THROW(run(), CLI::ParseError); } +TEST_F(TApp, OriginalOrder) { + std::vector st1; + CLI::Option* op1 = app.add_option("-a", st1); + std::vector st2; + CLI::Option* op2 = app.add_option("-b", st2); + + args = {"-a", "1", "-b", "2", "-a3", "-a", "4"}; + + run(); + + EXPECT_EQ(st1, std::vector({1,3,4})); + EXPECT_EQ(st2, std::vector({2})); + + EXPECT_EQ(app.parse_order(), std::vector({op1, op2, op1, op1})); + +} + TEST_F(TApp, RequiresFlags) { CLI::Option *opt = app.add_flag("-s,--string"); app.add_flag("--both")->requires(opt); From 2c15786bca69cd5342eaa6a44593e2b9d42e8c99 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 2 Jun 2017 13:09:47 -0400 Subject: [PATCH 2/6] Adding one more library listing --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a23b1d8a..655dd3d5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ An acceptable CLI parser library should be all of the following: * Work with standard types, simple custom types, and extendible to exotic types. * Permissively licenced. -The major CLI parsers for C++ include: +The major CLI parsers for C++ include (with my biased opinions): * [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. * [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,6 +40,7 @@ The major CLI parsers for C++ include: * [DocOpt]: Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. * [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. * [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). +* [ProgramOptions.hxx]: Intresting library, less powerful and no subcommands. None of these libraries fulfill all the above requirements. As you probably have already guessed, CLI11 does. So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss. @@ -307,3 +308,4 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 [University of Cincinnati]: http://www.uc.edu [GitBook]: https://henryiii.gitbooks.io/cli11/content +[ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx From bfeadf8764c8fbd2b70a3f87362bae1fb7176879 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 2 Jun 2017 19:42:05 -0400 Subject: [PATCH 3/6] Adding a couple of extra libraries. --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 655dd3d5..533b23ee 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,23 @@ An acceptable CLI parser library should be all of the following: The major CLI parsers for C++ include (with my biased opinions): -* [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. -* [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. -* [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. -* [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. -* [DocOpt]: Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. -* [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. -* [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). -* [ProgramOptions.hxx]: Intresting library, less powerful and no subcommands. +| Library | My biased opinion | +|---------|-------------------| +| [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. | +| [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. | +| [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. | +| [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. | +| [DocOpt] | Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. | + +After I wrote this, I also found the following libraries: + +| Library | My biased opinion | +|---------|-------------------| +| [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. | +| [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). | +| [ProgramOptions.hxx] | Intresting library, less powerful and no subcommands. | +| [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. | +| [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. | None of these libraries fulfill all the above requirements. As you probably have already guessed, CLI11 does. So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss. @@ -307,5 +316,8 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [DIANA/HEP]: http://diana-hep.org [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 [University of Cincinnati]: http://www.uc.edu -[GitBook]: https://henryiii.gitbooks.io/cli11/content -[ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx +[GitBook]: https://henryiii.gitbooks.io/cli11/content +[ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx +[Argument Aggregator]: https://github.com/vietjtnguyen/argagg +[Args]: https://github.com/Taywee/args +[fmt]: https://github.com/fmtlib/fmt From 100db357db00d6f3fb963f4661cdb20fd7b57aef Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sun, 4 Jun 2017 11:29:42 -0400 Subject: [PATCH 4/6] Better example names --- examples/CMakeLists.txt | 6 +++--- examples/{try2.cpp => groups.cpp} | 0 examples/{try.cpp => simple.cpp} | 0 examples/{try1.cpp => subcommands.cpp} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename examples/{try2.cpp => groups.cpp} (100%) rename examples/{try.cpp => simple.cpp} (100%) rename examples/{try1.cpp => subcommands.cpp} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d08459ad..b4efebdb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,6 @@ function(add_cli_exe T) endif() endfunction() -add_cli_exe(try try.cpp) -add_cli_exe(try1 try1.cpp) -add_cli_exe(try2 try2.cpp) +add_cli_exe(simple simple.cpp) +add_cli_exe(subcommands subcommands.cpp) +add_cli_exe(groups groups.cpp) diff --git a/examples/try2.cpp b/examples/groups.cpp similarity index 100% rename from examples/try2.cpp rename to examples/groups.cpp diff --git a/examples/try.cpp b/examples/simple.cpp similarity index 100% rename from examples/try.cpp rename to examples/simple.cpp diff --git a/examples/try1.cpp b/examples/subcommands.cpp similarity index 100% rename from examples/try1.cpp rename to examples/subcommands.cpp From 11df3becfb76971fb57a7ffc58ca3064205e0bd2 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sun, 4 Jun 2017 13:19:10 -0400 Subject: [PATCH 5/6] Show parse order is preserved --- CHANGELOG.md | 3 ++ README.md | 2 +- examples/CMakeLists.txt | 1 + examples/inter_argument_order.cpp | 50 +++++++++++++++++++++++++++++++ include/CLI/App.hpp | 2 +- 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 examples/inter_argument_order.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a1a17d..a066720d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Version 1.1 (in progress) +* Added `app.parse_order()` with original parse order + ## Version 1.0 * Cleanup using `clang-tidy` and `clang-format` * Small improvements to Timers, easier to subclass Error diff --git a/README.md b/README.md index 533b23ee..402d5efb 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ On the command line, options can be given as: * `--file=filename` (equals) 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. -If you set `.allow_extras()` on the main `App`, the parse function will return the left over arguments instead of throwing an error. +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()`. If `--` is present in the command line, everything after that is positional only. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b4efebdb..f41d948e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -17,3 +17,4 @@ endfunction() add_cli_exe(simple simple.cpp) add_cli_exe(subcommands subcommands.cpp) add_cli_exe(groups groups.cpp) +add_cli_exe(inter_argument_order inter_argument_order.cpp) diff --git a/examples/inter_argument_order.cpp b/examples/inter_argument_order.cpp new file mode 100644 index 00000000..8056523b --- /dev/null +++ b/examples/inter_argument_order.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +int main(int argc, char **argv) { + CLI::App app; + + std::vector foos; + auto foo = app.add_option("--foo,-f", foos); + + std::vector bars; + auto bar = app.add_option("--bar", bars); + + app.add_flag("--z,--x"); // Random other flags + + // Standard parsing lines (copy and paste in) + try { + app.parse(argc, argv); + } catch (const CLI::ParseError &e) { + return app.exit(e); + } + + // I perfer using the back and popping + std::reverse(std::begin(foos), std::end(foos)); + std::reverse(std::begin(bars), std::end(bars)); + + std::vector> keyval; + for(auto option : app.parse_order()) { + if(option == foo) { + keyval.emplace_back("foo", foos.back()); + foos.pop_back(); + } + if(option == bar) { + keyval.emplace_back("bar", bars.back()); + bars.pop_back(); + } + } + + // Prove the vector is correct + std::string name; + int value; + + for(auto& tuple : keyval) { + std::tie(name, value) = tuple; + std::cout << name << " : " << value << std::endl; + } +} + + diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 8a17c80b..d5ed30d6 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -254,7 +254,7 @@ class App { variable.emplace_back(); retval &= detail::lexical_cast(a, variable.back()); } - return variable.size() > 0 && retval; + return (!variable.empty()) && retval; }; Option *opt = add_option(name, fun, description, defaulted); From 18163306293386e20fb87b298f8c881093a4a908 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sun, 4 Jun 2017 13:23:01 -0400 Subject: [PATCH 6/6] clang-formatting new source --- examples/inter_argument_order.cpp | 8 +++----- include/CLI/App.hpp | 10 ++++------ tests/AppTest.cpp | 17 ++++++++--------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/inter_argument_order.cpp b/examples/inter_argument_order.cpp index 8056523b..6d570e1b 100644 --- a/examples/inter_argument_order.cpp +++ b/examples/inter_argument_order.cpp @@ -17,7 +17,7 @@ int main(int argc, char **argv) { // Standard parsing lines (copy and paste in) try { app.parse(argc, argv); - } catch (const CLI::ParseError &e) { + } catch(const CLI::ParseError &e) { return app.exit(e); } @@ -25,7 +25,7 @@ int main(int argc, char **argv) { std::reverse(std::begin(foos), std::end(foos)); std::reverse(std::begin(bars), std::end(bars)); - std::vector> keyval; + std::vector> keyval; for(auto option : app.parse_order()) { if(option == foo) { keyval.emplace_back("foo", foos.back()); @@ -41,10 +41,8 @@ int main(int argc, char **argv) { std::string name; int value; - for(auto& tuple : keyval) { + for(auto &tuple : keyval) { std::tie(name, value) = tuple; std::cout << name << " : " << value << std::endl; } } - - diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index d5ed30d6..f4729cda 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -80,9 +80,9 @@ class App { /// /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. missing_t missing_; - + /// This is a list of pointers to options with the orignal parse order - std::vector parse_order_; + std::vector