aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor C. Richberger <taywee@gmx.com>2017-12-25 22:49:30 -0700
committerGitHub <noreply@github.com>2017-12-25 22:49:30 -0700
commit365c0cad14aa194dfa5cf68caca3cc20c7d39076 (patch)
tree5391d4b0e55b25d4c909ff4e816dcb742132cf2f
parentreplace explicit std::vector ParseCLI with template, reduce optimization (diff)
parentadd more test cases (diff)
downloadargs.hxx-365c0cad14aa194dfa5cf68caca3cc20c7d39076.tar.xz
Merge pull request #56 from pavel-belikov/bash-completion
Bash completion
-rw-r--r--CMakeLists.txt8
-rw-r--r--README.md7
-rw-r--r--args.hxx535
-rw-r--r--examples/bash_completion.sh13
-rw-r--r--examples/completion.cxx27
-rw-r--r--examples/gitlike.cxx (renamed from gitlike.cxx)0
-rw-r--r--test.cxx73
7 files changed, 569 insertions, 94 deletions
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/args.hxx b/args.hxx
index 37e86b4..0525214 100644
--- a/args.hxx
+++ b/args.hxx
@@ -165,6 +165,26 @@ namespace args
return output;
}
+ namespace detail
+ {
+ template <typename T>
+ 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)
@@ -705,6 +739,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
@@ -776,6 +844,11 @@ namespace args
return nullptr;
}
+ virtual std::vector<FlagBase*> GetAllFlags()
+ {
+ return {};
+ }
+
virtual bool HasFlag() const
{
return false;
@@ -841,12 +914,12 @@ namespace args
bool kickout = false;
std::string defaultString;
bool defaultStringManual = false;
- std::string choicesString;
+ std::vector<std::string> choicesStrings;
bool choicesStringManual = false;
virtual std::string GetDefaultString(const HelpParams&) const { return {}; }
- virtual std::string GetChoicesString(const HelpParams&) const { return {}; }
+ virtual std::vector<std::string> GetChoicesStrings(const HelpParams&) const { return {}; }
virtual std::string GetNameString(const HelpParams&) const { return Name(); }
@@ -881,23 +954,23 @@ namespace args
*/
std::string HelpDefault(const HelpParams &params) const
{
- return GetDefaultString(params);
+ return defaultStringManual ? defaultString : 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<std::string> &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 &params) const
+ std::vector<std::string> HelpChoices(const HelpParams &params) const
{
- return GetChoicesString(params);
+ return choicesStringManual ? choicesStrings : GetChoicesStrings(params);
}
virtual std::vector<std::tuple<std::string, std::string, unsigned>> GetDescription(const HelpParams &params, const unsigned indentLevel) const override
@@ -907,7 +980,7 @@ 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);
+ 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) };
@@ -919,40 +992,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 <typename T, typename = int>
@@ -978,27 +1017,18 @@ namespace args
}
template <typename T>
- std::string MapKeysToString(const T &map)
+ std::vector<std::string> MapKeysToStrings(const T &map)
{
- std::string res;
+ std::vector<std::string> res;
using K = typename std::decay<decltype(std::begin(map)->first)>::type;
if (IsConvertableToString<K>::value)
{
- std::vector<std::string> 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;
}
@@ -1065,6 +1095,16 @@ namespace args
return nullptr;
}
+ virtual std::vector<FlagBase*> 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())
@@ -1153,6 +1193,49 @@ namespace args
}
};
+ class CompletionFlag : public ValueFlagBase
+ {
+ public:
+ std::vector<std::string> reply;
+ size_t cword = 0;
+ std::string syntax;
+
+ template <typename GroupClass>
+ 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<std::string> &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
@@ -1311,6 +1394,17 @@ namespace args
return nullptr;
}
+ virtual std::vector<FlagBase*> GetAllFlags() override
+ {
+ std::vector<FlagBase*> 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())
@@ -1772,6 +1866,39 @@ namespace args
return Matched() ? Group::Match(flag) : nullptr;
}
+ virtual std::vector<FlagBase*> GetAllFlags() override
+ {
+ std::vector<FlagBase*> 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 (selectedCommand != nullptr)
+ {
+ auto childFlags = selectedCommand->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)
@@ -2077,6 +2204,9 @@ namespace args
bool allowSeparateShortValue = true;
bool allowSeparateLongValue = true;
+ CompletionFlag *completion = nullptr;
+ bool readCompletion = false;
+
protected:
enum class OptionType
{
@@ -2085,14 +2215,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;
}
@@ -2100,6 +2230,28 @@ namespace args
return OptionType::Positional;
}
+ template <typename It>
+ 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
@@ -2145,6 +2297,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;
@@ -2207,7 +2364,10 @@ namespace args
#endif
}
- flag->ParseValue(values);
+ if (!readCompletion)
+ {
+ flag->ParseValue(values);
+ }
if (flag->KickOut())
{
@@ -2254,7 +2414,10 @@ namespace args
#endif
}
- flag->ParseValue(values);
+ if (!readCompletion)
+ {
+ flag->ParseValue(values);
+ }
if (flag->KickOut())
{
@@ -2280,16 +2443,148 @@ namespace args
return true;
}
+ bool AddCompletionReply(const std::string &cur, const std::string &choice)
+ {
+ if (cur.empty() || choice.find(cur) == 0)
+ {
+ 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;
+ }
+
+ return false;
+ }
+
+ template <typename It>
+ bool Complete(It it, It end)
+ {
+ auto nextIt = it;
+ if (!readCompletion || (++nextIt != end))
+ {
+ return false;
+ }
+
+ const auto &chunk = *it;
+ auto pos = GetNextPositional();
+ std::vector<Command *> commands = GetCommands();
+ const auto optionType = ParseOption(chunk, true);
+
+ if (!commands.empty() && (chunk.empty() || optionType == 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() || optionType != OptionType::Positional;
+ 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)))
+ {
+ for (auto &flagName : matcher.GetFlagStrings())
+ {
+ if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix)))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if (optionType == 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 (optionType == OptionType::ShortFlag && allowJoinedShortValue)
+ {
+ if (chunk.size() > shortprefix.size() + 1)
+ {
+ auto arg = chunk.at(shortprefix.size());
+ //TODO: support -abcVALUE where a and b take no value
+ if (auto flag = this->Match(arg))
+ {
+ for (auto &choice : flag->HelpChoices(helpParams))
+ {
+ AddCompletionReply(chunk, shortprefix + arg + choice);
+ }
+ }
+ }
+ }
+ }
+ }
+
+#ifndef ARGS_NOEXCEPT
+ throw Completion(completion->Get());
+#else
+ return true;
+#endif
+ }
+
template <typename It>
It Parse(It begin, It end)
{
bool terminated = false;
-
std::vector<Command *> 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)
@@ -2371,6 +2666,66 @@ 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<std::string> 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
+ {
+ Parse(curArgs.begin(), curArgs.end());
+ throw Completion("");
+ }
+ catch (Completion &)
+ {
+ throw;
+ }
+ catch (args::Error&)
+ {
+ throw Completion("");
+ }
+#else
+ return Parse(curArgs.begin(), curArgs.end());
+#endif
+ }
}
Validate(shortprefix, longprefix);
@@ -2392,6 +2747,12 @@ namespace args
matched = true;
}
+ void AddCompletion(CompletionFlag &completionFlag)
+ {
+ completion = &completionFlag;
+ Add(completionFlag);
+ }
+
/** The program name for help generation
*/
const std::string &Prog() const
@@ -2613,6 +2974,7 @@ namespace args
{
Command::Reset();
matched = true;
+ readCompletion = false;
}
/** Parse all arguments.
@@ -2759,18 +3121,13 @@ namespace args
virtual ~HelpFlag() {}
- virtual FlagBase *Match(const EitherFlag &arg) override
+ virtual void ParseValue(const std::vector<std::string> &)
{
- 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
@@ -3241,9 +3598,9 @@ namespace args
Reader reader;
protected:
- virtual std::string GetChoicesString(const HelpParams &) const override
+ virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
{
- return detail::MapKeysToString(map);
+ return detail::MapKeysToStrings(map);
}
public:
@@ -3323,9 +3680,9 @@ namespace args
Reader reader;
protected:
- virtual std::string GetChoicesString(const HelpParams &) const override
+ virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
{
- return detail::MapKeysToString(map);
+ return detail::MapKeysToStrings(map);
}
public:
@@ -3600,9 +3957,9 @@ namespace args
Reader reader;
protected:
- virtual std::string GetChoicesString(const HelpParams &) const override
+ virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
{
- return detail::MapKeysToString(map);
+ return detail::MapKeysToStrings(map);
}
public:
@@ -3675,9 +4032,9 @@ namespace args
Reader reader;
protected:
- virtual std::string GetChoicesString(const HelpParams &) const override
+ virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
{
- return detail::MapKeysToString(map);
+ return detail::MapKeysToStrings(map);
}
public:
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 <taywee@gmx.com> and Pavel Belikov
+ * This code is released under the license described in the LICENSE file
+ */
+
+#include "args.hxx"
+#include <iostream>
+
+int main(int argc, const char **argv)
+{
+ args::ArgumentParser p("parser");
+ args::CompletionFlag c(p, {"complete"});
+ args::ValueFlag<std::string> f(p, "name", "description", {'f', "foo"}, "abc");
+ args::ValueFlag<std::string> b(p, "name", "description", {'b', "bar"}, "abc");
+ args::MapFlag<std::string, int> m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}});
+ args::Positional<std::string> pos(p, "name", "desc");
+
+ try
+ {
+ p.ParseCLI(argc, argv);
+ }
+ catch (args::Completion &e)
+ {
+ std::cout << e.what();
+ }
+
+ return 0;
+}
diff --git a/gitlike.cxx b/examples/gitlike.cxx
index aa0e20e..aa0e20e 100644
--- a/gitlike.cxx
+++ b/examples/gitlike.cxx
diff --git a/test.cxx b/test.cxx
index 01decbe..13cd4d0 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,59 @@ TEST_CASE("Choices description works as expected", "[args]")
args::MapPositional<std::string, int, args::ValueReader, std::map> mappos(p, "mappos", "mappos", {{"1",1}, {"2", 2}});
args::MapPositionalList<char, int, std::vector, args::ValueReader, std::map> 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<std::string>{"1", "2"});
+ REQUIRE(maplist.HelpChoices(p.helpParams) == std::vector<std::string>{"1", "2"});
+ REQUIRE(mappos.HelpChoices(p.helpParams) == std::vector<std::string>{"1", "2"});
+ REQUIRE(mapposlist.HelpChoices(p.helpParams) == std::vector<std::string>{"1", "2"});
+}
+
+TEST_CASE("Completion works as expected", "[args]")
+{
+ using namespace Catch::Matchers;
+
+ args::ArgumentParser p("parser");
+ args::CompletionFlag c(p, {"completion"});
+ args::Group g(p);
+ args::ValueFlag<std::string> f(g, "name", "description", {'f', "foo"}, "abc");
+ args::ValueFlag<std::string> b(g, "name", "description", {'b', "bar"}, "abc");
+
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-f"}), Equals("-f"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar"));
+
+ args::MapFlag<std::string, int> m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}});
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "2", "test", "-m", ""}), Equals("1\n2"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "--map="}), Equals("1\n2"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "2", "test", "--map", "="}), Equals("1\n2"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-m1"}), Equals("-m1"));
+
+ args::Positional<std::string> pos(p, "name", "desc");
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", ""}), Equals(""));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b\n-m"));
+ REQUIRE_THROWS_WITH(p.ParseArgs(std::vector<std::string>{"--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<std::string> f1(sp, "name", "description", {'f', "foo"}, "abc");
+ f1.KickOut();
+ sp.Parse();
+ });
+
+ args::Command c2(p2, "command2", "desc", [](args::Subparser &sp)
+ {
+ args::ValueFlag<std::string> f1(sp, "name", "description", {'b', "bar"}, "abc");
+ sp.Parse();
+ });
+
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-"}), Equals(""));
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", ""}), Equals("command1\ncommand2"));
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "2", "test", "command1", ""}), Equals("-f"));
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "2", "test", "command2", ""}), Equals("-b"));
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "2", "test", "command3", ""}), Equals(""));
+ REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector<std::string>{"--completion", "bash", "3", "test", "command1", "-f", "-"}), Equals(""));
}
#undef ARGS_HXX
@@ -1390,3 +1439,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<std::string> f(g, "name", "description", {'f', "foo"}, "abc");
+ argstest::ValueFlag<std::string> b(g, "name", "description", {'b', "bar"}, "abc");
+
+ p.ParseArgs(std::vector<std::string>{"--completion", "bash", "1", "test", "-"});
+ REQUIRE(p.GetError() == argstest::Error::Completion);
+ REQUIRE(argstest::get(c) == "-f\n-b");
+}