From 3419d110deb32ef2b840cfc3094b89b9e02efe88 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Mon, 6 Nov 2017 10:58:48 +0300 Subject: add help output for commands --- args.hxx | 174 ++++++++++++++++++++++++++++++++++++++------------------------- test.cxx | 35 ++++++++++--- 2 files changed, 133 insertions(+), 76 deletions(-) diff --git a/args.hxx b/args.hxx index ca4d966..f439d9f 100644 --- a/args.hxx +++ b/args.hxx @@ -488,6 +488,10 @@ namespace args /** The separator for long flags */ std::string longSeparator; + + /** The program name for help generation + */ + std::string programName; }; class FlagBase; @@ -1126,13 +1130,21 @@ namespace args friend class Subparser; std::string name; - std::string description; + std::string help; std::function parserCoroutine; bool commandIsRequired = false; + std::vector> subparserDescription; + std::vector subparserProgramLine; + bool subparserHasFlag = false; protected: + std::string description; + std::string epilog; + std::string proglinePostfix; + + Command *selectedCommand = nullptr; Subparser *subparser = nullptr; @@ -1174,13 +1186,54 @@ namespace args return *res; } + const Command &SelectedCommand() const + { + const Command *res = this; + while (res->selectedCommand != nullptr) + { + res = res->selectedCommand; + } + + return *res; + } + public: - Command(Group &base_, std::string name_, std::string description_, std::function coroutine_ = {}) - : name(std::move(name_)), description(std::move(description_)), parserCoroutine(std::move(coroutine_)) + Command(Group &base_, std::string name_, std::string help_, std::function coroutine_ = {}) + : name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_)) { base_.Add(*this); } + /** The description that appears on the prog line after options + */ + const std::string &ProglinePostfix() const + { return proglinePostfix; } + + /** The description that appears on the prog line after options + */ + void ProglinePostfix(const std::string &proglinePostfix_) + { this->proglinePostfix = proglinePostfix_; } + + /** The description that appears above options + */ + const std::string &Description() const + { return description; } + /** The description that appears above options + */ + + void Description(const std::string &description_) + { this->description = description_; } + + /** The description that appears below options + */ + const std::string &Epilog() const + { return epilog; } + + /** The description that appears below options + */ + void Epilog(const std::string &epilog_) + { this->epilog = epilog_; } + const std::function &GetCoroutine() const { return parserCoroutine; @@ -1191,6 +1244,11 @@ namespace args return name; } + const std::string &Help() const + { + return help; + } + virtual bool IsGroup() const override { return false; @@ -1295,7 +1353,7 @@ namespace args return selectedCommand->HasFlag(); } - return Matched() ? Group::HasFlag() : false; + return Matched() ? subparserHasFlag || Group::HasFlag() : false; } virtual std::vector GetProgramLine() const @@ -1307,7 +1365,9 @@ namespace args if (Matched()) { - return Group::GetProgramLine(); + auto res = Group::GetProgramLine(); + res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end()); + return res; } return {}; @@ -1336,20 +1396,14 @@ namespace args } std::vector> descriptions; - unsigned addindent = 0; - - std::tuple desc; - std::get<0>(desc) = Name(); - std::get<1>(desc) = description; - std::get<2>(desc) = indent; - if (!Name().empty()) - { - addindent = 1; - descriptions.push_back(desc); - } if (!Matched()) { + if (!Name().empty()) + { + descriptions.emplace_back(Name(), help, indent); + } + return descriptions; } @@ -1360,7 +1414,7 @@ namespace args continue; } - auto groupDescriptions = child->GetDescription(params, indent + addindent); + auto groupDescriptions = child->GetDescription(params, indent); descriptions.insert( std::end(descriptions), std::make_move_iterator(std::begin(groupDescriptions)), @@ -1369,7 +1423,7 @@ namespace args for (auto childDescription: subparserDescription) { - std::get<2>(childDescription) += indent + addindent; + std::get<2>(childDescription) += indent; descriptions.push_back(std::move(childDescription)); } @@ -1409,11 +1463,6 @@ namespace args friend class Subparser; private: - std::string prog; - std::string proglinePostfix; - std::string description; - std::string epilog; - std::string longprefix; std::string shortprefix; @@ -1697,14 +1746,14 @@ namespace args public: HelpParams helpParams; - ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string()) : - description(description_), - epilog(epilog_), - terminator("--") + ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string()) { + Description(description_); + Epilog(epilog); LongPrefix("--"); ShortPrefix("-"); LongSeparator("="); + Terminator("--"); SetArgumentSeparations(true, true, true, true); matched = true; } @@ -1712,38 +1761,11 @@ namespace args /** The program name for help generation */ const std::string &Prog() const - { return prog; } + { return helpParams.programName; } /** The program name for help generation */ void Prog(const std::string &prog_) - { this->prog = prog_; } - - /** The description that appears on the prog line after options - */ - const std::string &ProglinePostfix() const - { return proglinePostfix; } - /** The description that appears on the prog line after options - */ - void ProglinePostfix(const std::string &proglinePostfix_) - { this->proglinePostfix = proglinePostfix_; } - - /** The description that appears above options - */ - const std::string &Description() const - { return description; } - /** The description that appears above options - */ - void Description(const std::string &description_) - { this->description = description_; } - - /** The description that appears below options - */ - const std::string &Epilog() const - { return epilog; } - /** The description that appears below options - */ - void Epilog(const std::string &epilog_) - { this->epilog = epilog_; } + { this->helpParams.programName = prog_; } /** The prefix for long flags */ @@ -1845,10 +1867,19 @@ namespace args bool hasoptions = false; bool hasarguments = false; - const auto description_text = Wrap(this->description, helpParams.width - helpParams.descriptionindent); - const auto epilog_text = Wrap(this->epilog, helpParams.width - helpParams.descriptionindent); + auto &command = SelectedCommand(); + const auto &description = command.Description().empty() ? command.Help() : command.Description(); + const auto description_text = Wrap(description, helpParams.width - helpParams.descriptionindent); + const auto epilog_text = Wrap(command.Epilog(), helpParams.width - helpParams.descriptionindent); + std::ostringstream prognameline; - prognameline << prog; + prognameline << Prog(); + + if (!command.Name().empty()) + { + prognameline << ' ' << command.Name(); + } + if (HasFlag()) { hasoptions = true; @@ -1857,7 +1888,8 @@ namespace args prognameline << " {OPTIONS}"; } } - for (const std::string &posname: GetProgramLine()) + + for (const std::string &posname: command.GetProgramLine()) { hasarguments = true; if (helpParams.showProglinePositionals) @@ -1865,9 +1897,9 @@ namespace args prognameline << " [" << posname << ']'; } } - if (!proglinePostfix.empty()) + if (!command.ProglinePostfix().empty()) { - prognameline << ' ' << proglinePostfix; + prognameline << ' ' << command.ProglinePostfix(); } const auto proglines = Wrap(prognameline.str(), helpParams.width - (helpParams.progindent + 4), helpParams.width - helpParams.progindent); auto progit = std::begin(proglines); @@ -1883,13 +1915,17 @@ namespace args help_ << '\n'; - for (const auto &line: description_text) + if (!description_text.empty()) { - help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; + for (const auto &line: description_text) + { + help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; + } + help_ << "\n"; } - help_ << "\n"; + help_ << std::string(helpParams.progindent, ' ') << "OPTIONS:\n\n"; - for (const auto &desc: Group::GetDescription(helpParams, 0)) + for (const auto &desc: command.GetDescription(helpParams, 0)) { const auto groupindent = std::get<2>(desc) * helpParams.eachgroupindent; const auto flags = Wrap(std::get<0>(desc), helpParams.width - (helpParams.flagindent + helpParams.helpindent + helpParams.gutter)); @@ -1989,9 +2025,9 @@ namespace args */ bool ParseCLI(const int argc, const char * const * argv) { - if (prog.empty()) + if (Prog().empty()) { - prog.assign(argv[0]); + Prog(argv[0]); } const std::vector args(argv + 1, argv + argc); return ParseArgs(args) == std::end(args); @@ -2007,6 +2043,8 @@ namespace args { isParsed = true; command.subparserDescription = GetDescription(parser.helpParams, 0); + command.subparserHasFlag = HasFlag(); + command.subparserProgramLine = GetProgramLine(); auto it = parser.Parse(args.begin(), args.end()); kicked.assign(it, args.end()); } diff --git a/test.cxx b/test.cxx index e95786e..18b8c69 100644 --- a/test.cxx +++ b/test.cxx @@ -763,17 +763,36 @@ TEST_CASE("Subparser help works as expected", "[args]") c.Parse(); }); + p.Prog("git"); + + std::ostringstream s; + auto d = p.GetDescription(p.helpParams, 0); - REQUIRE(d.size() == 3); - REQUIRE(std::get<0>(d[0]) == "-g"); - REQUIRE(std::get<0>(d[1]) == "add"); - REQUIRE(std::get<0>(d[2]) == "commit"); + s << p; + REQUIRE(s.str() == R"( git {OPTIONS} + + git-like parser + + OPTIONS: + + -g global flag + add add file contents to the index + commit record changes to the repository + +)"); p.ParseArgs(std::vector{"add"}); - d = p.GetDescription(p.helpParams, 0); - REQUIRE(d.size() == 2); - REQUIRE(std::get<0>(d[0]) == "add"); - REQUIRE(std::get<0>(d[1]) == "-f"); + s.str(""); + s << p; + REQUIRE(s.str() == R"( git add {OPTIONS} + + add file contents to the index + + OPTIONS: + + -f flag + +)"); } #undef ARGS_HXX -- cgit v1.2.1