From da501db8a06b31bf771767302ddad065524d5e67 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sun, 12 Nov 2017 16:34:21 +0300 Subject: add HelpParams::proglineShowFlags --- args.hxx | 228 ++++++++++++++++++++++++++++++++++++++++++--------------------- test.cxx | 49 ++++++++++++++ 2 files changed, 202 insertions(+), 75 deletions(-) diff --git a/args.hxx b/args.hxx index 8839253..817835d 100644 --- a/args.hxx +++ b/args.hxx @@ -291,6 +291,11 @@ namespace args { return isShort ? std::string(1, shortFlag) : longFlag; } + + std::string str(const std::string &shortPrefix, const std::string &longPrefix) const + { + return isShort ? shortPrefix + std::string(1, shortFlag) : longPrefix + longFlag; + } }; @@ -316,7 +321,22 @@ namespace args Matcher(ShortIt shortFlagsStart, ShortIt shortFlagsEnd, LongIt longFlagsStart, LongIt longFlagsEnd) : shortFlags(shortFlagsStart, shortFlagsEnd), longFlags(longFlagsStart, longFlagsEnd) - {} + { + if (shortFlags.empty() && longFlags.empty()) + { +#ifndef ARGS_NOEXCEPT + throw UsageError("empty Matcher"); +#endif + } + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + Error GetError() const noexcept + { + return shortFlags.empty() && longFlags.empty() ? Error::Usage : Error::None; + } +#endif /** Specify short and long flags separately as iterables * @@ -370,37 +390,55 @@ namespace args /** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded */ - std::vector GetFlagStrings(const std::string &shortPrefix, const std::string &longPrefix) const + std::vector GetFlagStrings() const { - std::vector flagStrings; + std::vector flagStrings; flagStrings.reserve(shortFlags.size() + longFlags.size()); for (const char flag: shortFlags) { - flagStrings.emplace_back(shortPrefix + std::string(1, flag)); + flagStrings.emplace_back(flag); } for (const std::string &flag: longFlags) { - flagStrings.emplace_back(longPrefix + flag); + flagStrings.emplace_back(flag); } return flagStrings; } - /** (INTERNAL) Get all flag strings as a vector, with the prefixes and names embedded + /** (INTERNAL) Get long flag if it exists or any short flag */ - std::vector GetFlagStrings(const std::string &shortPrefix, const std::string &longPrefix, const std::string &name, const std::string &shortSeparator, const std::string longSeparator) const + EitherFlag GetLongOrAny() const { - const std::string bracedname(std::string("[") + name + "]"); - std::vector flagStrings; - flagStrings.reserve(shortFlags.size() + longFlags.size()); - for (const char flag: shortFlags) + if (!longFlags.empty()) { - flagStrings.emplace_back(shortPrefix + std::string(1, flag) + shortSeparator + bracedname); + return *longFlags.begin(); } - for (const std::string &flag: longFlags) + + if (!shortFlags.empty()) { - flagStrings.emplace_back(longPrefix + flag + longSeparator + bracedname); + return *shortFlags.begin(); } - return flagStrings; + + // should be unreachable + return ' '; + } + + /** (INTERNAL) Get short flag if it exists or any long flag + */ + EitherFlag GetShortOrAny() const + { + if (!shortFlags.empty()) + { + return *shortFlags.begin(); + } + + if (!longFlags.empty()) + { + return *longFlags.begin(); + } + + // should be unreachable + return ' '; } }; @@ -443,6 +481,11 @@ namespace args return static_cast(static_cast(lhs) & static_cast(rhs)); } + class FlagBase; + class PositionalBase; + class Command; + class ArgumentParser; + /** A simple structure of parameters for easy user-modifyable help menus */ struct HelpParams @@ -520,12 +563,15 @@ namespace args /** The prefix for progline when command has any subcommands */ std::string proglineCommand = "COMMAND"; - }; - class FlagBase; - class PositionalBase; - class Command; - class ArgumentParser; + /** Show flags in program line + */ + bool proglineShowFlags = false; + + /** Use short flags in program lines when possible + */ + bool proglinePreferShortFlags = false; + }; /** Base class for all match types */ @@ -551,6 +597,11 @@ namespace args return options; } + bool IsRequired() const noexcept + { + return (GetOptions() & Options::Required) != Options::None; + } + virtual bool Matched() const noexcept { return matched; @@ -698,6 +749,16 @@ namespace args Nargs(size_t num_) : min(num_), max(num_) { } + + friend bool operator == (const Nargs &lhs, const Nargs &rhs) + { + return lhs.min == rhs.min && lhs.max == rhs.max; + } + + friend bool operator != (const Nargs &lhs, const Nargs &rhs) + { + return !(lhs == rhs); + } }; /** Base class for all flag options @@ -736,7 +797,7 @@ namespace args virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override { - if (!Matched() && (GetOptions() & Options::Required) != Options::None) + if (!Matched() && IsRequired()) { #ifdef ARGS_NOEXCEPT (void)shortPrefix; @@ -744,26 +805,51 @@ namespace args error = Error::Required; #else std::ostringstream problem; - problem << "Flag '" << matcher.GetFlagStrings(shortPrefix, longPrefix).at(0) << "' is required"; + problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required"; throw RequiredError(problem.str()); #endif } } + virtual std::vector GetProgramLine(const HelpParams ¶ms) const override + { + if (!params.proglineShowFlags) + { + return {}; + } + + const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name(); + const EitherFlag flag = params.proglinePreferShortFlags ? matcher.GetShortOrAny() : matcher.GetLongOrAny(); + std::string res = flag.str(params.shortPrefix, params.longPrefix); + if (!postfix.empty()) + { + res += " <" + postfix + ">"; + } + + return { IsRequired() ? res : "[" + res + "]" }; + } + virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override { std::tuple description; - const auto flagStrings = matcher.GetFlagStrings(params.shortPrefix, params.longPrefix); - std::ostringstream flagstream; - for (auto it = std::begin(flagStrings); it != std::end(flagStrings); ++it) + const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name(); + std::string flags; + for (const auto &flag : matcher.GetFlagStrings()) { - if (it != std::begin(flagStrings)) + if (!flags.empty()) + { + flags += ", "; + } + + flags += flag.isShort ? params.shortPrefix : params.longPrefix; + flags += flag.str(); + if (!postfix.empty()) { - flagstream << ", "; + flags += flag.isShort ? params.shortSeparator : params.longSeparator; + flags += "[" + postfix + "]"; } - flagstream << *it; } - std::get<0>(description) = flagstream.str(); + std::get<0>(description) = std::move(flags); std::get<1>(description) = help; std::get<2>(description) = indentLevel; return { std::move(description) }; @@ -784,6 +870,12 @@ namespace args return Error::Usage; } + const auto matcherError = matcher.GetError(); + if (matcherError != Error::None) + { + return matcherError; + } + return error; } #endif @@ -810,25 +902,6 @@ namespace args ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {} virtual ~ValueFlagBase() {} - virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override - { - std::tuple description; - const auto flagStrings = matcher.GetFlagStrings(params.shortPrefix, params.longPrefix, Name(), params.shortSeparator, params.longSeparator); - std::ostringstream flagstream; - for (auto it = std::begin(flagStrings); it != std::end(flagStrings); ++it) - { - if (it != std::begin(flagStrings)) - { - flagstream << ", "; - } - flagstream << *it; - } - std::get<0>(description) = flagstream.str(); - std::get<1>(description) = help; - std::get<2>(description) = indentLevel; - return { std::move(description) }; - } - virtual Nargs NumberOfArguments() const noexcept override { return 1; @@ -874,12 +947,12 @@ namespace args virtual std::vector GetProgramLine(const HelpParams &) const override { - return { "[" + Name() + ']' }; + return { IsRequired() ? Name() : "[" + Name() + ']' }; } virtual void Validate(const std::string &, const std::string &) const override { - if ((GetOptions() & Options::Required) != Options::None && !Matched()) + if (IsRequired() && !Matched()) { #ifdef ARGS_NOEXCEPT error = Error::Required; @@ -1327,6 +1400,25 @@ namespace args return *res; } + void UpdateSubparserHelp(const HelpParams ¶ms) const + { + if (parserCoroutine) + { + RaiiSubparser coro(*this, params); +#ifndef ARGS_NOEXCEPT + try + { + parserCoroutine(coro.Parser()); + } + catch (args::SubparserError) + { + } +#else + parserCoroutine(coro.Parser()); +#endif + } + } + public: Command(Group &base_, std::string name_, std::string help_, std::function coroutine_ = {}) : name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_)) @@ -1482,6 +1574,8 @@ namespace args std::vector GetCommandProgramLine(const HelpParams ¶ms) const { + UpdateSubparserHelp(params); + auto res = Group::GetProgramLine(params); res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end()); @@ -1495,7 +1589,7 @@ namespace args res.insert(res.begin(), Name()); } - if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions) + if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions && !params.proglineShowFlags) { res.push_back(params.proglineOptions); } @@ -1510,8 +1604,12 @@ namespace args virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { - auto &command = SelectedCommand(); - return command.Matched() ? command.GetCommandProgramLine(params) : std::vector(); + if (!Matched()) + { + return {}; + } + + return GetCommandProgramLine(params); } virtual std::vector GetCommands() override @@ -1531,30 +1629,10 @@ namespace args virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override { - if (selectedCommand != nullptr) - { - return selectedCommand->GetDescription(params, indent); - } - std::vector> descriptions; unsigned addindent = 0; - if ((params.showCommandChildren || params.showCommandFullHelp) && !Matched() && parserCoroutine) - { - RaiiSubparser coro(*this, params); -#ifndef ARGS_NOEXCEPT - try - { - parserCoroutine(coro.Parser()); - } - catch (args::SubparserError) - { - } -#else - parserCoroutine(coro.Parser()); -#endif - } - + UpdateSubparserHelp(params); if (!Matched()) { @@ -1562,7 +1640,7 @@ namespace args { std::ostringstream s; bool empty = true; - for (const auto &progline : GetCommandProgramLine(params)) + for (const auto &progline: GetCommandProgramLine(params)) { if (!empty) { diff --git a/test.cxx b/test.cxx index ba4d5f8..3376059 100644 --- a/test.cxx +++ b/test.cxx @@ -909,6 +909,55 @@ TEST_CASE("Global options work as expected", "[args]") REQUIRE_NOTHROW(p.ParseArgs(std::vector{"b", "-f"})); } +TEST_CASE("GetProgramLine works as expected", "[args]") +{ + args::ArgumentParser p("parser"); + args::Flag g(p, "g", "g", {'g'}, args::Options::Global); + args::Flag hidden(p, "hidden", "hidden flag", {'h'}, args::Options::Hidden); + args::Command a(p, "a", "command a", [](args::Subparser &s) + { + args::ValueFlag f(s, "STRING", "my f flag", {'f', "f-long"}, args::Options::Required); + args::Positional pos(s, "positional", "positional", args::Options::Required); + s.Parse(); + }); + + args::Command b(p, "b", "command b"); + args::ValueFlag f(b, "STRING", "my f flag", {'f'}, args::Options::Required); + args::Positional pos(b, "positional", "positional"); + + auto line = [&](args::Command &element) + { + p.Reset(); + auto strings = element.GetCommandProgramLine(p.helpParams); + std::string res; + for (const std::string &s: strings) + { + if (!res.empty()) + { + res += ' '; + } + + res += s; + } + + return res; + }; + + REQUIRE(line(p) == "COMMAND {OPTIONS}"); + REQUIRE(line(a) == "a positional {OPTIONS}"); + REQUIRE(line(b) == "b [positional] {OPTIONS}"); + + p.helpParams.proglineShowFlags = true; + REQUIRE(line(p) == "COMMAND [-g]"); + REQUIRE(line(a) == "a --f-long positional"); + REQUIRE(line(b) == "b -f [positional]"); + + p.helpParams.proglinePreferShortFlags = true; + REQUIRE(line(p) == "COMMAND [-g]"); + REQUIRE(line(a) == "a -f positional"); + REQUIRE(line(b) == "b -f [positional]"); +} + #undef ARGS_HXX #define ARGS_TESTNAMESPACE #define ARGS_NOEXCEPT -- cgit v1.2.1