diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index b36c1f00..b18485cd 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -1045,10 +1045,10 @@ class App { /// Check to see if a subcommand is part of this command (text version) App *get_subcommand(std::string subcom) const { - for(const App_p &subcomptr : subcommands_) - if(subcomptr->check_name(subcom)) - return subcomptr.get(); - throw OptionNotFound(subcom); + auto subc = _find_subcommand(subcom, false); + if(subc == nullptr) + throw OptionNotFound(subcom); + return subc; } /// Get a pointer to subcommand by index App *get_subcommand(int index = 0) const { @@ -1736,11 +1736,10 @@ class App { if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) { return parent_ != nullptr && parent_->_valid_subcommand(current); } - - for(const App_p &com : subcommands_) - if(!com->disabled_ && com->check_name(current) && !*com) - return true; - + auto com = _find_subcommand(current, true); + if((com != nullptr) && !*com) { + return true; + } // Check parent if exists, else return false return parent_ != nullptr && parent_->_valid_subcommand(current); } @@ -2151,28 +2150,43 @@ class App { } } + /// Locate a subcommand by name + App *_find_subcommand(const std::string &subc_name, bool ignore_disabled) const noexcept { + for(const App_p &com : subcommands_) { + if((com->disabled_) && (ignore_disabled)) + continue; + if(com->get_name().empty()) { + auto subc = com->_find_subcommand(subc_name, ignore_disabled); + if(subc != nullptr) { + return subc; + } + } else if(com->check_name(subc_name)) { + return com.get(); + } + } + return nullptr; + } + /// Parse a subcommand, modify args and continue /// /// Unlike the others, this one will always allow fallthrough void _parse_subcommand(std::vector &args) { if(_count_remaining_positionals(/* required */ true) > 0) return _parse_positional(args); - for(const App_p &com : subcommands_) { - if(com->disabled_) - continue; - if(com->check_name(args.back())) { - args.pop_back(); - if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) == - std::end(parsed_subcommands_)) - parsed_subcommands_.push_back(com.get()); - com->_parse(args); - return; - } + auto com = _find_subcommand(args.back(), true); + if(com != nullptr) { + args.pop_back(); + if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com) == + std::end(parsed_subcommands_)) + parsed_subcommands_.push_back(com); + com->_parse(args); + return; } - if(parent_ != nullptr) - return parent_->_parse_subcommand(args); - else + + if(parent_ == nullptr) throw HorribleError("Subcommand " + args.back() + " missing"); + + return parent_->_parse_subcommand(args); } /// Parse a short (false) or long (true) argument, must be at the top of the list diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 7f9d5e9c..551ffdfb 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -234,6 +234,109 @@ TEST_F(TApp, NamelessSubComPositionals) { EXPECT_EQ(val, 2); } +TEST_F(TApp, NamelessSubWithSub) { + + auto sub = app.add_subcommand(); + auto subsub = sub->add_subcommand("val"); + + args = {"val"}; + run(); + EXPECT_TRUE(subsub->parsed()); + EXPECT_TRUE(app.got_subcommand("val")); +} + +TEST_F(TApp, NamelessSubWithMultipleSub) { + + auto sub1 = app.add_subcommand(); + auto sub2 = app.add_subcommand(); + auto sub1sub1 = sub1->add_subcommand("val1"); + auto sub1sub2 = sub1->add_subcommand("val2"); + auto sub2sub1 = sub2->add_subcommand("val3"); + auto sub2sub2 = sub2->add_subcommand("val4"); + args = {"val1"}; + run(); + EXPECT_TRUE(sub1sub1->parsed()); + EXPECT_TRUE(app.got_subcommand("val1")); + + args = {"val2"}; + run(); + EXPECT_TRUE(sub1sub2->parsed()); + EXPECT_TRUE(app.got_subcommand("val2")); + + args = {"val3"}; + run(); + EXPECT_TRUE(sub2sub1->parsed()); + EXPECT_TRUE(app.got_subcommand("val3")); + + args = {"val4"}; + run(); + EXPECT_TRUE(sub2sub2->parsed()); + EXPECT_TRUE(app.got_subcommand("val4")); + + args = {"val4", "val1"}; + run(); + EXPECT_TRUE(sub2sub2->parsed()); + EXPECT_TRUE(app.got_subcommand("val4")); + EXPECT_TRUE(sub1sub1->parsed()); + EXPECT_TRUE(app.got_subcommand("val1")); +} + +TEST_F(TApp, Nameless4LayerDeep) { + + auto sub = app.add_subcommand(); + auto ssub = sub->add_subcommand(); + auto sssub = ssub->add_subcommand(); + + auto ssssub = sssub->add_subcommand(); + auto sssssub = ssssub->add_subcommand("val"); + + args = {"val"}; + run(); + EXPECT_TRUE(sssssub->parsed()); + EXPECT_TRUE(app.got_subcommand("val")); +} + +/// Put subcommands in some crazy pattern and make everything still works +TEST_F(TApp, Nameless4LayerDeepMulit) { + + auto sub1 = app.add_subcommand(); + auto sub2 = app.add_subcommand(); + auto ssub1 = sub1->add_subcommand(); + auto ssub2 = sub2->add_subcommand(); + + auto sssub1 = ssub1->add_subcommand(); + auto sssub2 = ssub2->add_subcommand(); + sssub1->add_subcommand("val1"); + ssub2->add_subcommand("val2"); + sub2->add_subcommand("val3"); + ssub1->add_subcommand("val4"); + sssub2->add_subcommand("val5"); + args = {"val1"}; + run(); + EXPECT_TRUE(app.got_subcommand("val1")); + + args = {"val2"}; + run(); + EXPECT_TRUE(app.got_subcommand("val2")); + + args = {"val3"}; + run(); + EXPECT_TRUE(app.got_subcommand("val3")); + + args = {"val4"}; + run(); + EXPECT_TRUE(app.got_subcommand("val4")); + args = {"val5"}; + run(); + EXPECT_TRUE(app.got_subcommand("val5")); + + args = {"val4", "val1", "val5"}; + run(); + EXPECT_TRUE(app.got_subcommand("val4")); + EXPECT_TRUE(app.got_subcommand("val1")); + EXPECT_TRUE(app.got_subcommand("val5")); +} + TEST_F(TApp, FallThroughRegular) { app.fallthrough(); int val = 1;