Commit 6aa546fc427871e2e3bbd99bc40b4d5d790c3ef7
Committed by
Henry Schreiner
1 parent
0631189b
merge upstream App (#246)
Make sure that nameless subcommands can handle subcommands and that App will treat subcommands in a group nearly the same as if they were in the in the app to begin with.
Showing
2 changed files
with
140 additions
and
23 deletions
include/CLI/App.hpp
| ... | ... | @@ -1045,10 +1045,10 @@ class App { |
| 1045 | 1045 | |
| 1046 | 1046 | /// Check to see if a subcommand is part of this command (text version) |
| 1047 | 1047 | App *get_subcommand(std::string subcom) const { |
| 1048 | - for(const App_p &subcomptr : subcommands_) | |
| 1049 | - if(subcomptr->check_name(subcom)) | |
| 1050 | - return subcomptr.get(); | |
| 1051 | - throw OptionNotFound(subcom); | |
| 1048 | + auto subc = _find_subcommand(subcom, false); | |
| 1049 | + if(subc == nullptr) | |
| 1050 | + throw OptionNotFound(subcom); | |
| 1051 | + return subc; | |
| 1052 | 1052 | } |
| 1053 | 1053 | /// Get a pointer to subcommand by index |
| 1054 | 1054 | App *get_subcommand(int index = 0) const { |
| ... | ... | @@ -1736,11 +1736,10 @@ class App { |
| 1736 | 1736 | if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) { |
| 1737 | 1737 | return parent_ != nullptr && parent_->_valid_subcommand(current); |
| 1738 | 1738 | } |
| 1739 | - | |
| 1740 | - for(const App_p &com : subcommands_) | |
| 1741 | - if(!com->disabled_ && com->check_name(current) && !*com) | |
| 1742 | - return true; | |
| 1743 | - | |
| 1739 | + auto com = _find_subcommand(current, true); | |
| 1740 | + if((com != nullptr) && !*com) { | |
| 1741 | + return true; | |
| 1742 | + } | |
| 1744 | 1743 | // Check parent if exists, else return false |
| 1745 | 1744 | return parent_ != nullptr && parent_->_valid_subcommand(current); |
| 1746 | 1745 | } |
| ... | ... | @@ -2151,28 +2150,43 @@ class App { |
| 2151 | 2150 | } |
| 2152 | 2151 | } |
| 2153 | 2152 | |
| 2153 | + /// Locate a subcommand by name | |
| 2154 | + App *_find_subcommand(const std::string &subc_name, bool ignore_disabled) const noexcept { | |
| 2155 | + for(const App_p &com : subcommands_) { | |
| 2156 | + if((com->disabled_) && (ignore_disabled)) | |
| 2157 | + continue; | |
| 2158 | + if(com->get_name().empty()) { | |
| 2159 | + auto subc = com->_find_subcommand(subc_name, ignore_disabled); | |
| 2160 | + if(subc != nullptr) { | |
| 2161 | + return subc; | |
| 2162 | + } | |
| 2163 | + } else if(com->check_name(subc_name)) { | |
| 2164 | + return com.get(); | |
| 2165 | + } | |
| 2166 | + } | |
| 2167 | + return nullptr; | |
| 2168 | + } | |
| 2169 | + | |
| 2154 | 2170 | /// Parse a subcommand, modify args and continue |
| 2155 | 2171 | /// |
| 2156 | 2172 | /// Unlike the others, this one will always allow fallthrough |
| 2157 | 2173 | void _parse_subcommand(std::vector<std::string> &args) { |
| 2158 | 2174 | if(_count_remaining_positionals(/* required */ true) > 0) |
| 2159 | 2175 | return _parse_positional(args); |
| 2160 | - for(const App_p &com : subcommands_) { | |
| 2161 | - if(com->disabled_) | |
| 2162 | - continue; | |
| 2163 | - if(com->check_name(args.back())) { | |
| 2164 | - args.pop_back(); | |
| 2165 | - if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) == | |
| 2166 | - std::end(parsed_subcommands_)) | |
| 2167 | - parsed_subcommands_.push_back(com.get()); | |
| 2168 | - com->_parse(args); | |
| 2169 | - return; | |
| 2170 | - } | |
| 2176 | + auto com = _find_subcommand(args.back(), true); | |
| 2177 | + if(com != nullptr) { | |
| 2178 | + args.pop_back(); | |
| 2179 | + if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com) == | |
| 2180 | + std::end(parsed_subcommands_)) | |
| 2181 | + parsed_subcommands_.push_back(com); | |
| 2182 | + com->_parse(args); | |
| 2183 | + return; | |
| 2171 | 2184 | } |
| 2172 | - if(parent_ != nullptr) | |
| 2173 | - return parent_->_parse_subcommand(args); | |
| 2174 | - else | |
| 2185 | + | |
| 2186 | + if(parent_ == nullptr) | |
| 2175 | 2187 | throw HorribleError("Subcommand " + args.back() + " missing"); |
| 2188 | + | |
| 2189 | + return parent_->_parse_subcommand(args); | |
| 2176 | 2190 | } |
| 2177 | 2191 | |
| 2178 | 2192 | /// Parse a short (false) or long (true) argument, must be at the top of the list | ... | ... |
tests/SubcommandTest.cpp
| ... | ... | @@ -234,6 +234,109 @@ TEST_F(TApp, NamelessSubComPositionals) { |
| 234 | 234 | EXPECT_EQ(val, 2); |
| 235 | 235 | } |
| 236 | 236 | |
| 237 | +TEST_F(TApp, NamelessSubWithSub) { | |
| 238 | + | |
| 239 | + auto sub = app.add_subcommand(); | |
| 240 | + auto subsub = sub->add_subcommand("val"); | |
| 241 | + | |
| 242 | + args = {"val"}; | |
| 243 | + run(); | |
| 244 | + EXPECT_TRUE(subsub->parsed()); | |
| 245 | + EXPECT_TRUE(app.got_subcommand("val")); | |
| 246 | +} | |
| 247 | + | |
| 248 | +TEST_F(TApp, NamelessSubWithMultipleSub) { | |
| 249 | + | |
| 250 | + auto sub1 = app.add_subcommand(); | |
| 251 | + auto sub2 = app.add_subcommand(); | |
| 252 | + auto sub1sub1 = sub1->add_subcommand("val1"); | |
| 253 | + auto sub1sub2 = sub1->add_subcommand("val2"); | |
| 254 | + auto sub2sub1 = sub2->add_subcommand("val3"); | |
| 255 | + auto sub2sub2 = sub2->add_subcommand("val4"); | |
| 256 | + args = {"val1"}; | |
| 257 | + run(); | |
| 258 | + EXPECT_TRUE(sub1sub1->parsed()); | |
| 259 | + EXPECT_TRUE(app.got_subcommand("val1")); | |
| 260 | + | |
| 261 | + args = {"val2"}; | |
| 262 | + run(); | |
| 263 | + EXPECT_TRUE(sub1sub2->parsed()); | |
| 264 | + EXPECT_TRUE(app.got_subcommand("val2")); | |
| 265 | + | |
| 266 | + args = {"val3"}; | |
| 267 | + run(); | |
| 268 | + EXPECT_TRUE(sub2sub1->parsed()); | |
| 269 | + EXPECT_TRUE(app.got_subcommand("val3")); | |
| 270 | + | |
| 271 | + args = {"val4"}; | |
| 272 | + run(); | |
| 273 | + EXPECT_TRUE(sub2sub2->parsed()); | |
| 274 | + EXPECT_TRUE(app.got_subcommand("val4")); | |
| 275 | + | |
| 276 | + args = {"val4", "val1"}; | |
| 277 | + run(); | |
| 278 | + EXPECT_TRUE(sub2sub2->parsed()); | |
| 279 | + EXPECT_TRUE(app.got_subcommand("val4")); | |
| 280 | + EXPECT_TRUE(sub1sub1->parsed()); | |
| 281 | + EXPECT_TRUE(app.got_subcommand("val1")); | |
| 282 | +} | |
| 283 | + | |
| 284 | +TEST_F(TApp, Nameless4LayerDeep) { | |
| 285 | + | |
| 286 | + auto sub = app.add_subcommand(); | |
| 287 | + auto ssub = sub->add_subcommand(); | |
| 288 | + auto sssub = ssub->add_subcommand(); | |
| 289 | + | |
| 290 | + auto ssssub = sssub->add_subcommand(); | |
| 291 | + auto sssssub = ssssub->add_subcommand("val"); | |
| 292 | + | |
| 293 | + args = {"val"}; | |
| 294 | + run(); | |
| 295 | + EXPECT_TRUE(sssssub->parsed()); | |
| 296 | + EXPECT_TRUE(app.got_subcommand("val")); | |
| 297 | +} | |
| 298 | + | |
| 299 | +/// Put subcommands in some crazy pattern and make everything still works | |
| 300 | +TEST_F(TApp, Nameless4LayerDeepMulit) { | |
| 301 | + | |
| 302 | + auto sub1 = app.add_subcommand(); | |
| 303 | + auto sub2 = app.add_subcommand(); | |
| 304 | + auto ssub1 = sub1->add_subcommand(); | |
| 305 | + auto ssub2 = sub2->add_subcommand(); | |
| 306 | + | |
| 307 | + auto sssub1 = ssub1->add_subcommand(); | |
| 308 | + auto sssub2 = ssub2->add_subcommand(); | |
| 309 | + sssub1->add_subcommand("val1"); | |
| 310 | + ssub2->add_subcommand("val2"); | |
| 311 | + sub2->add_subcommand("val3"); | |
| 312 | + ssub1->add_subcommand("val4"); | |
| 313 | + sssub2->add_subcommand("val5"); | |
| 314 | + args = {"val1"}; | |
| 315 | + run(); | |
| 316 | + EXPECT_TRUE(app.got_subcommand("val1")); | |
| 317 | + | |
| 318 | + args = {"val2"}; | |
| 319 | + run(); | |
| 320 | + EXPECT_TRUE(app.got_subcommand("val2")); | |
| 321 | + | |
| 322 | + args = {"val3"}; | |
| 323 | + run(); | |
| 324 | + EXPECT_TRUE(app.got_subcommand("val3")); | |
| 325 | + | |
| 326 | + args = {"val4"}; | |
| 327 | + run(); | |
| 328 | + EXPECT_TRUE(app.got_subcommand("val4")); | |
| 329 | + args = {"val5"}; | |
| 330 | + run(); | |
| 331 | + EXPECT_TRUE(app.got_subcommand("val5")); | |
| 332 | + | |
| 333 | + args = {"val4", "val1", "val5"}; | |
| 334 | + run(); | |
| 335 | + EXPECT_TRUE(app.got_subcommand("val4")); | |
| 336 | + EXPECT_TRUE(app.got_subcommand("val1")); | |
| 337 | + EXPECT_TRUE(app.got_subcommand("val5")); | |
| 338 | +} | |
| 339 | + | |
| 237 | 340 | TEST_F(TApp, FallThroughRegular) { |
| 238 | 341 | app.fallthrough(); |
| 239 | 342 | int val = 1; | ... | ... |