diff --git a/CHANGELOG.md b/CHANGELOG.md index 10781310..f9632156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Version 1.1 (in progress) * Added support for basic enumerations [#12](https://github.com/CLIUtils/CLI11/issues/12) * 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..42a32f5d 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. | @@ -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); @@ -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 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/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; } } 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; +} diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 02d20751..a58cae06 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_; @@ -152,6 +155,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; @@ -949,7 +958,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, " ") + "]"); } } @@ -1060,7 +1069,15 @@ class App { else { args.pop_back(); missing()->emplace_back(detail::Classifer::NONE, positional); + + if(prefix_command_) { + while(!args.empty()) { + missing()->emplace_back(detail::Classifer::NONE, args.back()); + args.pop_back(); + } + } } + } /// Parse a subcommand, modify args and continue 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"}; 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;