From 635eb65aced5133c9a28fbfbda0d62898710a504 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 12:34:25 -0400 Subject: [PATCH 1/8] Prefix program support --- include/CLI/App.hpp | 19 ++++++++++++++++++- tests/SubcommandTest.cpp | 13 +++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index bae90197..8b3e29fb 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -57,6 +57,9 @@ class App { /// If true, allow extra arguments (ie, don't throw an error). bool allow_extras_{false}; + /// If true, return immediatly on an unrecognised option (implies allow_extras) + bool prefix_command_{false}; + /// This is a function that runs when complete. Great for subcommands. Can throw. std::function callback_; @@ -149,6 +152,12 @@ class App { return this; } + /// Do not parse anything after the first unrecongnised option and return + App *prefix_command(bool allow = true) { + prefix_command_ = allow; + return this; + } + /// Ignore case. Subcommand inherit value. App *ignore_case(bool value = true) { ignore_case_ = value; @@ -849,7 +858,7 @@ class App { return val.first != detail::Classifer::POSITIONAL_MARK; }); - if(num_left_over > 0 && !allow_extras_) + if(num_left_over > 0 && !(allow_extras_ || prefix_command_)) throw ExtrasError("[" + detail::rjoin(args, " ") + "]"); } } @@ -959,7 +968,15 @@ class App { else { args.pop_back(); missing()->emplace_back(detail::Classifer::NONE, positional); + + if(prefix_command_) { + for(std::string positional : args) { + missing()->emplace_back(detail::Classifer::NONE, args.back()); + args.pop_back(); + } + } } + } /// Parse a subcommand, modify args and continue diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index ef363abf..c2753705 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -231,6 +231,19 @@ TEST_F(TApp, BadSubcomSearch) { EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound); } +TEST_F(TApp, PrefixProgram) { + + app.prefix_command(); + + app.add_flag("--simple"); + + args = {"--simple", "other", "--simple", "--mine"}; + auto ret_args = run(); + + EXPECT_EQ(ret_args, std::vector({"--mine", "--simple", "other"})); + +} + struct SubcommandProgram : public TApp { CLI::App *start; From 34860a6f83ba844053e21684fd41388fedc25981 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 12:59:14 -0400 Subject: [PATCH 2/8] Fix for broken loop --- include/CLI/App.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 8b3e29fb..663582f5 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -970,7 +970,7 @@ class App { missing()->emplace_back(detail::Classifer::NONE, positional); if(prefix_command_) { - for(std::string positional : args) { + while(!args.empty()) { missing()->emplace_back(detail::Classifer::NONE, args.back()); args.pop_back(); } From e2fae48e5997025c2df13a31a05ac5d1a02299da Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sun, 4 Jun 2017 13:51:33 -0400 Subject: [PATCH 3/8] Adding prefix command to docs --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a066720d..4814a7f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Version 1.1 (in progress) * Added `app.parse_order()` with original parse order +* Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ## Version 1.0 * Cleanup using `clang-tidy` and `clang-format` diff --git a/README.md b/README.md index 402d5efb..728b4d8e 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ There are several options that are supported on the main app and subcommands. Th * `.parsed()`: True if this subcommand was given on the command line * `.set_callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. * `.allow_extras()`: Do not throw an error if extra arguments are left over (Only useful on the main `App`, as that's the one that throws errors). +* `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app to be a "prefix" to calling another app. ## Configuration file From 36ac4c1cc7f9faee785d7a167893757b1cad4fc9 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sun, 4 Jun 2017 14:13:02 -0400 Subject: [PATCH 4/8] Adding example program for prefix program --- examples/CMakeLists.txt | 1 + examples/prefix_command.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 examples/prefix_command.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f41d948e..1b6f2abc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -18,3 +18,4 @@ 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) +add_cli_exe(prefix_command prefix_command.cpp) diff --git a/examples/prefix_command.cpp b/examples/prefix_command.cpp new file mode 100644 index 00000000..103ee833 --- /dev/null +++ b/examples/prefix_command.cpp @@ -0,0 +1,32 @@ +#include "CLI/CLI.hpp" + +int main(int argc, char **argv) { + + CLI::App app("Prefix command app"); + app.prefix_command(); + + std::vector vals; + app.add_option("--vals,-v", vals) + ->expected(1); + + std::vector more_comms; + try { + more_comms = app.parse(argc, argv); + } catch(const CLI::ParseError &e) { + return app.exit(e); + } + + std::cout << "Prefix:"; + for(int v : vals) + std::cout << v << ":"; + + std::cout << std::endl << "Remaining commands: "; + + // Perfer to loop over from beginning, not "pop" order + std::reverse(std::begin(more_comms), std::end(more_comms)); + for(auto com : more_comms) + std::cout << com << " "; + std::cout << std::endl; + + return 0; +} From 2173948e4188291006d48a95ee131f164a52bd2a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 5 Jun 2017 08:16:30 -0400 Subject: [PATCH 5/8] Fix in example from @serge-sans-paille, closes #19 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 402d5efb..cc711499 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ To set up, add options, and run, your main function will look something like thi CLI::App app{"App description"}; std::string filename = "default"; -app.add_option("-f,--file", file, "A help string"); +app.add_option("-f,--file", filename, "A help string"); try { app.parse(argc, argv); From deae51390024fda89f7cb35f4232f4ae229b51eb Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 5 Jun 2017 08:17:12 -0400 Subject: [PATCH 6/8] Minor update on another lib --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc711499..49f8bd6d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ After I wrote this, I also found the following libraries: |---------|-------------------| | [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. | +| [ProgramOptions.hxx] | Intresting library, less powerful and no subcommands. Nice callback system. | | [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. | From 04268dac5a945b39fd3d7fc80b297aed1a7d8719 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 5 Jun 2017 08:37:23 -0400 Subject: [PATCH 7/8] Minor cleanup to inter_arg_order --- examples/inter_argument_order.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/inter_argument_order.cpp b/examples/inter_argument_order.cpp index 6d570e1b..ad3ae02b 100644 --- a/examples/inter_argument_order.cpp +++ b/examples/inter_argument_order.cpp @@ -2,6 +2,7 @@ #include #include #include +#include int main(int argc, char **argv) { CLI::App app; @@ -25,7 +26,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()); @@ -38,11 +39,7 @@ int main(int argc, char **argv) { } // 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; + for(auto &pair : keyval) { + std::cout << pair.first << " : " << pair.second << std::endl; } } From e3423bb5ad99ec327076fec9e67bbc67a57d2039 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 5 Jun 2017 08:58:01 -0400 Subject: [PATCH 8/8] Futher improvement for #12, can change type str --- include/CLI/Option.hpp | 4 ++++ tests/HelpTest.cpp | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 1647617c..ee74929a 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -433,6 +433,10 @@ class Option { /// Set the default value string representation void set_default_val(std::string val) { defaultval_ = val; } + + /// Set the type name displayed on this option + void set_type_name(std::string val) {typeval_ = val;} + ///@} protected: diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp index ac42ad05..863f4225 100644 --- a/tests/HelpTest.cpp +++ b/tests/HelpTest.cpp @@ -190,6 +190,22 @@ TEST(THelp, ExcludesPositional) { EXPECT_THAT(help, HasSubstr("Excludes: op1")); } +TEST(THelp, ManualSetters) { + + CLI::App app{"My prog"}; + + int x; + + CLI::Option *op1 = app.add_option("--op", x); + op1->set_default_val("12"); + op1->set_type_name("BIGGLES"); + + std::string help = app.help(); + + EXPECT_THAT(help, HasSubstr("=12")); + EXPECT_THAT(help, HasSubstr("BIGGLES")); +} + TEST(THelp, Subcom) { CLI::App app{"My prog"};