From debaca70bc9ac882a210c4efd3761edf3935f10d Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 11:27:03 +0300 Subject: change GetChoicesString() signature --- args.hxx | 137 +++++++++++++++++++++++++++++++++------------------------------ test.cxx | 10 ++--- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/args.hxx b/args.hxx index 37e86b4..ba0a010 100644 --- a/args.hxx +++ b/args.hxx @@ -705,6 +705,40 @@ namespace args std::string defaultString = "\nDefault: "; }; + /** A number of arguments which can be consumed by an option. + * + * Represents a closed interval [min, max]. + */ + struct Nargs + { + const size_t min; + const size_t max; + + Nargs(size_t min_, size_t max_) : min(min_), max(max_) + { +#ifndef ARGS_NOEXCEPT + if (max < min) + { + throw UsageError("Nargs: max > min"); + } +#endif + } + + 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 match types */ class Base @@ -841,12 +875,12 @@ namespace args bool kickout = false; std::string defaultString; bool defaultStringManual = false; - std::string choicesString; + std::vector choicesStrings; bool choicesStringManual = false; virtual std::string GetDefaultString(const HelpParams&) const { return {}; } - virtual std::string GetChoicesString(const HelpParams&) const { return {}; } + virtual std::vector GetChoicesStrings(const HelpParams&) const { return {}; } virtual std::string GetNameString(const HelpParams&) const { return Name(); } @@ -884,20 +918,20 @@ namespace args return GetDefaultString(params); } - /** Sets choices string that will be added to argument description. - * Use empty string to disable it for this argument. + /** Sets choices strings that will be added to argument description. + * Use empty vector to disable it for this argument. */ - void HelpChoices(const std::string &str) + void HelpChoices(const std::vector &array) { choicesStringManual = true; - choicesString = str; + choicesStrings = array; } - /** Gets choices string that will be added to argument description. + /** Gets choices strings that will be added to argument description. */ - std::string HelpChoices(const HelpParams ¶ms) const + std::vector HelpChoices(const HelpParams ¶ms) const { - return GetChoicesString(params); + return GetChoicesStrings(params); } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override @@ -907,7 +941,23 @@ namespace args std::get<1>(description) = help; std::get<2>(description) = indentLevel; - AddDescriptionPostfix(std::get<1>(description), choicesStringManual, choicesString, params.addChoices, GetChoicesString(params), params.choiceString); + auto join = [](const std::vector &array) -> std::string + { + std::string res; + + for (auto &str : array) + { + if (!res.empty()) + { + res += ", "; + } + res += str; + } + + return res; + }; + + AddDescriptionPostfix(std::get<1>(description), choicesStringManual, join(choicesStrings), params.addChoices, join(GetChoicesStrings(params)), params.choiceString); AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault, GetDefaultString(params), params.defaultString); return { std::move(description) }; @@ -919,40 +969,6 @@ namespace args } }; - /** A number of arguments which can be consumed by an option. - * - * Represents a closed interval [min, max]. - */ - struct Nargs - { - const size_t min; - const size_t max; - - Nargs(size_t min_, size_t max_) : min(min_), max(max_) - { -#ifndef ARGS_NOEXCEPT - if (max < min) - { - throw UsageError("Nargs: max > min"); - } -#endif - } - - 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); - } - }; - namespace detail { template @@ -978,27 +994,18 @@ namespace args } template - std::string MapKeysToString(const T &map) + std::vector MapKeysToStrings(const T &map) { - std::string res; + std::vector res; using K = typename std::decayfirst)>::type; if (IsConvertableToString::value) { - std::vector values; for (const auto &p : map) { - values.push_back(detail::ToString(p.first)); + res.push_back(detail::ToString(p.first)); } - std::sort(values.begin(), values.end()); - for (const auto &s : values) - { - if (!res.empty()) - { - res += ", "; - } - res += s; - } + std::sort(res.begin(), res.end()); } return res; } @@ -3241,9 +3248,9 @@ namespace args Reader reader; protected: - virtual std::string GetChoicesString(const HelpParams &) const override + virtual std::vector GetChoicesStrings(const HelpParams &) const override { - return detail::MapKeysToString(map); + return detail::MapKeysToStrings(map); } public: @@ -3323,9 +3330,9 @@ namespace args Reader reader; protected: - virtual std::string GetChoicesString(const HelpParams &) const override + virtual std::vector GetChoicesStrings(const HelpParams &) const override { - return detail::MapKeysToString(map); + return detail::MapKeysToStrings(map); } public: @@ -3600,9 +3607,9 @@ namespace args Reader reader; protected: - virtual std::string GetChoicesString(const HelpParams &) const override + virtual std::vector GetChoicesStrings(const HelpParams &) const override { - return detail::MapKeysToString(map); + return detail::MapKeysToStrings(map); } public: @@ -3675,9 +3682,9 @@ namespace args Reader reader; protected: - virtual std::string GetChoicesString(const HelpParams &) const override + virtual std::vector GetChoicesStrings(const HelpParams &) const override { - return detail::MapKeysToString(map); + return detail::MapKeysToStrings(map); } public: diff --git a/test.cxx b/test.cxx index 01decbe..5be6ff7 100644 --- a/test.cxx +++ b/test.cxx @@ -1203,7 +1203,7 @@ TEST_CASE("Default values work as expected", "[args]") )"); f.HelpDefault("123"); - b.HelpChoices("1, 2, 3"); + b.HelpChoices({"1", "2", "3"}); REQUIRE(p.Help() == R"( prog {OPTIONS} parser @@ -1239,10 +1239,10 @@ TEST_CASE("Choices description works as expected", "[args]") args::MapPositional mappos(p, "mappos", "mappos", {{"1",1}, {"2", 2}}); args::MapPositionalList mapposlist(p, "mapposlist", "mapposlist", {{'1',1}, {'2', 2}}); - REQUIRE(map.HelpChoices(p.helpParams) == "1, 2"); - REQUIRE(maplist.HelpChoices(p.helpParams) == "1, 2"); - REQUIRE(mappos.HelpChoices(p.helpParams) == "1, 2"); - REQUIRE(mapposlist.HelpChoices(p.helpParams) == "1, 2"); + REQUIRE(map.HelpChoices(p.helpParams) == std::vector{"1", "2"}); + REQUIRE(maplist.HelpChoices(p.helpParams) == std::vector{"1", "2"}); + REQUIRE(mappos.HelpChoices(p.helpParams) == std::vector{"1", "2"}); + REQUIRE(mapposlist.HelpChoices(p.helpParams) == std::vector{"1", "2"}); } #undef ARGS_HXX -- cgit v1.2.1 From a8b96a749d28465af2a45ccfe2b46c6fd96018b2 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 15:55:28 +0300 Subject: add auto completion for flags --- args.hxx | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- test.cxx | 14 +++ 2 files changed, 293 insertions(+), 30 deletions(-) diff --git a/args.hxx b/args.hxx index ba0a010..2165f10 100644 --- a/args.hxx +++ b/args.hxx @@ -165,6 +165,26 @@ namespace args return output; } + namespace detail + { + template + std::string Join(const T& array, const std::string &delimiter) + { + std::string res; + for (auto &element : array) + { + if (!res.empty()) + { + res += delimiter; + } + + res += element; + } + + return res; + } + } + /** (INTERNAL) Wrap a string into a vector of lines * * This is quick and hacky, but works well enough. You can specify a @@ -218,6 +238,7 @@ namespace args Extra, Help, Subparser, + Completion, }; #else /** Base error class @@ -226,7 +247,7 @@ namespace args { public: Error(const std::string &problem) : std::runtime_error(problem) {} - virtual ~Error() {}; + virtual ~Error() {} }; /** Errors that occur during usage @@ -235,7 +256,7 @@ namespace args { public: UsageError(const std::string &problem) : Error(problem) {} - virtual ~UsageError() {}; + virtual ~UsageError() {} }; /** Errors that occur during regular parsing @@ -244,7 +265,7 @@ namespace args { public: ParseError(const std::string &problem) : Error(problem) {} - virtual ~ParseError() {}; + virtual ~ParseError() {} }; /** Errors that are detected from group validation after parsing finishes @@ -253,7 +274,7 @@ namespace args { public: ValidationError(const std::string &problem) : Error(problem) {} - virtual ~ValidationError() {}; + virtual ~ValidationError() {} }; /** Errors that when a required flag is omitted @@ -262,7 +283,7 @@ namespace args { public: RequiredError(const std::string &problem) : ValidationError(problem) {} - virtual ~RequiredError() {}; + virtual ~RequiredError() {} }; /** Errors in map lookups @@ -271,7 +292,7 @@ namespace args { public: MapError(const std::string &problem) : ParseError(problem) {} - virtual ~MapError() {}; + virtual ~MapError() {} }; /** Error that occurs when a singular flag is specified multiple times @@ -280,7 +301,7 @@ namespace args { public: ExtraError(const std::string &problem) : ParseError(problem) {} - virtual ~ExtraError() {}; + virtual ~ExtraError() {} }; /** An exception that indicates that the user has requested help @@ -289,7 +310,7 @@ namespace args { public: Help(const std::string &flag) : Error(flag) {} - virtual ~Help() {}; + virtual ~Help() {} }; /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers. @@ -298,7 +319,16 @@ namespace args { public: SubparserError() : Error("") {} - virtual ~SubparserError() {}; + virtual ~SubparserError() {} + }; + + /** An exception that contains autocompletion reply + */ + class Completion : public Error + { + public: + Completion(const std::string &flag) : Error(flag) {} + virtual ~Completion() {} }; #endif @@ -530,9 +560,13 @@ namespace args */ KickOut = 0x20, + /** Flag is excluded from auto completion. + */ + HiddenFromCompletion = 0x40, + /** Flag is excluded from options help and usage line */ - Hidden = HiddenFromUsage | HiddenFromDescription, + Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion, }; inline Options operator | (Options lhs, Options rhs) @@ -810,6 +844,11 @@ namespace args return nullptr; } + virtual std::vector GetAllFlags() + { + return {}; + } + virtual bool HasFlag() const { return false; @@ -915,7 +954,7 @@ namespace args */ std::string HelpDefault(const HelpParams ¶ms) const { - return GetDefaultString(params); + return defaultStringManual ? defaultString : GetDefaultString(params); } /** Sets choices strings that will be added to argument description. @@ -931,7 +970,7 @@ namespace args */ std::vector HelpChoices(const HelpParams ¶ms) const { - return GetChoicesStrings(params); + return choicesStringManual ? choicesStrings : GetChoicesStrings(params); } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override @@ -941,23 +980,7 @@ namespace args std::get<1>(description) = help; std::get<2>(description) = indentLevel; - auto join = [](const std::vector &array) -> std::string - { - std::string res; - - for (auto &str : array) - { - if (!res.empty()) - { - res += ", "; - } - res += str; - } - - return res; - }; - - AddDescriptionPostfix(std::get<1>(description), choicesStringManual, join(choicesStrings), params.addChoices, join(GetChoicesStrings(params)), params.choiceString); + AddDescriptionPostfix(std::get<1>(description), choicesStringManual, detail::Join(choicesStrings, ", "), params.addChoices, detail::Join(GetChoicesStrings(params), ", "), params.choiceString); AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault, GetDefaultString(params), params.defaultString); return { std::move(description) }; @@ -1072,6 +1095,16 @@ namespace args return nullptr; } + virtual std::vector GetAllFlags() override + { + return { this }; + } + + const Matcher &GetMatcher() const + { + return matcher; + } + virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override { if (!Matched() && IsRequired()) @@ -1160,6 +1193,49 @@ namespace args } }; + class CompletionFlag : public ValueFlagBase + { + public: + std::vector reply; + size_t cword = 0; + std::string syntax; + + template + CompletionFlag(GroupClass &group_, Matcher &&matcher_): ValueFlagBase("completion", "completion flag", std::move(matcher_), Options::Hidden) + { + group_.AddCompletion(*this); + } + + virtual ~CompletionFlag() {} + + virtual Nargs NumberOfArguments() const noexcept override + { + return 2; + } + + virtual void ParseValue(const std::vector &value_) override + { + syntax = value_.at(0); + std::istringstream(value_.at(1)) >> cword; + } + + /** Get the completion reply + */ + std::string Get() noexcept + { + return detail::Join(reply, "\n"); + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + cword = 0; + syntax.clear(); + reply.clear(); + } + }; + + /** Base class for positional options */ class PositionalBase : public NamedBase @@ -1318,6 +1394,17 @@ namespace args return nullptr; } + virtual std::vector GetAllFlags() override + { + std::vector res; + for (Base *child: Children()) + { + auto childRes = child->GetAllFlags(); + res.insert(res.end(), childRes.begin(), childRes.end()); + } + return res; + } + virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override { for (Base *child: Children()) @@ -1779,6 +1866,33 @@ namespace args return Matched() ? Group::Match(flag) : nullptr; } + virtual std::vector GetAllFlags() override + { + std::vector res; + + if (!Matched()) + { + return res; + } + + for (auto *child: Children()) + { + if (selectedCommand == nullptr || (child->GetOptions() & Options::Global) != Options::None) + { + auto childFlags = child->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + } + + if (subparser != nullptr) + { + auto childFlags = subparser->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + + return res; + } + virtual PositionalBase *GetNextPositional() override { if (selectedCommand != nullptr) @@ -2084,6 +2198,9 @@ namespace args bool allowSeparateShortValue = true; bool allowSeparateLongValue = true; + CompletionFlag *completion = nullptr; + bool readCompletion = false; + protected: enum class OptionType { @@ -2287,16 +2404,105 @@ namespace args return true; } + bool AddCompletionReply(const std::string &cur, const std::string &choice) + { + if (cur.empty() || choice.find(cur) == 0) + { + completion->reply.push_back(choice); + return true; + } + + return false; + } + + template + bool Complete(It it, It end) + { + auto nextIt = it; + if (!readCompletion || (++nextIt != end)) + { + return false; + } + + const auto &chunk = *it; + auto pos = GetNextPositional(); + std::vector commands = GetCommands(); + + if (!commands.empty() && (chunk.empty() || ParseOption(chunk) == OptionType::Positional)) + { + for (auto &cmd : commands) + { + if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + AddCompletionReply(chunk, cmd->Name()); + } + } + } else + { + bool hasPositionalCompletion = true; + + if (!commands.empty()) + { + for (auto &cmd : commands) + { + if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + AddCompletionReply(chunk, cmd->Name()); + } + } + } else if (pos) + { + if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + auto choices = pos->HelpChoices(helpParams); + hasPositionalCompletion = !choices.empty(); + for (auto &choice : choices) + { + AddCompletionReply(chunk, choice); + } + } + } + + if (hasPositionalCompletion) + { + auto flags = GetAllFlags(); + for (auto flag : flags) + { + if ((flag->GetOptions() & Options::HiddenFromCompletion) != Options::None) + { + continue; + } + + auto &matcher = flag->GetMatcher(); + if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) + { + AddCompletionReply(chunk, matcher.GetLongOrAny().str(shortprefix, longprefix)); + } + } + } + } + +#ifndef ARGS_NOEXCEPT + throw Completion(completion->Get()); +#else + return true; +#endif + } + template It Parse(It begin, It end) { bool terminated = false; - std::vector commands = GetCommands(); // Check all arg chunks for (auto it = begin; it != end; ++it) { + if (Complete(it, end)) + { + return end; + } + const auto &chunk = *it; if (!terminated && chunk == terminator) @@ -2378,6 +2584,42 @@ namespace args #endif } } + + if (!readCompletion && completion != nullptr && completion->Matched()) + { +#ifdef ARGS_NOEXCEPT + error = Error::Completion; +#endif + readCompletion = true; + ++it; + size_t argsLeft = std::distance(it, end); + if (completion->cword == 0 || argsLeft <= 1 || completion->cword >= argsLeft) + { +#ifndef ARGS_NOEXCEPT + throw Completion(""); +#endif + } + + std::vector curArgs(++it, end); + curArgs.resize(completion->cword); +#ifndef ARGS_NOEXCEPT + try + { + Parse(curArgs.begin(), curArgs.end()); + throw Completion(""); + } + catch (Completion &e) + { + throw; + } + catch (args::Error&) + { + throw Completion(""); + } +#else + return Parse(curArgs.begin(), curArgs.end()); +#endif + } } Validate(shortprefix, longprefix); @@ -2399,6 +2641,12 @@ namespace args matched = true; } + void AddCompletion(CompletionFlag &completionFlag) + { + completion = &completionFlag; + Add(completionFlag); + } + /** The program name for help generation */ const std::string &Prog() const @@ -2620,6 +2868,7 @@ namespace args { Command::Reset(); matched = true; + readCompletion = false; } /** Parse all arguments. diff --git a/test.cxx b/test.cxx index 5be6ff7..2d3b625 100644 --- a/test.cxx +++ b/test.cxx @@ -1245,6 +1245,20 @@ TEST_CASE("Choices description works as expected", "[args]") REQUIRE(mapposlist.HelpChoices(p.helpParams) == std::vector{"1", "2"}); } +TEST_CASE("Completion works as expected", "[args]") +{ + using namespace Catch::Matchers; + + args::ArgumentParser p("parser"); + args::CompletionFlag c(p, {"completion"}); + args::ValueFlag f(p, "name", "description", {'f', "foo"}, "abc"); + args::ValueFlag b(p, "name", "description", {'b', "bar"}, "abc"); + + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-f"}), Equals("-f")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar")); +} + #undef ARGS_HXX #define ARGS_TESTNAMESPACE #define ARGS_NOEXCEPT -- cgit v1.2.1 From f0ae519bdf43a67cfb6b238e28bdcc75917d94de Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 16:30:37 +0300 Subject: add completion for flag values --- args.hxx | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- test.cxx | 5 +++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/args.hxx b/args.hxx index 2165f10..91cd368 100644 --- a/args.hxx +++ b/args.hxx @@ -2224,6 +2224,28 @@ namespace args return OptionType::Positional; } + template + bool Complete(FlagBase &flag, It it, It end) + { + auto nextIt = it; + if (!readCompletion || (++nextIt != end)) + { + return false; + } + + const auto &chunk = *it; + for (auto &choice : flag.HelpChoices(helpParams)) + { + AddCompletionReply(chunk, choice); + } + +#ifndef ARGS_NOEXCEPT + throw Completion(completion->Get()); +#else + return true; +#endif + } + /** (INTERNAL) Parse flag's values * * \param arg The string to display in error message as a flag name @@ -2269,6 +2291,11 @@ namespace args values.size() < nargs.max && (nargs.min == nargs.max || ParseOption(*valueIt) == OptionType::Positional)) { + if (Complete(flag, valueIt, end)) + { + it = end; + return ""; + } values.push_back(*valueIt); ++it; @@ -2476,7 +2503,43 @@ namespace args auto &matcher = flag->GetMatcher(); if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) { - AddCompletionReply(chunk, matcher.GetLongOrAny().str(shortprefix, longprefix)); + for (auto &name : matcher.GetFlagStrings()) + { + if (AddCompletionReply(chunk, name.str(shortprefix, longprefix))) + { + break; + } + } + } + } + + if (ParseOption(chunk) == OptionType::LongFlag && allowJoinedLongValue) + { + const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator); + if (separator != chunk.npos) + { + std::string arg(chunk, 0, separator); + if (auto flag = this->Match(arg.substr(longprefix.size()))) + { + for (auto &choice : flag->HelpChoices(helpParams)) + { + AddCompletionReply(chunk, arg + longseparator + choice); + } + } + } + } else if (ParseOption(chunk) == OptionType::ShortFlag && allowJoinedShortValue) + { + if (chunk.size() > shortprefix.size() + 1) + { + auto name = chunk.at(shortprefix.size()); + //TODO: support -abcVALUE where a and b take no value + if (auto flag = this->Match(name)) + { + for (auto &choice : flag->HelpChoices(helpParams)) + { + AddCompletionReply(chunk, shortprefix + name + choice); + } + } } } } diff --git a/test.cxx b/test.cxx index 2d3b625..4768cde 100644 --- a/test.cxx +++ b/test.cxx @@ -1257,6 +1257,11 @@ TEST_CASE("Completion works as expected", "[args]") REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-f"}), Equals("-f")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar")); + + args::MapFlag m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}}); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "2", "test", "-m", ""}), Equals("1\n2")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--map="}), Equals("--map=1\n--map=2")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-m1"}), Equals("-m1")); } #undef ARGS_HXX -- cgit v1.2.1 From 39fb642c4148cc12eefffcef056a4cbfbbfab314 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 20:47:59 +0300 Subject: fix bash equal sign tokenization --- args.hxx | 47 +++++++++++++++++++++++++++++++++++++++-------- test.cxx | 8 +++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/args.hxx b/args.hxx index 91cd368..42934bc 100644 --- a/args.hxx +++ b/args.hxx @@ -2209,14 +2209,14 @@ namespace args Positional }; - OptionType ParseOption(const std::string &s) + OptionType ParseOption(const std::string &s, bool allowEmpty = false) { - if (s.find(longprefix) == 0 && s.length() > longprefix.length()) + if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length())) { return OptionType::LongFlag; } - if (s.find(shortprefix) == 0 && s.length() > shortprefix.length()) + if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length())) { return OptionType::ShortFlag; } @@ -2435,7 +2435,13 @@ namespace args { if (cur.empty() || choice.find(cur) == 0) { - completion->reply.push_back(choice); + if (completion->syntax == "bash" && ParseOption(choice) == OptionType::LongFlag && choice.find(longseparator) != std::string::npos) + { + completion->reply.push_back(choice.substr(choice.find(longseparator) + 1)); + } else + { + completion->reply.push_back(choice); + } return true; } @@ -2454,8 +2460,9 @@ namespace args const auto &chunk = *it; auto pos = GetNextPositional(); std::vector commands = GetCommands(); + const auto optionType = ParseOption(chunk, true); - if (!commands.empty() && (chunk.empty() || ParseOption(chunk) == OptionType::Positional)) + if (!commands.empty() && (chunk.empty() || optionType == OptionType::Positional)) { for (auto &cmd : commands) { @@ -2482,7 +2489,7 @@ namespace args if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None) { auto choices = pos->HelpChoices(helpParams); - hasPositionalCompletion = !choices.empty(); + hasPositionalCompletion = !choices.empty() || optionType != OptionType::Positional; for (auto &choice : choices) { AddCompletionReply(chunk, choice); @@ -2513,7 +2520,7 @@ namespace args } } - if (ParseOption(chunk) == OptionType::LongFlag && allowJoinedLongValue) + if (optionType == OptionType::LongFlag && allowJoinedLongValue) { const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator); if (separator != chunk.npos) @@ -2527,7 +2534,7 @@ namespace args } } } - } else if (ParseOption(chunk) == OptionType::ShortFlag && allowJoinedShortValue) + } else if (optionType == OptionType::ShortFlag && allowJoinedShortValue) { if (chunk.size() > shortprefix.size() + 1) { @@ -2665,6 +2672,30 @@ namespace args std::vector curArgs(++it, end); curArgs.resize(completion->cword); + + if (completion->syntax == "bash") + { + // bash tokenizes --flag=value as --flag=value + for (size_t idx = 0; idx < curArgs.size(); ) + { + if (idx > 0 && curArgs[idx] == "=") + { + curArgs[idx - 1] += "="; + if (idx + 1 < curArgs.size()) + { + curArgs[idx - 1] += curArgs[idx + 1]; + curArgs.erase(curArgs.begin() + idx, curArgs.begin() + idx + 2); + } else + { + curArgs.erase(curArgs.begin() + idx); + } + } else + { + ++idx; + } + } + + } #ifndef ARGS_NOEXCEPT try { diff --git a/test.cxx b/test.cxx index 4768cde..94defee 100644 --- a/test.cxx +++ b/test.cxx @@ -1260,8 +1260,14 @@ TEST_CASE("Completion works as expected", "[args]") args::MapFlag m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}}); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "2", "test", "-m", ""}), Equals("1\n2")); - REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--map="}), Equals("--map=1\n--map=2")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--map="}), Equals("1\n2")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "2", "test", "--map", "="}), Equals("1\n2")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-m1"}), Equals("-m1")); + + args::Positional pos(p, "name", "desc"); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b\n-m")); + REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar\n--map")); } #undef ARGS_HXX -- cgit v1.2.1 From 4b983b8f035dbd854edc3fd50c767d913bf775ad Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 21:07:26 +0300 Subject: add examples --- CMakeLists.txt | 8 +++ README.md | 7 +++ examples/bash_completion.sh | 13 +++++ examples/completion.cxx | 27 ++++++++++ examples/gitlike.cxx | 129 ++++++++++++++++++++++++++++++++++++++++++++ gitlike.cxx | 129 -------------------------------------------- 6 files changed, 184 insertions(+), 129 deletions(-) create mode 100644 examples/bash_completion.sh create mode 100644 examples/completion.cxx create mode 100644 examples/gitlike.cxx delete mode 100644 gitlike.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 322e8d1..d0aaa69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,14 @@ add_executable(argstest-multiple-inclusion test/multiple_inclusion_1.cxx test/mu target_link_libraries(argstest-multiple-inclusion args) set_property(TARGET argstest-multiple-inclusion PROPERTY CXX_STANDARD 11) +add_executable(gitlike examples/gitlike.cxx) +target_link_libraries(gitlike args) +set_property(TARGET gitlike PROPERTY CXX_STANDARD 11) + +add_executable(completion examples/completion.cxx) +target_link_libraries(completion args) +set_property(TARGET completion PROPERTY CXX_STANDARD 11) + enable_testing() add_test(NAME "test" COMMAND argstest) add_test(NAME "test-multiple-inclusion" COMMAND argstest-multiple-inclusion) diff --git a/README.md b/README.md index f3a35d8..5327549 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ It: * Allows one value flag to take a specific number of values (like `--foo first second`, where --foo slurps both arguments). * Allows you to have value flags only optionally accept values +* Provides autocompletion for bash # What does it not do? @@ -179,10 +180,16 @@ int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); + args::CompletionFlag completion(parser, {"complete"}); try { parser.ParseCLI(argc, argv); } + catch (args::Completion e) + { + std::cout << e.what(); + return 0; + } catch (args::Help) { std::cout << parser; diff --git a/examples/bash_completion.sh b/examples/bash_completion.sh new file mode 100644 index 0000000..54937ad --- /dev/null +++ b/examples/bash_completion.sh @@ -0,0 +1,13 @@ +_args() { + _init_completion -n 2> /dev/null + local program comparg + + program="${COMP_WORDS[0]}" + comparg="--complete" # replace this with your flag + + COMPREPLY=($("$program" "$comparg" bash "$COMP_CWORD" "${COMP_WORDS[@]}" 2> /dev/null)) + [[ $COMPREPLY ]] && return + _filedir +} + +complete -F _args completion diff --git a/examples/completion.cxx b/examples/completion.cxx new file mode 100644 index 0000000..3122c30 --- /dev/null +++ b/examples/completion.cxx @@ -0,0 +1,27 @@ +/* Copyright © 2016-2017 Taylor C. Richberger and Pavel Belikov + * This code is released under the license described in the LICENSE file + */ + +#include "args.hxx" +#include + +int main(int argc, const char **argv) +{ + args::ArgumentParser p("parser"); + args::CompletionFlag c(p, {"complete"}); + args::ValueFlag f(p, "name", "description", {'f', "foo"}, "abc"); + args::ValueFlag b(p, "name", "description", {'b', "bar"}, "abc"); + args::MapFlag m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}}); + args::Positional pos(p, "name", "desc"); + + try + { + p.ParseCLI(argc, argv); + } + catch (args::Completion &e) + { + std::cout << e.what(); + } + + return 0; +} diff --git a/examples/gitlike.cxx b/examples/gitlike.cxx new file mode 100644 index 0000000..aa0e20e --- /dev/null +++ b/examples/gitlike.cxx @@ -0,0 +1,129 @@ +/* Copyright © 2016 Taylor C. Richberger + * This code is released under the license described in the LICENSE file + */ + +#include +#include +#include +#include + +void Init(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs); +void Add(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs); + +using commandtype = std::function::const_iterator, std::vector::const_iterator)>; + +int main(int argc, char **argv) +{ + std::unordered_map map{ + {"init", Init}, + {"add", Add}}; + + const std::vector args(argv + 1, argv + argc); + args::ArgumentParser parser("This is a git-like program", "Valid commands are init and add"); + args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); + parser.Prog(argv[0]); + parser.ProglinePostfix("{command options}"); + args::Flag version(parser, "version", "Show the version of this program", {"version"}); + args::ValueFlag htmlpath(parser, "html-path", "Specify the html path", {"html-path"}); + args::MapPositional command(parser, "command", "Command to execute", map); + command.KickOut(true); + try + { + auto next = parser.ParseArgs(args); + std::cout << std::boolalpha; + std::cout << "Before command options:" << std::endl; + std::cout << "Version called: " << bool{version} << std::endl; + std::cout << "html-path called: " << bool{htmlpath} << ", value: " << args::get(htmlpath) << std::endl; + if (command) + { + args::get(command)(argv[0], next, std::end(args)); + } else + { + std::cout << parser; + } + } + catch (args::Help) + { + std::cout << parser; + return 0; + } + catch (args::Error e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return 1; + } + return 0; +} + +void Init(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs) +{ + std::cout << "In Init" << std::endl; + args::ArgumentParser parser(""); + parser.Prog(progname + " init"); + args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); + args::ValueFlag templatedir(parser, "template-directory", "directory from which templates will be used", {"template"}); + args::Flag bare(parser, "bare", "create a bare repository", {"bare"}); + args::Flag quiet(parser, "quiet", "be quiet", {'q', "quiet"}); + args::Positional directory(parser, "directory", "The directory to create in", "."); + try + { + parser.ParseArgs(beginargs, endargs); + std::cout << std::boolalpha; + std::cout << "templatedir: " << bool{templatedir} << ", value: " << args::get(templatedir) << std::endl; + std::cout << "bare: " << bool{bare} << std::endl; + std::cout << "quiet: " << bool{quiet} << std::endl; + std::cout << "directory: " << bool{directory} << ", value: " << args::get(directory) << std::endl; + } + catch (args::Help) + { + std::cout << parser; + return; + } + catch (args::ParseError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return; + } +} + +void Add(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs) +{ + std::cout << "In Add" << std::endl; + args::ArgumentParser parser(""); + parser.Prog(progname + " add"); + args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); + args::Flag dryrun(parser, "dryrun", "dry run", {'n', "dry-run"}); + args::Flag verbose(parser, "verbose", "be verbose", {'v', "verbose"}); + args::Flag refresh(parser, "refresh", "Don't add, only refresh the index", {"refresh"}); + args::PositionalList pathspec(parser, "pathspec", "pathspecs"); + try + { + parser.ParseArgs(beginargs, endargs); + std::cout << std::boolalpha; + std::cout << "dryrun: " << bool{dryrun} << std::endl;; + std::cout << "verbose: " << bool{verbose} << std::endl; + std::cout << "refresh: " << bool{refresh} << std::endl; + std::cout << "pathspec: " << bool{pathspec} << std::endl; + if (pathspec) + { + std::cout << "values: " << std::endl; + for (const auto &spec: args::get(pathspec)) + { + std::cout << " - " << spec << std::endl; + } + } + } + catch (args::Help) + { + std::cout << parser; + return; + } + catch (args::ParseError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return; + } +} diff --git a/gitlike.cxx b/gitlike.cxx deleted file mode 100644 index aa0e20e..0000000 --- a/gitlike.cxx +++ /dev/null @@ -1,129 +0,0 @@ -/* Copyright © 2016 Taylor C. Richberger - * This code is released under the license described in the LICENSE file - */ - -#include -#include -#include -#include - -void Init(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs); -void Add(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs); - -using commandtype = std::function::const_iterator, std::vector::const_iterator)>; - -int main(int argc, char **argv) -{ - std::unordered_map map{ - {"init", Init}, - {"add", Add}}; - - const std::vector args(argv + 1, argv + argc); - args::ArgumentParser parser("This is a git-like program", "Valid commands are init and add"); - args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); - parser.Prog(argv[0]); - parser.ProglinePostfix("{command options}"); - args::Flag version(parser, "version", "Show the version of this program", {"version"}); - args::ValueFlag htmlpath(parser, "html-path", "Specify the html path", {"html-path"}); - args::MapPositional command(parser, "command", "Command to execute", map); - command.KickOut(true); - try - { - auto next = parser.ParseArgs(args); - std::cout << std::boolalpha; - std::cout << "Before command options:" << std::endl; - std::cout << "Version called: " << bool{version} << std::endl; - std::cout << "html-path called: " << bool{htmlpath} << ", value: " << args::get(htmlpath) << std::endl; - if (command) - { - args::get(command)(argv[0], next, std::end(args)); - } else - { - std::cout << parser; - } - } - catch (args::Help) - { - std::cout << parser; - return 0; - } - catch (args::Error e) - { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return 1; - } - return 0; -} - -void Init(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs) -{ - std::cout << "In Init" << std::endl; - args::ArgumentParser parser(""); - parser.Prog(progname + " init"); - args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); - args::ValueFlag templatedir(parser, "template-directory", "directory from which templates will be used", {"template"}); - args::Flag bare(parser, "bare", "create a bare repository", {"bare"}); - args::Flag quiet(parser, "quiet", "be quiet", {'q', "quiet"}); - args::Positional directory(parser, "directory", "The directory to create in", "."); - try - { - parser.ParseArgs(beginargs, endargs); - std::cout << std::boolalpha; - std::cout << "templatedir: " << bool{templatedir} << ", value: " << args::get(templatedir) << std::endl; - std::cout << "bare: " << bool{bare} << std::endl; - std::cout << "quiet: " << bool{quiet} << std::endl; - std::cout << "directory: " << bool{directory} << ", value: " << args::get(directory) << std::endl; - } - catch (args::Help) - { - std::cout << parser; - return; - } - catch (args::ParseError e) - { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return; - } -} - -void Add(const std::string &progname, std::vector::const_iterator beginargs, std::vector::const_iterator endargs) -{ - std::cout << "In Add" << std::endl; - args::ArgumentParser parser(""); - parser.Prog(progname + " add"); - args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); - args::Flag dryrun(parser, "dryrun", "dry run", {'n', "dry-run"}); - args::Flag verbose(parser, "verbose", "be verbose", {'v', "verbose"}); - args::Flag refresh(parser, "refresh", "Don't add, only refresh the index", {"refresh"}); - args::PositionalList pathspec(parser, "pathspec", "pathspecs"); - try - { - parser.ParseArgs(beginargs, endargs); - std::cout << std::boolalpha; - std::cout << "dryrun: " << bool{dryrun} << std::endl;; - std::cout << "verbose: " << bool{verbose} << std::endl; - std::cout << "refresh: " << bool{refresh} << std::endl; - std::cout << "pathspec: " << bool{pathspec} << std::endl; - if (pathspec) - { - std::cout << "values: " << std::endl; - for (const auto &spec: args::get(pathspec)) - { - std::cout << " - " << spec << std::endl; - } - } - } - catch (args::Help) - { - std::cout << parser; - return; - } - catch (args::ParseError e) - { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return; - } -} -- cgit v1.2.1 From 8039dad9359d597c53d48483f7a611af73236430 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 21:13:54 +0300 Subject: remove ParseValue for completion --- args.hxx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/args.hxx b/args.hxx index 42934bc..dc802ad 100644 --- a/args.hxx +++ b/args.hxx @@ -2358,7 +2358,10 @@ namespace args #endif } - flag->ParseValue(values); + if (!readCompletion) + { + flag->ParseValue(values); + } if (flag->KickOut()) { @@ -2405,7 +2408,10 @@ namespace args #endif } - flag->ParseValue(values); + if (!readCompletion) + { + flag->ParseValue(values); + } if (flag->KickOut()) { @@ -3109,18 +3115,13 @@ namespace args virtual ~HelpFlag() {} - virtual FlagBase *Match(const EitherFlag &arg) override + virtual void ParseValue(const std::vector &) { - if (FlagBase::Match(arg)) - { #ifdef ARGS_NOEXCEPT error = Error::Help; - return this; #else - throw Help(arg.str()); + throw Help(Name()); #endif - } - return nullptr; } /** Get whether this was matched -- cgit v1.2.1 From 32a103d5b530c4cf0709e02f2c893c752c9e3c97 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 21:33:19 +0300 Subject: fix warnings --- args.hxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/args.hxx b/args.hxx index dc802ad..cfbc590 100644 --- a/args.hxx +++ b/args.hxx @@ -2516,9 +2516,9 @@ namespace args auto &matcher = flag->GetMatcher(); if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) { - for (auto &name : matcher.GetFlagStrings()) + for (auto &flagName : matcher.GetFlagStrings()) { - if (AddCompletionReply(chunk, name.str(shortprefix, longprefix))) + if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix))) { break; } @@ -2544,13 +2544,13 @@ namespace args { if (chunk.size() > shortprefix.size() + 1) { - auto name = chunk.at(shortprefix.size()); + auto arg = chunk.at(shortprefix.size()); //TODO: support -abcVALUE where a and b take no value - if (auto flag = this->Match(name)) + if (auto flag = this->Match(arg)) { for (auto &choice : flag->HelpChoices(helpParams)) { - AddCompletionReply(chunk, shortprefix + name + choice); + AddCompletionReply(chunk, shortprefix + arg + choice); } } } @@ -2708,7 +2708,7 @@ namespace args Parse(curArgs.begin(), curArgs.end()); throw Completion(""); } - catch (Completion &e) + catch (Completion &) { throw; } -- cgit v1.2.1 From 016304b1043e104d19b3a48e6ed2d8d398c63229 Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 22:21:56 +0300 Subject: fix subparsers completion --- args.hxx | 6 ++++++ test.cxx | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/args.hxx b/args.hxx index cfbc590..0525214 100644 --- a/args.hxx +++ b/args.hxx @@ -1884,6 +1884,12 @@ namespace args } } + if (selectedCommand != nullptr) + { + auto childFlags = selectedCommand->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + if (subparser != nullptr) { auto childFlags = subparser->GetAllFlags(); diff --git a/test.cxx b/test.cxx index 94defee..ffb475b 100644 --- a/test.cxx +++ b/test.cxx @@ -1251,8 +1251,9 @@ TEST_CASE("Completion works as expected", "[args]") args::ArgumentParser p("parser"); args::CompletionFlag c(p, {"completion"}); - args::ValueFlag f(p, "name", "description", {'f', "foo"}, "abc"); - args::ValueFlag b(p, "name", "description", {'b', "bar"}, "abc"); + args::Group g(p); + args::ValueFlag f(g, "name", "description", {'f', "foo"}, "abc"); + args::ValueFlag b(g, "name", "description", {'b', "bar"}, "abc"); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-f"}), Equals("-f")); @@ -1268,6 +1269,26 @@ TEST_CASE("Completion works as expected", "[args]") REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b\n-m")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar\n--map")); + + args::ArgumentParser p2("parser"); + args::CompletionFlag complete2(p2, {"completion"}); + + args::Command c1(p2, "command1", "desc", [](args::Subparser &sp) + { + args::ValueFlag f1(sp, "name", "description", {'f', "foo"}, "abc"); + sp.Parse(); + }); + + args::Command c2(p2, "command2", "desc", [](args::Subparser &sp) + { + args::ValueFlag f1(sp, "name", "description", {'b', "bar"}, "abc"); + sp.Parse(); + }); + + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("")); + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("command1\ncommand2")); + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command1", ""}), Equals("-f")); + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command2", ""}), Equals("-b")); } #undef ARGS_HXX @@ -1415,3 +1436,17 @@ TEST_CASE("Matcher validation works as expected in noexcept mode", "[args]") REQUIRE(parser.GetError() == argstest::Error::Usage); } +TEST_CASE("Completion works as expected in noexcept mode", "[args]") +{ + using namespace Catch::Matchers; + + argstest::ArgumentParser p("parser"); + argstest::CompletionFlag c(p, {"completion"}); + argstest::Group g(p); + argstest::ValueFlag f(g, "name", "description", {'f', "foo"}, "abc"); + argstest::ValueFlag b(g, "name", "description", {'b', "bar"}, "abc"); + + p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}); + REQUIRE(p.GetError() == argstest::Error::Completion); + REQUIRE(argstest::get(c) == "-f\n-b"); +} -- cgit v1.2.1 From b8fc68ac37103a3b35d013ff10d0b623a3d6a75d Mon Sep 17 00:00:00 2001 From: Pavel Belikov Date: Sat, 23 Dec 2017 23:16:30 +0300 Subject: add more test cases --- test.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test.cxx b/test.cxx index ffb475b..13cd4d0 100644 --- a/test.cxx +++ b/test.cxx @@ -1276,6 +1276,7 @@ TEST_CASE("Completion works as expected", "[args]") args::Command c1(p2, "command1", "desc", [](args::Subparser &sp) { args::ValueFlag f1(sp, "name", "description", {'f', "foo"}, "abc"); + f1.KickOut(); sp.Parse(); }); @@ -1289,6 +1290,8 @@ TEST_CASE("Completion works as expected", "[args]") REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("command1\ncommand2")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command1", ""}), Equals("-f")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command2", ""}), Equals("-b")); + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command3", ""}), Equals("")); + REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "3", "test", "command1", "-f", "-"}), Equals("")); } #undef ARGS_HXX -- cgit v1.2.1