Commit 20cccfc353b7ede7bffa4111c764c0b0481a049a
Committed by
Henry Schreiner
1 parent
0a35db8f
Adding take_last
Showing
4 changed files
with
58 additions
and
5 deletions
README.md
| @@ -155,6 +155,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t | @@ -155,6 +155,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t | ||
| 155 | * `->envname(name)`: Gets the value from the environment if present and not passed on the command line. | 155 | * `->envname(name)`: Gets the value from the environment if present and not passed on the command line. |
| 156 | * `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. | 156 | * `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
| 157 | * `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). | 157 | * `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). |
| 158 | +* `->take_last()`: Only take the last option/flag given on the command line, automatically true for bool flags | ||
| 158 | * `->check(CLI::ExistingFile)`: Requires that the file exists if given. | 159 | * `->check(CLI::ExistingFile)`: Requires that the file exists if given. |
| 159 | * `->check(CLI::ExistingDirectory)`: Requires that the directory exists. | 160 | * `->check(CLI::ExistingDirectory)`: Requires that the directory exists. |
| 160 | * `->check(CLI::NonexistentPath)`: Requires that the path does not exist. | 161 | * `->check(CLI::NonexistentPath)`: Requires that the path does not exist. |
include/CLI/App.hpp
| @@ -351,22 +351,23 @@ class App { | @@ -351,22 +351,23 @@ class App { | ||
| 351 | return opt; | 351 | return opt; |
| 352 | } | 352 | } |
| 353 | 353 | ||
| 354 | - /// Bool version | 354 | + /// Bool version - defaults to allowing multiple passings, but can be forced to one if `take_last(false)` is used. |
| 355 | template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> | 355 | template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> |
| 356 | Option *add_flag(std::string name, | 356 | Option *add_flag(std::string name, |
| 357 | T &count, ///< A varaible holding true if passed | 357 | T &count, ///< A varaible holding true if passed |
| 358 | std::string description = "") { | 358 | std::string description = "") { |
| 359 | 359 | ||
| 360 | count = false; | 360 | count = false; |
| 361 | - CLI::callback_t fun = [&count](CLI::results_t) { | 361 | + CLI::callback_t fun = [&count](CLI::results_t res) { |
| 362 | count = true; | 362 | count = true; |
| 363 | - return true; | 363 | + return res.size() == 1; |
| 364 | }; | 364 | }; |
| 365 | 365 | ||
| 366 | Option *opt = add_option(name, fun, description, false); | 366 | Option *opt = add_option(name, fun, description, false); |
| 367 | if(opt->get_positional()) | 367 | if(opt->get_positional()) |
| 368 | throw IncorrectConstruction("Flags cannot be positional"); | 368 | throw IncorrectConstruction("Flags cannot be positional"); |
| 369 | opt->set_custom_option("", 0); | 369 | opt->set_custom_option("", 0); |
| 370 | + opt->take_last(); | ||
| 370 | return opt; | 371 | return opt; |
| 371 | } | 372 | } |
| 372 | 373 |
include/CLI/Option.hpp
| @@ -74,6 +74,9 @@ class Option { | @@ -74,6 +74,9 @@ class Option { | ||
| 74 | /// The number of expected values, 0 for flag, -1 for unlimited vector | 74 | /// The number of expected values, 0 for flag, -1 for unlimited vector |
| 75 | int expected_{1}; | 75 | int expected_{1}; |
| 76 | 76 | ||
| 77 | + /// Only take the last argument (requires `expected_ == 1`) | ||
| 78 | + bool last_{false}; | ||
| 79 | + | ||
| 77 | /// A private setting to allow args to not be able to accept incorrect expected values | 80 | /// A private setting to allow args to not be able to accept incorrect expected values |
| 78 | bool changeable_{false}; | 81 | bool changeable_{false}; |
| 79 | 82 | ||
| @@ -155,10 +158,20 @@ class Option { | @@ -155,10 +158,20 @@ class Option { | ||
| 155 | throw IncorrectConstruction("Cannot make a flag take arguments!"); | 158 | throw IncorrectConstruction("Cannot make a flag take arguments!"); |
| 156 | else if(!changeable_) | 159 | else if(!changeable_) |
| 157 | throw IncorrectConstruction("You can only change the expected arguments for vectors"); | 160 | throw IncorrectConstruction("You can only change the expected arguments for vectors"); |
| 161 | + else if(last_) | ||
| 162 | + throw IncorrectConstruction("You can't change expected arguments after you've set take_last!"); | ||
| 158 | expected_ = value; | 163 | expected_ = value; |
| 159 | return this; | 164 | return this; |
| 160 | } | 165 | } |
| 161 | 166 | ||
| 167 | + /// Take the last argument if given multiple times | ||
| 168 | + Option *take_last(bool value = true) { | ||
| 169 | + if(expected_ != 0 && expected_ != 1) | ||
| 170 | + throw IncorrectConstruction("take_last only works for flags and single value options!"); | ||
| 171 | + last_ = value; | ||
| 172 | + return this; | ||
| 173 | + } | ||
| 174 | + | ||
| 162 | /// Adds a validator | 175 | /// Adds a validator |
| 163 | Option *check(std::function<bool(std::string)> validator) { | 176 | Option *check(std::function<bool(std::string)> validator) { |
| 164 | 177 | ||
| @@ -243,6 +256,9 @@ class Option { | @@ -243,6 +256,9 @@ class Option { | ||
| 243 | /// The number of arguments the option expects | 256 | /// The number of arguments the option expects |
| 244 | int get_expected() const { return expected_; } | 257 | int get_expected() const { return expected_; } |
| 245 | 258 | ||
| 259 | + /// The status of the take last flag | ||
| 260 | + bool get_take_last() const { return last_; } | ||
| 261 | + | ||
| 246 | /// True if this has a default value | 262 | /// True if this has a default value |
| 247 | int get_default() const { return default_; } | 263 | int get_default() const { return default_; } |
| 248 | 264 | ||
| @@ -340,7 +356,16 @@ class Option { | @@ -340,7 +356,16 @@ class Option { | ||
| 340 | 356 | ||
| 341 | /// Process the callback | 357 | /// Process the callback |
| 342 | void run_callback() const { | 358 | void run_callback() const { |
| 343 | - if(!callback_(results_)) | 359 | + bool result; |
| 360 | + // If take_last, only operate on the final item | ||
| 361 | + if(last_) { | ||
| 362 | + results_t partial_result = {results_.back()}; | ||
| 363 | + result = !callback_(partial_result); | ||
| 364 | + } else { | ||
| 365 | + result = !callback_(results_); | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + if(result) | ||
| 344 | throw ConversionError(get_name() + "=" + detail::join(results_)); | 369 | throw ConversionError(get_name() + "=" + detail::join(results_)); |
| 345 | if(!validators_.empty()) { | 370 | if(!validators_.empty()) { |
| 346 | for(const std::string &result : results_) | 371 | for(const std::string &result : results_) |
| @@ -407,7 +432,7 @@ class Option { | @@ -407,7 +432,7 @@ class Option { | ||
| 407 | return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_); | 432 | return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_); |
| 408 | } | 433 | } |
| 409 | 434 | ||
| 410 | - /// Puts a result at position r | 435 | + /// Puts a result at the end, unless last_ is set, in which case it just keeps the last one |
| 411 | void add_result(std::string s) { | 436 | void add_result(std::string s) { |
| 412 | results_.push_back(s); | 437 | results_.push_back(s); |
| 413 | callback_run_ = false; | 438 | callback_run_ = false; |
tests/AppTest.cpp
| @@ -167,6 +167,20 @@ TEST_F(TApp, BoolAndIntFlags) { | @@ -167,6 +167,20 @@ TEST_F(TApp, BoolAndIntFlags) { | ||
| 167 | EXPECT_EQ((unsigned int)2, uflag); | 167 | EXPECT_EQ((unsigned int)2, uflag); |
| 168 | } | 168 | } |
| 169 | 169 | ||
| 170 | +TEST_F(TApp, BoolOnlyFlag) { | ||
| 171 | + bool bflag; | ||
| 172 | + app.add_flag("-b", bflag)->take_last(false); | ||
| 173 | + | ||
| 174 | + args = {"-b"}; | ||
| 175 | + EXPECT_NO_THROW(run()); | ||
| 176 | + EXPECT_TRUE(bflag); | ||
| 177 | + | ||
| 178 | + app.reset(); | ||
| 179 | + | ||
| 180 | + args = {"-b", "-b"}; | ||
| 181 | + EXPECT_THROW(run(), CLI::ConversionError); | ||
| 182 | +} | ||
| 183 | + | ||
| 170 | TEST_F(TApp, ShortOpts) { | 184 | TEST_F(TApp, ShortOpts) { |
| 171 | 185 | ||
| 172 | unsigned long long funnyint; | 186 | unsigned long long funnyint; |
| @@ -204,6 +218,18 @@ TEST_F(TApp, DefaultOpts) { | @@ -204,6 +218,18 @@ TEST_F(TApp, DefaultOpts) { | ||
| 204 | EXPECT_EQ("9", s); | 218 | EXPECT_EQ("9", s); |
| 205 | } | 219 | } |
| 206 | 220 | ||
| 221 | +TEST_F(TApp, TakeLastOpt) { | ||
| 222 | + | ||
| 223 | + std::string str; | ||
| 224 | + app.add_option("--str", str)->take_last(); | ||
| 225 | + | ||
| 226 | + args = {"--str=one", "--str=two"}; | ||
| 227 | + | ||
| 228 | + run(); | ||
| 229 | + | ||
| 230 | + EXPECT_EQ(str, "two"); | ||
| 231 | +} | ||
| 232 | + | ||
| 207 | TEST_F(TApp, EnumTest) { | 233 | TEST_F(TApp, EnumTest) { |
| 208 | enum Level : std::int32_t { High, Medium, Low }; | 234 | enum Level : std::int32_t { High, Medium, Low }; |
| 209 | Level level = Level::Low; | 235 | Level level = Level::Low; |