Commit 20cccfc353b7ede7bffa4111c764c0b0481a049a

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent 0a35db8f

Adding take_last

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;
... ...