From 3d7de7d25c08465b2bb04b218f3f76802a854b9c Mon Sep 17 00:00:00 2001 From: Philip Top Date: Tue, 12 Feb 2019 00:43:41 -0800 Subject: [PATCH] Add documentation for positionals_at_end documentation clarification on some of the validators (#223) Add a flag that specifies that positional options can only occur as the last arguments of a command line. Will generate an ExtrasError if all positional arguments are not captured, regardless of the state of allow_extras. --- README.md | 5 ++++- include/CLI/App.hpp | 17 +++++++++++++++++ include/CLI/Validators.hpp | 4 ++-- tests/AppTest.cpp | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5a5a2369..89478c10 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ passed result will contain false or -1 if result is a signed integer type, or 0 alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `int_or_bool` is a boolean value the default behavior is to take the last value given, while if `int_or_bool` is an integer type the behavior will be to sum -all the given arguments and return the result. This can be modifed if needed by changing the `multi_option_policy` on +all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited). On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. @@ -276,6 +276,8 @@ Before parsing, you can set the following options: - `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. - `->check(CLI::NonexistentPath)`: Requires that the path does not exist. - `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. +- `->check(CLI::PositiveNumber)`: Requires the number be greater or equal to 0 +- `->check(CLI::ValidIPV4)`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'` - `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. - `->each(void(std::string)>`: Run this function on each value received, as it is received. - `->configurable(false)`: Disable this option from being in a configuration file. @@ -357,6 +359,7 @@ There are several options that are supported on the main app and subcommands. Th - `.name(name)`: Add or change the name. - `.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. +- `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. - `.footer(message)`: Set text to appear at the bottom of the help string. - `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index ec5e08d9..07ba44ee 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -156,6 +156,8 @@ class App { false #endif }; + /// specify that positional arguments come at the end of the argument sequence not inheritable + bool positionals_at_end_{false}; /// A pointer to the parent if this is a subcommand App *parent_{nullptr}; @@ -289,6 +291,12 @@ class App { return this; } + /// specify that the positional arguments are only at the end of the sequence + App *positionals_at_end(bool value = true) { + positionals_at_end_ = value; + return this; + } + /// Ignore underscore. Subcommands inherit value. App *ignore_underscore(bool value = true) { ignore_underscore_ = value; @@ -1575,6 +1583,9 @@ class App { /// Check the status of the allow windows style options bool get_allow_windows_style_options() const { return allow_windows_style_options_; } + /// Check the status of the allow windows style options + bool get_positionals_at_end() const { return positionals_at_end_; } + /// Get the group of this subcommand const std::string &get_group() const { return group_; } @@ -2018,6 +2029,9 @@ class App { case detail::Classifier::NONE: // Probably a positional or something for a parent (sub)command _parse_positional(args); + if(positionals_at_end_) { + positional_only = true; + } } } @@ -2063,6 +2077,9 @@ class App { if(parent_ != nullptr && fallthrough_) return parent_->_parse_positional(args); else { + if(positionals_at_end_) { + throw CLI::ExtrasError(args); + } args.pop_back(); missing_.emplace_back(detail::Classifier::NONE, positional); diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index 0c96527b..ad1f40ab 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -176,7 +176,7 @@ struct IPV4Validator : public Validator { } }; -/// Validate the argument is a number and equal greater then 0 +/// Validate the argument is a number and greater than or equal to 0 struct PositiveNumber : public Validator { PositiveNumber() { tname = "POSITIVE"; @@ -186,7 +186,7 @@ struct PositiveNumber : public Validator { return "Failed parsing number " + number_str; } if(number < 0) { - return "number less then 0 " + number_str; + return "Number less then 0 " + number_str; } return std::string(); }; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 8d05a4d5..090794ac 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -828,6 +828,25 @@ TEST_F(TApp, PositionalNoSpace) { EXPECT_EQ(options.at(0), "Test"); } +// Tests positionals at end +TEST_F(TApp, PositionalAtEnd) { + std::string options; + std::string foo; + + app.add_option("-O", options); + app.add_option("foo", foo); + app.positionals_at_end(); + EXPECT_TRUE(app.get_positionals_at_end()); + args = {"-O", "Test", "param1"}; + run(); + + EXPECT_EQ(options, "Test"); + EXPECT_EQ(foo, "param1"); + + args = {"param2", "-O", "Test"}; + EXPECT_THROW(run(), CLI::ExtrasError); +} + TEST_F(TApp, PositionalNoSpaceLong) { std::vector options; std::string foo, bar;