Commit fa5da7deaa1ae9df258bef7c9741525a752e4330

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent 9b586786

Adding tests for inhert, a few fixes

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&lt;Option&gt; { @@ -146,7 +147,6 @@ class Option : public OptionBase&lt;Option&gt; {
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&lt;Option&gt; { @@ -226,7 +226,6 @@ class Option : public OptionBase&lt;Option&gt; {
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&lt;Option&gt; { @@ -282,12 +281,18 @@ class Option : public OptionBase&lt;Option&gt; {
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&lt;Option&gt; { @@ -502,6 +507,8 @@ class Option : public OptionBase&lt;Option&gt; {
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 +}