From 4a3b57ac5bda40f423f98b15a82a93468a536f42 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 22 Nov 2017 11:09:24 -0500 Subject: [PATCH] Inherit subcommand values, adding getters and tests --- README.md | 15 ++++++++++----- include/CLI/App.hpp | 13 ++++++++++--- tests/CreationTest.cpp | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d6e93d8cd..5c28b2e61 100644 --- a/README.md +++ b/README.md @@ -191,21 +191,26 @@ everything after that is positional only. Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including ignore case). -If you want to require at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. + +If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed. +0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximimum number allows you to keep arguments that match a previous +subcommand name from matching. If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool). All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line. For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.set_callback`. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful. -If you want only one, use `app.require_subcommand(1)`. You are allowed to throw `CLI::Success` in the callbacks. +You are allowed to throw `CLI::Success` in the callbacks. Multiple subcommands are allowed, to allow [`Click`][Click] like series of commands (order is preserved). There are several options that are supported on the main app and subcommands. These are: * `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. * `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. -* `.require_subcommand()`: Require 1 or more subcommands. Accepts an integer argument to require an exact number of subcommands. +* `.require_subcommand()`: Require 1 or more subcommands. +* `.require_subcommand(N)`: Require `N` subcommands if `N`>0, or up to `N` if `N`<0. N=0 resets to the default 0 or more. +* `.require_subcommand(min, max)`: Explicilty set min and max allowed subcommands. Setting `max` to 0 is unlimited. * `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand. * `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line * `.get_subcommands()`: The list of subcommands given on the command line @@ -249,7 +254,7 @@ arguments, use `.config_to_str(default_also=false)`, where `default_also` will a ## Inheriting defaults -Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, and `footer`. The help flag existence, name, and description are inherited, as well. +Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. Options have defaults for `group`, `required`, `take_last`, and `ignore_case`. To set these defaults, you should set the `option_defauts()` object, for example: @@ -258,7 +263,7 @@ app.option_defauts()->required(); // All future options will be required ``` -The default settings for options are inherited to subcommands, as well. +The default settings for options are inherited to subcommands, as well. ## Subclassing diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index d2941a359..70c5d5a3d 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -136,7 +136,7 @@ class App { /// Minimum required subcommands size_t require_subcommand_min_ = 0; - /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited + /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE size_t require_subcommand_max_ = 0; /// The group membership INHERITABLE @@ -175,6 +175,7 @@ class App { fallthrough_ = parent_->fallthrough_; group_ = parent_->group_; footer_ = parent_->footer_; + require_subcommand_max_ = parent_->require_subcommand_max_; } } @@ -254,7 +255,7 @@ class App { /// Require a subcommand to be given (does not affect help call) /// The number required can be given. Negative values indicate maximum - /// number allowed (0 for any number). + /// number allowed (0 for any number). Max number inheritable. App *require_subcommand(int value) { if(value < 0) { require_subcommand_min_ = 0; @@ -267,13 +268,19 @@ class App { } /// Explicitly control the number of subcommands required. Setting 0 - /// for the max means unlimited number allowed + /// for the max means unlimited number allowed. Max number inheritable. App *require_subcommand(size_t min, size_t max) { require_subcommand_min_ = min; require_subcommand_max_ = max; return this; } + /// Get the required min subcommand value + size_t get_require_subcommand_min() const { return require_subcommand_min_; } + + /// Get the required max subcommand value + size_t get_require_subcommand_max() const { return require_subcommand_max_; } + /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand. /// Default from parent, usually set on parent. App *fallthrough(bool value = true) { diff --git a/tests/CreationTest.cpp b/tests/CreationTest.cpp index 945bc871f..cd9be2bc3 100644 --- a/tests/CreationTest.cpp +++ b/tests/CreationTest.cpp @@ -322,7 +322,7 @@ TEST_F(TApp, HelpFlagFromDefaultsSubcommands) { } TEST_F(TApp, SubcommandDefaults) { - // allow_extras, prefix_command, ignore_case, fallthrough, group + // allow_extras, prefix_command, ignore_case, fallthrough, group, min/max subcommand // Initial defaults EXPECT_FALSE(app.get_allow_extras()); @@ -331,6 +331,8 @@ TEST_F(TApp, SubcommandDefaults) { EXPECT_FALSE(app.get_fallthrough()); EXPECT_EQ(app.get_footer(), ""); EXPECT_EQ(app.get_group(), "Subcommands"); + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)0); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)0); app.allow_extras(); app.prefix_command(); @@ -338,6 +340,7 @@ TEST_F(TApp, SubcommandDefaults) { app.fallthrough(); app.set_footer("footy"); app.group("Stuff"); + app.require_subcommand(2, 3); auto app2 = app.add_subcommand("app2"); @@ -348,4 +351,37 @@ TEST_F(TApp, SubcommandDefaults) { EXPECT_TRUE(app2->get_fallthrough()); EXPECT_EQ(app2->get_footer(), "footy"); EXPECT_EQ(app2->get_group(), "Stuff"); + EXPECT_EQ(app2->get_require_subcommand_min(), (size_t)0); + EXPECT_EQ(app2->get_require_subcommand_max(), (size_t)3); +} + +TEST_F(TApp, SubcommandMinMax) { + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)0); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)0); + + app.require_subcommand(); + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)1); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)0); + + app.require_subcommand(2); + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)2); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)2); + + app.require_subcommand(0); + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)0); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)0); + + app.require_subcommand(-2); + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)0); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)2); + + app.require_subcommand(3, 7); + + EXPECT_EQ(app.get_require_subcommand_min(), (size_t)3); + EXPECT_EQ(app.get_require_subcommand_max(), (size_t)7); }