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 | 155 | * `->envname(name)`: Gets the value from the environment if present and not passed on the command line. |
| 156 | 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 | 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 | 159 | * `->check(CLI::ExistingFile)`: Requires that the file exists if given. |
| 159 | 160 | * `->check(CLI::ExistingDirectory)`: Requires that the directory exists. |
| 160 | 161 | * `->check(CLI::NonexistentPath)`: Requires that the path does not exist. | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -351,22 +351,23 @@ class App { |
| 351 | 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 | 355 | template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> |
| 356 | 356 | Option *add_flag(std::string name, |
| 357 | 357 | T &count, ///< A varaible holding true if passed |
| 358 | 358 | std::string description = "") { |
| 359 | 359 | |
| 360 | 360 | count = false; |
| 361 | - CLI::callback_t fun = [&count](CLI::results_t) { | |
| 361 | + CLI::callback_t fun = [&count](CLI::results_t res) { | |
| 362 | 362 | count = true; |
| 363 | - return true; | |
| 363 | + return res.size() == 1; | |
| 364 | 364 | }; |
| 365 | 365 | |
| 366 | 366 | Option *opt = add_option(name, fun, description, false); |
| 367 | 367 | if(opt->get_positional()) |
| 368 | 368 | throw IncorrectConstruction("Flags cannot be positional"); |
| 369 | 369 | opt->set_custom_option("", 0); |
| 370 | + opt->take_last(); | |
| 370 | 371 | return opt; |
| 371 | 372 | } |
| 372 | 373 | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -74,6 +74,9 @@ class Option { |
| 74 | 74 | /// The number of expected values, 0 for flag, -1 for unlimited vector |
| 75 | 75 | int expected_{1}; |
| 76 | 76 | |
| 77 | + /// Only take the last argument (requires `expected_ == 1`) | |
| 78 | + bool last_{false}; | |
| 79 | + | |
| 77 | 80 | /// A private setting to allow args to not be able to accept incorrect expected values |
| 78 | 81 | bool changeable_{false}; |
| 79 | 82 | |
| ... | ... | @@ -155,10 +158,20 @@ class Option { |
| 155 | 158 | throw IncorrectConstruction("Cannot make a flag take arguments!"); |
| 156 | 159 | else if(!changeable_) |
| 157 | 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 | 163 | expected_ = value; |
| 159 | 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 | 175 | /// Adds a validator |
| 163 | 176 | Option *check(std::function<bool(std::string)> validator) { |
| 164 | 177 | |
| ... | ... | @@ -243,6 +256,9 @@ class Option { |
| 243 | 256 | /// The number of arguments the option expects |
| 244 | 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 | 262 | /// True if this has a default value |
| 247 | 263 | int get_default() const { return default_; } |
| 248 | 264 | |
| ... | ... | @@ -340,7 +356,16 @@ class Option { |
| 340 | 356 | |
| 341 | 357 | /// Process the callback |
| 342 | 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 | 369 | throw ConversionError(get_name() + "=" + detail::join(results_)); |
| 345 | 370 | if(!validators_.empty()) { |
| 346 | 371 | for(const std::string &result : results_) |
| ... | ... | @@ -407,7 +432,7 @@ class Option { |
| 407 | 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 | 436 | void add_result(std::string s) { |
| 412 | 437 | results_.push_back(s); |
| 413 | 438 | callback_run_ = false; | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -167,6 +167,20 @@ TEST_F(TApp, BoolAndIntFlags) { |
| 167 | 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 | 184 | TEST_F(TApp, ShortOpts) { |
| 171 | 185 | |
| 172 | 186 | unsigned long long funnyint; |
| ... | ... | @@ -204,6 +218,18 @@ TEST_F(TApp, DefaultOpts) { |
| 204 | 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 | 233 | TEST_F(TApp, EnumTest) { |
| 208 | 234 | enum Level : std::int32_t { High, Medium, Low }; |
| 209 | 235 | Level level = Level::Low; | ... | ... |