Commit fa5da7deaa1ae9df258bef7c9741525a752e4330
Committed by
Henry Schreiner
1 parent
9b586786
Adding tests for inhert, a few fixes
Showing
3 changed files
with
147 additions
and
48 deletions
include/CLI/App.hpp
| @@ -78,7 +78,7 @@ class App { | @@ -78,7 +78,7 @@ class App { | ||
| 78 | ///@} | 78 | ///@} |
| 79 | /// @name Options | 79 | /// @name Options |
| 80 | ///@{ | 80 | ///@{ |
| 81 | - | 81 | + |
| 82 | /// The default values for options, customizable and changeable INHERITABLE | 82 | /// The default values for options, customizable and changeable INHERITABLE |
| 83 | OptionDefaults option_defaults_; | 83 | OptionDefaults option_defaults_; |
| 84 | 84 | ||
| @@ -143,21 +143,22 @@ class App { | @@ -143,21 +143,22 @@ class App { | ||
| 143 | ///@} | 143 | ///@} |
| 144 | 144 | ||
| 145 | /// Special private constructor for subcommand | 145 | /// Special private constructor for subcommand |
| 146 | - App(std::string description_, App* parent) : description_(std::move(description_)), parent_(parent) { | 146 | + App(std::string description_, App *parent) : description_(std::move(description_)), parent_(parent) { |
| 147 | // Inherit if not from a nullptr | 147 | // Inherit if not from a nullptr |
| 148 | if(parent_ != nullptr) { | 148 | if(parent_ != nullptr) { |
| 149 | if(parent_->help_ptr_ != nullptr) | 149 | if(parent_->help_ptr_ != nullptr) |
| 150 | set_help_flag(parent_->help_ptr_->get_name(), parent_->help_ptr_->get_description()); | 150 | set_help_flag(parent_->help_ptr_->get_name(), parent_->help_ptr_->get_description()); |
| 151 | - | 151 | + |
| 152 | /// OptionDefaults | 152 | /// OptionDefaults |
| 153 | option_defaults_ = parent_->option_defaults_; | 153 | option_defaults_ = parent_->option_defaults_; |
| 154 | - | 154 | + |
| 155 | // INHERITABLE | 155 | // INHERITABLE |
| 156 | allow_extras_ = parent_->allow_extras_; | 156 | allow_extras_ = parent_->allow_extras_; |
| 157 | prefix_command_ = parent_->prefix_command_; | 157 | prefix_command_ = parent_->prefix_command_; |
| 158 | ignore_case_ = parent_->ignore_case_; | 158 | ignore_case_ = parent_->ignore_case_; |
| 159 | fallthrough_ = parent_->fallthrough_; | 159 | fallthrough_ = parent_->fallthrough_; |
| 160 | group_ = parent_->group_; | 160 | group_ = parent_->group_; |
| 161 | + footer_ = parent_->footer_; | ||
| 161 | } | 162 | } |
| 162 | } | 163 | } |
| 163 | 164 | ||
| @@ -176,6 +177,9 @@ class App { | @@ -176,6 +177,9 @@ class App { | ||
| 176 | return this; | 177 | return this; |
| 177 | } | 178 | } |
| 178 | 179 | ||
| 180 | + /// Get footer. | ||
| 181 | + std::string get_footer() const { return footer_; } | ||
| 182 | + | ||
| 179 | /// Set a callback for the end of parsing. | 183 | /// Set a callback for the end of parsing. |
| 180 | /// | 184 | /// |
| 181 | /// Due to a bug in c++11, | 185 | /// Due to a bug in c++11, |
| @@ -193,12 +197,17 @@ class App { | @@ -193,12 +197,17 @@ class App { | ||
| 193 | return this; | 197 | return this; |
| 194 | } | 198 | } |
| 195 | 199 | ||
| 200 | + /// Get the status of allow extras | ||
| 201 | + bool get_allow_extras() const { return allow_extras_; } | ||
| 202 | + | ||
| 196 | /// Do not parse anything after the first unrecognised option and return | 203 | /// Do not parse anything after the first unrecognised option and return |
| 197 | App *prefix_command(bool allow = true) { | 204 | App *prefix_command(bool allow = true) { |
| 198 | prefix_command_ = allow; | 205 | prefix_command_ = allow; |
| 199 | return this; | 206 | return this; |
| 200 | } | 207 | } |
| 201 | 208 | ||
| 209 | + bool get_prefix_command() const { return prefix_command_; } | ||
| 210 | + | ||
| 202 | /// Ignore case. Subcommand inherit value. | 211 | /// Ignore case. Subcommand inherit value. |
| 203 | App *ignore_case(bool value = true) { | 212 | App *ignore_case(bool value = true) { |
| 204 | ignore_case_ = value; | 213 | ignore_case_ = value; |
| @@ -211,6 +220,8 @@ class App { | @@ -211,6 +220,8 @@ class App { | ||
| 211 | return this; | 220 | return this; |
| 212 | } | 221 | } |
| 213 | 222 | ||
| 223 | + bool get_ignore_case() const { return ignore_case_; } | ||
| 224 | + | ||
| 214 | /// Check to see if this subcommand was parsed, true only if received on command line. | 225 | /// Check to see if this subcommand was parsed, true only if received on command line. |
| 215 | bool parsed() const { return parsed_; } | 226 | bool parsed() const { return parsed_; } |
| 216 | 227 | ||
| @@ -232,6 +243,9 @@ class App { | @@ -232,6 +243,9 @@ class App { | ||
| 232 | return this; | 243 | return this; |
| 233 | } | 244 | } |
| 234 | 245 | ||
| 246 | + /// Check the status of fallthrough | ||
| 247 | + bool get_fallthrough() const { return fallthrough_; } | ||
| 248 | + | ||
| 235 | /// Changes the group membership | 249 | /// Changes the group membership |
| 236 | App *group(std::string name) { | 250 | App *group(std::string name) { |
| 237 | group_ = name; | 251 | group_ = name; |
| @@ -240,9 +254,9 @@ class App { | @@ -240,9 +254,9 @@ class App { | ||
| 240 | 254 | ||
| 241 | /// Get the group of this subcommand | 255 | /// Get the group of this subcommand |
| 242 | const std::string &get_group() const { return group_; } | 256 | const std::string &get_group() const { return group_; } |
| 243 | - | 257 | + |
| 244 | /// Get the OptionDefault object, to set option defaults | 258 | /// Get the OptionDefault object, to set option defaults |
| 245 | - OptionDefaults* option_defaults() {return &option_defaults_;} | 259 | + OptionDefaults *option_defaults() { return &option_defaults_; } |
| 246 | 260 | ||
| 247 | ///@} | 261 | ///@} |
| 248 | /// @name Adding options | 262 | /// @name Adding options |
| @@ -881,7 +895,7 @@ class App { | @@ -881,7 +895,7 @@ class App { | ||
| 881 | std::set<std::string> subcmd_groups_seen; | 895 | std::set<std::string> subcmd_groups_seen; |
| 882 | for(const App_p &com : subcommands_) { | 896 | for(const App_p &com : subcommands_) { |
| 883 | const std::string &group_key = detail::to_lower(com->get_group()); | 897 | const std::string &group_key = detail::to_lower(com->get_group()); |
| 884 | - if(group_key == "hidden" || subcmd_groups_seen.count(group_key) != 0) | 898 | + if(group_key == "" || subcmd_groups_seen.count(group_key) != 0) |
| 885 | continue; | 899 | continue; |
| 886 | 900 | ||
| 887 | subcmd_groups_seen.insert(group_key); | 901 | subcmd_groups_seen.insert(group_key); |
include/CLI/Option.hpp
| @@ -26,81 +26,82 @@ class App; | @@ -26,81 +26,82 @@ class App; | ||
| 26 | 26 | ||
| 27 | using Option_p = std::unique_ptr<Option>; | 27 | using Option_p = std::unique_ptr<Option>; |
| 28 | 28 | ||
| 29 | -template<typename CRTP> | ||
| 30 | -class OptionBase { | ||
| 31 | - friend App; | ||
| 32 | - | 29 | +template <typename CRTP> class OptionBase { |
| 30 | + friend App; | ||
| 31 | + | ||
| 33 | protected: | 32 | protected: |
| 34 | - | ||
| 35 | /// The group membership | 33 | /// The group membership |
| 36 | std::string group_{"Options"}; | 34 | std::string group_{"Options"}; |
| 37 | 35 | ||
| 38 | /// True if this is a required option | 36 | /// True if this is a required option |
| 39 | bool required_{false}; | 37 | bool required_{false}; |
| 40 | - | 38 | + |
| 41 | /// Ignore the case when matching (option, not value) | 39 | /// Ignore the case when matching (option, not value) |
| 42 | bool ignore_case_{false}; | 40 | bool ignore_case_{false}; |
| 43 | - | 41 | + |
| 44 | /// Only take the last argument (requires `expected_ == 1`) | 42 | /// Only take the last argument (requires `expected_ == 1`) |
| 45 | bool last_{false}; | 43 | bool last_{false}; |
| 46 | - | ||
| 47 | - template<typename T> | ||
| 48 | - void copy_from(T& other) { | ||
| 49 | - group_ = other.group_; | ||
| 50 | - required_ = other.required_; | ||
| 51 | - ignore_case_ = other.ignore_case_; | ||
| 52 | - last_ = other.last_; | 44 | + |
| 45 | + template <typename T> void copy_from(const T &other) { | ||
| 46 | + group_ = other.get_group(); | ||
| 47 | + required_ = other.get_required(); | ||
| 48 | + ignore_case_ = other.get_ignore_case(); | ||
| 49 | + last_ = other.get_take_last(); | ||
| 53 | } | 50 | } |
| 54 | - | 51 | + |
| 55 | public: | 52 | public: |
| 56 | - | 53 | + // setters |
| 54 | + | ||
| 55 | + /// Changes the group membership | ||
| 56 | + CRTP *group(std::string name) { | ||
| 57 | + group_ = name; | ||
| 58 | + return static_cast<CRTP *>(this); | ||
| 59 | + ; | ||
| 60 | + } | ||
| 61 | + | ||
| 57 | /// Set the option as required | 62 | /// Set the option as required |
| 58 | CRTP *required(bool value = true) { | 63 | CRTP *required(bool value = true) { |
| 59 | required_ = value; | 64 | required_ = value; |
| 60 | - return static_cast<CRTP*>(this); | 65 | + return static_cast<CRTP *>(this); |
| 61 | } | 66 | } |
| 62 | - | 67 | + |
| 63 | /// Support Plumbum term | 68 | /// Support Plumbum term |
| 64 | CRTP *mandatory(bool value = true) { return required(value); } | 69 | CRTP *mandatory(bool value = true) { return required(value); } |
| 65 | - | ||
| 66 | - /// Changes the group membership | ||
| 67 | - CRTP *group(std::string name) { | ||
| 68 | - group_ = name; | ||
| 69 | - return static_cast<CRTP*>(this);; | ||
| 70 | - } | ||
| 71 | - | 70 | + |
| 71 | + // Getters | ||
| 72 | + | ||
| 73 | + /// Get the group of this option | ||
| 74 | + const std::string &get_group() const { return group_; } | ||
| 75 | + | ||
| 72 | /// True if this is a required option | 76 | /// True if this is a required option |
| 73 | bool get_required() const { return required_; } | 77 | bool get_required() const { return required_; } |
| 74 | 78 | ||
| 79 | + /// The status of ignore case | ||
| 80 | + bool get_ignore_case() const { return ignore_case_; } | ||
| 81 | + | ||
| 75 | /// The status of the take last flag | 82 | /// The status of the take last flag |
| 76 | bool get_take_last() const { return last_; } | 83 | bool get_take_last() const { return last_; } |
| 77 | - | ||
| 78 | - /// The status of ignore case | ||
| 79 | - bool ignore_case() const {return ignore_case_;} | ||
| 80 | - | ||
| 81 | - /// Get the group of this option | ||
| 82 | - const std::string &get_group() const { return group_; } | ||
| 83 | }; | 84 | }; |
| 84 | - | ||
| 85 | -class OptionDefaults : public OptionBase<Option> { | 85 | + |
| 86 | +class OptionDefaults : public OptionBase<OptionDefaults> { | ||
| 86 | public: | 87 | public: |
| 87 | OptionDefaults() = default; | 88 | OptionDefaults() = default; |
| 88 | - | 89 | + |
| 89 | // Methods here need a different implementation if they are Option vs. OptionDefault | 90 | // Methods here need a different implementation if they are Option vs. OptionDefault |
| 90 | - | 91 | + |
| 91 | /// Take the last argument if given multiple times | 92 | /// Take the last argument if given multiple times |
| 92 | OptionDefaults *take_last(bool value = true) { | 93 | OptionDefaults *take_last(bool value = true) { |
| 93 | last_ = value; | 94 | last_ = value; |
| 94 | return this; | 95 | return this; |
| 95 | } | 96 | } |
| 96 | - | 97 | + |
| 97 | /// Ignore the case of the option name | 98 | /// Ignore the case of the option name |
| 98 | OptionDefaults *ignore_case(bool value = true) { | 99 | OptionDefaults *ignore_case(bool value = true) { |
| 99 | ignore_case_ = value; | 100 | ignore_case_ = value; |
| 100 | return this; | 101 | return this; |
| 101 | } | 102 | } |
| 102 | }; | 103 | }; |
| 103 | - | 104 | + |
| 104 | class Option : public OptionBase<Option> { | 105 | class Option : public OptionBase<Option> { |
| 105 | friend App; | 106 | friend App; |
| 106 | 107 | ||
| @@ -146,7 +147,6 @@ class Option : public OptionBase<Option> { | @@ -146,7 +147,6 @@ class Option : public OptionBase<Option> { | ||
| 146 | /// A private setting to allow args to not be able to accept incorrect expected values | 147 | /// A private setting to allow args to not be able to accept incorrect expected values |
| 147 | bool changeable_{false}; | 148 | bool changeable_{false}; |
| 148 | 149 | ||
| 149 | - | ||
| 150 | /// A list of validators to run on each value parsed | 150 | /// A list of validators to run on each value parsed |
| 151 | std::vector<std::function<bool(std::string)>> validators_; | 151 | std::vector<std::function<bool(std::string)>> validators_; |
| 152 | 152 | ||
| @@ -226,7 +226,6 @@ class Option : public OptionBase<Option> { | @@ -226,7 +226,6 @@ class Option : public OptionBase<Option> { | ||
| 226 | return this; | 226 | return this; |
| 227 | } | 227 | } |
| 228 | 228 | ||
| 229 | - | ||
| 230 | /// Sets required options | 229 | /// Sets required options |
| 231 | Option *requires(Option *opt) { | 230 | Option *requires(Option *opt) { |
| 232 | auto tup = requires_.insert(opt); | 231 | auto tup = requires_.insert(opt); |
| @@ -282,12 +281,18 @@ class Option : public OptionBase<Option> { | @@ -282,12 +281,18 @@ class Option : public OptionBase<Option> { | ||
| 282 | /// You are never expected to add an argument to the template here. | 281 | /// You are never expected to add an argument to the template here. |
| 283 | template <typename T = App> Option *ignore_case(bool value = true) { | 282 | template <typename T = App> Option *ignore_case(bool value = true) { |
| 284 | ignore_case_ = value; | 283 | ignore_case_ = value; |
| 285 | - for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) | 284 | + T *parent = dynamic_cast<T *>(parent_); |
| 285 | + | ||
| 286 | + if(parent == nullptr) | ||
| 287 | + throw IncorrectConstruction("This should not happen, there is always a parent!"); | ||
| 288 | + | ||
| 289 | + for(const Option_p &opt : parent->options_) | ||
| 286 | if(opt.get() != this && *opt == *this) | 290 | if(opt.get() != this && *opt == *this) |
| 287 | throw OptionAlreadyAdded(opt->get_name()); | 291 | throw OptionAlreadyAdded(opt->get_name()); |
| 292 | + | ||
| 288 | return this; | 293 | return this; |
| 289 | } | 294 | } |
| 290 | - | 295 | + |
| 291 | /// Take the last argument if given multiple times | 296 | /// Take the last argument if given multiple times |
| 292 | Option *take_last(bool value = true) { | 297 | Option *take_last(bool value = true) { |
| 293 | if(get_expected() != 0 && get_expected() != 1) | 298 | if(get_expected() != 0 && get_expected() != 1) |
| @@ -502,6 +507,8 @@ class Option : public OptionBase<Option> { | @@ -502,6 +507,8 @@ class Option : public OptionBase<Option> { | ||
| 502 | void set_custom_option(std::string typeval, int expected = 1, bool changeable = false) { | 507 | void set_custom_option(std::string typeval, int expected = 1, bool changeable = false) { |
| 503 | typeval_ = typeval; | 508 | typeval_ = typeval; |
| 504 | expected_ = expected; | 509 | expected_ = expected; |
| 510 | + if(expected == 0) | ||
| 511 | + required_ = false; | ||
| 505 | changeable_ = changeable; | 512 | changeable_ = changeable; |
| 506 | } | 513 | } |
| 507 | 514 |
tests/CreationTest.cpp
| @@ -262,3 +262,81 @@ TEST_F(TApp, AllSpaces) { | @@ -262,3 +262,81 @@ TEST_F(TApp, AllSpaces) { | ||
| 262 | EXPECT_TRUE(myapp->check_sname("a")); | 262 | EXPECT_TRUE(myapp->check_sname("a")); |
| 263 | EXPECT_TRUE(myapp->check_name("other")); | 263 | EXPECT_TRUE(myapp->check_name("other")); |
| 264 | } | 264 | } |
| 265 | + | ||
| 266 | +TEST_F(TApp, OptionFromDefaults) { | ||
| 267 | + app.option_defaults()->required(); | ||
| 268 | + | ||
| 269 | + // Options should remember defaults | ||
| 270 | + int x; | ||
| 271 | + auto opt = app.add_option("--simple", x); | ||
| 272 | + EXPECT_TRUE(opt->get_required()); | ||
| 273 | + | ||
| 274 | + // Flags cannot be required | ||
| 275 | + auto flag = app.add_flag("--other"); | ||
| 276 | + EXPECT_FALSE(flag->get_required()); | ||
| 277 | + | ||
| 278 | + app.option_defaults()->required(false); | ||
| 279 | + auto opt2 = app.add_option("--simple2", x); | ||
| 280 | + EXPECT_FALSE(opt2->get_required()); | ||
| 281 | + | ||
| 282 | + app.option_defaults()->required()->ignore_case(); | ||
| 283 | + | ||
| 284 | + auto opt3 = app.add_option("--simple3", x); | ||
| 285 | + EXPECT_TRUE(opt3->get_required()); | ||
| 286 | + EXPECT_TRUE(opt3->get_ignore_case()); | ||
| 287 | +} | ||
| 288 | + | ||
| 289 | +TEST_F(TApp, OptionFromDefaultsSubcommands) { | ||
| 290 | + // Initial defaults | ||
| 291 | + EXPECT_FALSE(app.option_defaults()->get_required()); | ||
| 292 | + EXPECT_FALSE(app.option_defaults()->get_take_last()); | ||
| 293 | + EXPECT_FALSE(app.option_defaults()->get_ignore_case()); | ||
| 294 | + EXPECT_EQ(app.option_defaults()->get_group(), "Options"); | ||
| 295 | + | ||
| 296 | + app.option_defaults()->required()->take_last()->ignore_case()->group("Something"); | ||
| 297 | + | ||
| 298 | + auto app2 = app.add_subcommand("app2"); | ||
| 299 | + | ||
| 300 | + EXPECT_TRUE(app2->option_defaults()->get_required()); | ||
| 301 | + EXPECT_TRUE(app2->option_defaults()->get_take_last()); | ||
| 302 | + EXPECT_TRUE(app2->option_defaults()->get_ignore_case()); | ||
| 303 | + EXPECT_EQ(app2->option_defaults()->get_group(), "Something"); | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +TEST_F(TApp, HelpFlagFromDefaultsSubcommands) { | ||
| 307 | + app.set_help_flag("--that", "Wow"); | ||
| 308 | + | ||
| 309 | + auto app2 = app.add_subcommand("app2"); | ||
| 310 | + | ||
| 311 | + EXPECT_EQ(app2->get_help_ptr()->get_name(), "--that"); | ||
| 312 | + EXPECT_EQ(app2->get_help_ptr()->get_description(), "Wow"); | ||
| 313 | +} | ||
| 314 | + | ||
| 315 | +TEST_F(TApp, SubcommandDefaults) { | ||
| 316 | + // allow_extras, prefix_command, ignore_case, fallthrough, group | ||
| 317 | + | ||
| 318 | + // Initial defaults | ||
| 319 | + EXPECT_FALSE(app.get_allow_extras()); | ||
| 320 | + EXPECT_FALSE(app.get_prefix_command()); | ||
| 321 | + EXPECT_FALSE(app.get_ignore_case()); | ||
| 322 | + EXPECT_FALSE(app.get_fallthrough()); | ||
| 323 | + EXPECT_EQ(app.get_footer(), ""); | ||
| 324 | + EXPECT_EQ(app.get_group(), "Subcommands"); | ||
| 325 | + | ||
| 326 | + app.allow_extras(); | ||
| 327 | + app.prefix_command(); | ||
| 328 | + app.ignore_case(); | ||
| 329 | + app.fallthrough(); | ||
| 330 | + app.set_footer("footy"); | ||
| 331 | + app.group("Stuff"); | ||
| 332 | + | ||
| 333 | + auto app2 = app.add_subcommand("app2"); | ||
| 334 | + | ||
| 335 | + // Initial defaults | ||
| 336 | + EXPECT_TRUE(app2->get_allow_extras()); | ||
| 337 | + EXPECT_TRUE(app2->get_prefix_command()); | ||
| 338 | + EXPECT_TRUE(app2->get_ignore_case()); | ||
| 339 | + EXPECT_TRUE(app2->get_fallthrough()); | ||
| 340 | + EXPECT_EQ(app2->get_footer(), "footy"); | ||
| 341 | + EXPECT_EQ(app2->get_group(), "Stuff"); | ||
| 342 | +} |