/* Copyright (c) 2016-2017 Taylor C. Richberger and Pavel * Belikov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /** \file args.hxx * \brief this single-header lets you use all of the args functionality * * The important stuff is done inside the args namespace */ #ifndef ARGS_HXX #define ARGS_HXX #include #include #include #include #include #include #include #include #include #include #include #ifdef ARGS_TESTNAMESPACE namespace argstest { #else /** \namespace args * \brief contains all the functionality of the args library */ namespace args { #endif /** Getter to grab the value from the argument type. * * If the Get() function of the type returns a reference, so does this, and * the value will be modifiable. */ template auto get(Option &option_) -> decltype(option_.Get()) { return option_.Get(); } /** (INTERNAL) Count UTF-8 glyphs * * This is not reliable, and will fail for combinatory glyphs, but it's * good enough here for now. * * \param string The string to count glyphs from * \return The UTF-8 glyphs in the string */ inline std::string::size_type Glyphs(const std::string &string_) { std::string::size_type length = 0; for (const char c: string_) { if ((c & 0xc0) != 0x80) { ++length; } } return length; } /** (INTERNAL) Wrap a vector of words into a vector of lines * * Empty words are skipped. Word "\n" forces wrapping. * * \param begin The begin iterator * \param end The end iterator * \param width The width of the body * \param firstlinewidth the width of the first line, defaults to the width of the body * \param firstlineindent the indent of the first line, defaults to 0 * \return the vector of lines */ template inline std::vector Wrap(It begin, It end, const std::string::size_type width, std::string::size_type firstlinewidth = 0, std::string::size_type firstlineindent = 0) { std::vector output; std::string line(firstlineindent, ' '); bool empty = true; if (firstlinewidth == 0) { firstlinewidth = width; } auto currentwidth = firstlinewidth; for (auto it = begin; it != end; ++it) { if (it->empty()) { continue; } if (*it == "\n") { if (!empty) { output.push_back(line); line.clear(); empty = true; currentwidth = width; } continue; } auto itemsize = Glyphs(*it); if ((line.length() + 1 + itemsize) > currentwidth) { if (!empty) { output.push_back(line); line.clear(); empty = true; currentwidth = width; } } if (itemsize > 0) { if (!empty) { line += ' '; } line += *it; empty = false; } } if (!empty) { output.push_back(line); } 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 * different width for the first line * * \param width The width of the body * \param firstlinewid the width of the first line, defaults to the width of the body * \return the vector of lines */ inline std::vector Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0) { // Preserve existing line breaks const auto newlineloc = in.find('\n'); if (newlineloc != in.npos) { auto first = Wrap(std::string(in, 0, newlineloc), width); auto second = Wrap(std::string(in, newlineloc + 1), width); first.insert( std::end(first), std::make_move_iterator(std::begin(second)), std::make_move_iterator(std::end(second))); return first; } std::istringstream stream(in); std::string::size_type indent = 0; for (char c : in) { if (!isspace(c)) { break; } ++indent; } return Wrap(std::istream_iterator(stream), std::istream_iterator(), width, firstlinewidth, indent); } #ifdef ARGS_NOEXCEPT /// Error class, for when ARGS_NOEXCEPT is defined enum class Error { None, Usage, Parse, Validation, Required, Map, Extra, Help, Subparser, Completion, }; #else /** Base error class */ class Error : public std::runtime_error { public: Error(const std::string &problem) : std::runtime_error(problem) {} virtual ~Error() {} }; /** Errors that occur during usage */ class UsageError : public Error { public: UsageError(const std::string &problem) : Error(problem) {} virtual ~UsageError() {} }; /** Errors that occur during regular parsing */ class ParseError : public Error { public: ParseError(const std::string &problem) : Error(problem) {} virtual ~ParseError() {} }; /** Errors that are detected from group validation after parsing finishes */ class ValidationError : public Error { public: ValidationError(const std::string &problem) : Error(problem) {} virtual ~ValidationError() {} }; /** Errors that when a required flag is omitted */ class RequiredError : public ValidationError { public: RequiredError(const std::string &problem) : ValidationError(problem) {} virtual ~RequiredError() {} }; /** Errors in map lookups */ class MapError : public ParseError { public: MapError(const std::string &problem) : ParseError(problem) {} virtual ~MapError() {} }; /** Error that occurs when a singular flag is specified multiple times */ class ExtraError : public ParseError { public: ExtraError(const std::string &problem) : ParseError(problem) {} virtual ~ExtraError() {} }; /** An exception that indicates that the user has requested help */ class Help : public Error { public: Help(const std::string &flag) : Error(flag) {} virtual ~Help() {} }; /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers. */ class SubparserError : public Error { public: SubparserError() : Error("") {} virtual ~SubparserError() {} }; /** An exception that contains autocompletion reply */ class Completion : public Error { public: Completion(const std::string &flag) : Error(flag) {} virtual ~Completion() {} }; #endif /** A simple unified option type for unified initializer lists for the Matcher class. */ struct EitherFlag { const bool isShort; const char shortFlag; const std::string longFlag; EitherFlag(const std::string &flag) : isShort(false), shortFlag(), longFlag(flag) {} EitherFlag(const char *flag) : isShort(false), shortFlag(), longFlag(flag) {} EitherFlag(const char flag) : isShort(true), shortFlag(flag), longFlag() {} /** Get just the long flags from an initializer list of EitherFlags */ static std::unordered_set GetLong(std::initializer_list flags) { std::unordered_set longFlags; for (const EitherFlag &flag: flags) { if (!flag.isShort) { longFlags.insert(flag.longFlag); } } return longFlags; } /** Get just the short flags from an initializer list of EitherFlags */ static std::unordered_set GetShort(std::initializer_list flags) { std::unordered_set shortFlags; for (const EitherFlag &flag: flags) { if (flag.isShort) { shortFlags.insert(flag.shortFlag); } } return shortFlags; } std::string str() const { 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; } }; /** A class of "matchers", specifying short and flags that can possibly be * matched. * * This is supposed to be constructed and then passed in, not used directly * from user code. */ class Matcher { private: const std::unordered_set shortFlags; const std::unordered_set longFlags; public: /** Specify short and long flags separately as iterators * * ex: `args::Matcher(shortFlags.begin(), shortFlags.end(), longFlags.begin(), longFlags.end())` */ template 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 * * ex: `args::Matcher(shortFlags, longFlags)` */ template Matcher(Short &&shortIn, Long &&longIn) : Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn)) {} /** Specify a mixed single initializer-list of both short and long flags * * This is the fancy one. It takes a single initializer list of * any number of any mixed kinds of flags. Chars are * automatically interpreted as short flags, and strings are * automatically interpreted as long flags: * * args::Matcher{'a'} * args::Matcher{"foo"} * args::Matcher{'h', "help"} * args::Matcher{"foo", 'f', 'F', "FoO"} */ Matcher(std::initializer_list in) : Matcher(EitherFlag::GetShort(in), EitherFlag::GetLong(in)) {} Matcher(Matcher &&other) : shortFlags(std::move(other.shortFlags)), longFlags(std::move(other.longFlags)) {} ~Matcher() {} /** (INTERNAL) Check if there is a match of a short flag */ bool Match(const char flag) const { return shortFlags.find(flag) != shortFlags.end(); } /** (INTERNAL) Check if there is a match of a long flag */ bool Match(const std::string &flag) const { return longFlags.find(flag) != longFlags.end(); } /** (INTERNAL) Check if there is a match of a flag */ bool Match(const EitherFlag &flag) const { return flag.isShort ? Match(flag.shortFlag) : Match(flag.longFlag); } /** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded */ std::vector GetFlagStrings() const { std::vector flagStrings; flagStrings.reserve(shortFlags.size() + longFlags.size()); for (const char flag: shortFlags) { flagStrings.emplace_back(flag); } for (const std::string &flag: longFlags) { flagStrings.emplace_back(flag); } return flagStrings; } /** (INTERNAL) Get long flag if it exists or any short flag */ EitherFlag GetLongOrAny() const { if (!longFlags.empty()) { return *longFlags.begin(); } if (!shortFlags.empty()) { return *shortFlags.begin(); } // 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 ' '; } }; /** Attributes for flags. */ enum class Options { /** Default options. */ None = 0x0, /** Flag can't be passed multiple times. */ Single = 0x01, /** Flag can't be omitted. */ Required = 0x02, /** Flag is excluded from usage line. */ HiddenFromUsage = 0x04, /** Flag is excluded from options help. */ HiddenFromDescription = 0x08, /** Flag is global and can be used in any subcommand. */ Global = 0x10, /** Flag stops a parser. */ KickOut = 0x20, /** Flag is excluded from auto completion. */ HiddenFromCompletion = 0x40, /** Flag is excluded from options help and usage line */ Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion, }; inline Options operator | (Options lhs, Options rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } inline Options operator & (Options lhs, Options rhs) { 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 { /** The width of the help menu */ unsigned int width = 80; /** The indent of the program line */ unsigned int progindent = 2; /** The indent of the program trailing lines for long parameters */ unsigned int progtailindent = 4; /** The indent of the description and epilogs */ unsigned int descriptionindent = 4; /** The indent of the flags */ unsigned int flagindent = 6; /** The indent of the flag descriptions */ unsigned int helpindent = 40; /** The additional indent each group adds */ unsigned int eachgroupindent = 2; /** The minimum gutter between each flag and its help */ unsigned int gutter = 1; /** Show the terminator when both options and positional parameters are present */ bool showTerminator = true; /** Show the {OPTIONS} on the prog line when this is true */ bool showProglineOptions = true; /** Show the positionals on the prog line when this is true */ bool showProglinePositionals = true; /** The prefix for short flags */ std::string shortPrefix; /** The prefix for long flags */ std::string longPrefix; /** The separator for short flags */ std::string shortSeparator; /** The separator for long flags */ std::string longSeparator; /** The program name for help generation */ std::string programName; /** Show command's flags */ bool showCommandChildren = false; /** Show command's descriptions and epilog */ bool showCommandFullHelp = false; /** The postfix for progline when showProglineOptions is true and command has any flags */ std::string proglineOptions = "{OPTIONS}"; /** The prefix for progline when command has any subcommands */ std::string proglineCommand = "COMMAND"; /** The prefix for progline value */ std::string proglineValueOpen = " <"; /** The postfix for progline value */ std::string proglineValueClose = ">"; /** The prefix for progline required argument */ std::string proglineRequiredOpen = ""; /** The postfix for progline required argument */ std::string proglineRequiredClose = ""; /** The prefix for progline non-required argument */ std::string proglineNonrequiredOpen = "["; /** The postfix for progline non-required argument */ std::string proglineNonrequiredClose = "]"; /** Show flags in program line */ bool proglineShowFlags = false; /** Use short flags in program lines when possible */ bool proglinePreferShortFlags = false; /** Program line prefix */ std::string usageString; /** String shown in help before flags descriptions */ std::string optionsString = "OPTIONS:"; /** Display value name after all the long and short flags */ bool useValueNameOnce = false; /** Show value name */ bool showValueName = true; /** Add newline before flag description */ bool addNewlineBeforeDescription = false; /** The prefix for option value */ std::string valueOpen = "["; /** The postfix for option value */ std::string valueClose = "]"; /** Add choices to argument description */ bool addChoices = false; /** The prefix for choices */ std::string choiceString = "\nOne of: "; /** Add default values to argument description */ bool addDefault = false; /** The prefix for default values */ 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 { private: Options options = {}; protected: bool matched = false; const std::string help; #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT mutable Error error = Error::None; mutable std::string errorMsg; #endif public: Base(const std::string &help_, Options options_ = {}) : options(options_), help(help_) {} virtual ~Base() {} Options GetOptions() const noexcept { return options; } bool IsRequired() const noexcept { return (GetOptions() & Options::Required) != Options::None; } virtual bool Matched() const noexcept { return matched; } virtual void Validate(const std::string &, const std::string &) const { } operator bool() const noexcept { return Matched(); } virtual std::vector> GetDescription(const HelpParams &, const unsigned indentLevel) const { std::tuple description; std::get<1>(description) = help; std::get<2>(description) = indentLevel; return { std::move(description) }; } virtual std::vector GetCommands() { return {}; } virtual bool IsGroup() const { return false; } virtual FlagBase *Match(const EitherFlag &) { return nullptr; } virtual PositionalBase *GetNextPositional() { return nullptr; } virtual std::vector GetAllFlags() { return {}; } virtual bool HasFlag() const { return false; } virtual bool HasPositional() const { return false; } virtual bool HasCommand() const { return false; } virtual std::vector GetProgramLine(const HelpParams &) const { return {}; } /// Sets a kick-out value for building subparsers void KickOut(bool kickout_) noexcept { if (kickout_) { options = options | Options::KickOut; } else { options = static_cast(static_cast(options) & ~static_cast(Options::KickOut)); } } /// Gets the kick-out value for building subparsers bool KickOut() const noexcept { return (options & Options::KickOut) != Options::None; } virtual void Reset() noexcept { matched = false; #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const { return error; } /// Only for ARGS_NOEXCEPT std::string GetErrorMsg() const { return errorMsg; } #endif }; /** Base class for all match types that have a name */ class NamedBase : public Base { protected: const std::string name; bool kickout = false; std::string defaultString; bool defaultStringManual = false; std::vector choicesStrings; bool choicesStringManual = false; virtual std::string GetDefaultString(const HelpParams&) const { return {}; } virtual std::vector GetChoicesStrings(const HelpParams&) const { return {}; } virtual std::string GetNameString(const HelpParams&) const { return Name(); } void AddDescriptionPostfix(std::string &dest, const bool isManual, const std::string &manual, bool isGenerated, const std::string &generated, const std::string &str) const { if (isManual && !manual.empty()) { dest += str; dest += manual; } else if (!isManual && isGenerated && !generated.empty()) { dest += str; dest += generated; } } public: NamedBase(const std::string &name_, const std::string &help_, Options options_ = {}) : Base(help_, options_), name(name_) {} virtual ~NamedBase() {} /** Sets default value string that will be added to argument description. * Use empty string to disable it for this argument. */ void HelpDefault(const std::string &str) { defaultStringManual = true; defaultString = str; } /** Gets default value string that will be added to argument description. */ std::string HelpDefault(const HelpParams ¶ms) const { return defaultStringManual ? defaultString : GetDefaultString(params); } /** Sets choices strings that will be added to argument description. * Use empty vector to disable it for this argument. */ void HelpChoices(const std::vector &array) { choicesStringManual = true; choicesStrings = array; } /** Gets choices strings that will be added to argument description. */ std::vector HelpChoices(const HelpParams ¶ms) const { return choicesStringManual ? choicesStrings : GetChoicesStrings(params); } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override { std::tuple description; std::get<0>(description) = GetNameString(params); std::get<1>(description) = help; std::get<2>(description) = indentLevel; 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) }; } virtual std::string Name() const { return name; } }; namespace detail { template struct IsConvertableToString : std::false_type {}; template struct IsConvertableToString() << std::declval(), int())> : std::true_type {}; template typename std::enable_if::value, std::string>::type ToString(const T &value) { std::ostringstream s; s << value; return s.str(); } template typename std::enable_if::value, std::string>::type ToString(const T &) { return {}; } template std::vector MapKeysToStrings(const T &map) { std::vector res; using K = typename std::decayfirst)>::type; if (IsConvertableToString::value) { for (const auto &p : map) { res.push_back(detail::ToString(p.first)); } std::sort(res.begin(), res.end()); } return res; } } /** Base class for all flag options */ class FlagBase : public NamedBase { protected: const Matcher matcher; virtual std::string GetNameString(const HelpParams ¶ms) const override { const std::string postfix = !params.showValueName || NumberOfArguments() == 0 ? std::string() : Name(); std::string flags; const auto flagStrings = matcher.GetFlagStrings(); const bool useValueNameOnce = flagStrings.size() == 1 ? false : params.useValueNameOnce; for (auto it = flagStrings.begin(); it != flagStrings.end(); ++it) { auto &flag = *it; if (it != flagStrings.begin()) { flags += ", "; } flags += flag.isShort ? params.shortPrefix : params.longPrefix; flags += flag.str(); if (!postfix.empty() && (!useValueNameOnce || it + 1 == flagStrings.end())) { flags += flag.isShort ? params.shortSeparator : params.longSeparator; flags += params.valueOpen + postfix + params.valueClose; } } return flags; } public: FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_, extraError_ ? Options::Single : Options()), matcher(std::move(matcher_)) {} FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : NamedBase(name_, help_, options_), matcher(std::move(matcher_)) {} virtual ~FlagBase() {} virtual FlagBase *Match(const EitherFlag &flag) override { if (matcher.Match(flag)) { if ((GetOptions() & Options::Single) != Options::None && matched) { std::ostringstream problem; problem << "Flag '" << flag.str() << "' was passed multiple times, but is only allowed to be passed once"; #ifdef ARGS_NOEXCEPT error = Error::Extra; errorMsg = problem.str(); #else throw ExtraError(problem.str()); #endif } matched = true; return this; } 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()) { std::ostringstream problem; problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required"; #ifdef ARGS_NOEXCEPT error = Error::Required; errorMsg = problem.str(); #else 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 += params.proglineValueOpen + postfix + params.proglineValueClose; } return { IsRequired() ? params.proglineRequiredOpen + res + params.proglineRequiredClose : params.proglineNonrequiredOpen + res + params.proglineNonrequiredClose }; } virtual bool HasFlag() const override { return true; } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { const auto nargs = NumberOfArguments(); if (nargs.min > nargs.max) { return Error::Usage; } const auto matcherError = matcher.GetError(); if (matcherError != Error::None) { return matcherError; } return error; } #endif /** Defines how many values can be consumed by this option. * * \return closed interval [min, max] */ virtual Nargs NumberOfArguments() const noexcept = 0; /** Parse values of this option. * * \param value Vector of values. It's size must be in NumberOfArguments() interval. */ virtual void ParseValue(const std::vector &value) = 0; }; /** Base class for value-accepting flag options */ class ValueFlagBase : public FlagBase { public: ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : FlagBase(name_, help_, std::move(matcher_), extraError_) {} ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {} virtual ~ValueFlagBase() {} virtual Nargs NumberOfArguments() const noexcept override { return 1; } }; 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 { protected: bool ready; public: PositionalBase(const std::string &name_, const std::string &help_, Options options_ = {}) : NamedBase(name_, help_, options_), ready(true) {} virtual ~PositionalBase() {} bool Ready() { return ready; } virtual void ParseValue(const std::string &value_) = 0; virtual void Reset() noexcept override { matched = false; ready = true; #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } virtual PositionalBase *GetNextPositional() override { return Ready() ? this : nullptr; } virtual bool HasPositional() const override { return true; } virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { return { IsRequired() ? params.proglineRequiredOpen + Name() + params.proglineRequiredClose : params.proglineNonrequiredOpen + Name() + params.proglineNonrequiredClose }; } virtual void Validate(const std::string &, const std::string &) const override { if (IsRequired() && !Matched()) { std::ostringstream problem; problem << "Option '" << Name() << "' is required"; #ifdef ARGS_NOEXCEPT error = Error::Required; errorMsg = problem.str(); #else throw RequiredError(problem.str()); #endif } } }; /** Class for all kinds of validating groups, including ArgumentParser */ class Group : public Base { private: std::vector children; std::function validator; public: /** Default validators */ struct Validators { static bool Xor(const Group &group) { return group.MatchedChildren() == 1; } static bool AtLeastOne(const Group &group) { return group.MatchedChildren() >= 1; } static bool AtMostOne(const Group &group) { return group.MatchedChildren() <= 1; } static bool All(const Group &group) { return group.Children().size() == group.MatchedChildren(); } static bool AllOrNone(const Group &group) { return (All(group) || None(group)); } static bool AllChildGroups(const Group &group) { return std::none_of(std::begin(group.Children()), std::end(group.Children()), [](const Base* child) -> bool { return child->IsGroup() && !child->Matched(); }); } static bool DontCare(const Group &) { return true; } static bool CareTooMuch(const Group &) { return false; } static bool None(const Group &group) { return group.MatchedChildren() == 0; } }; /// If help is empty, this group will not be printed in help output Group(const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) {} /// If help is empty, this group will not be printed in help output Group(Group &group_, const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) { group_.Add(*this); } virtual ~Group() {} /** Append a child to this Group. */ void Add(Base &child) { children.emplace_back(&child); } /** Get all this group's children */ const std::vector &Children() const { return children; } /** Return the first FlagBase that matches flag, or nullptr * * \param flag The flag with prefixes stripped * \return the first matching FlagBase pointer, or nullptr if there is no match */ virtual FlagBase *Match(const EitherFlag &flag) override { for (Base *child: Children()) { if (FlagBase *match = child->Match(flag)) { return match; } } 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()) { child->Validate(shortPrefix, longPrefix); } } /** Get the next ready positional, or nullptr if there is none * * \return the first ready PositionalBase pointer, or nullptr if there is no match */ virtual PositionalBase *GetNextPositional() override { for (Base *child: Children()) { if (auto next = child->GetNextPositional()) { return next; } } return nullptr; } /** Get whether this has any FlagBase children * * \return Whether or not there are any FlagBase children */ virtual bool HasFlag() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasFlag(); }); } /** Get whether this has any PositionalBase children * * \return Whether or not there are any PositionalBase children */ virtual bool HasPositional() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasPositional(); }); } /** Get whether this has any Command children * * \return Whether or not there are any Command children */ virtual bool HasCommand() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasCommand(); }); } /** Count the number of matched children this group has */ std::vector::size_type MatchedChildren() const { return std::count_if(std::begin(Children()), std::end(Children()), [](const Base *child){return child->Matched();}); } /** Whether or not this group matches validation */ virtual bool Matched() const noexcept override { return validator(*this); } /** Get validation */ bool Get() const { return Matched(); } /** Get all the child descriptions for help generation */ virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override { std::vector> descriptions; // Push that group description on the back if not empty unsigned addindent = 0; if (!help.empty()) { descriptions.emplace_back(help, "", indent); addindent = 1; } for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) { continue; } auto groupDescriptions = child->GetDescription(params, indent + addindent); descriptions.insert( std::end(descriptions), std::make_move_iterator(std::begin(groupDescriptions)), std::make_move_iterator(std::end(groupDescriptions))); } return descriptions; } /** Get the names of positional parameters */ virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { std::vector names; for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromUsage) != Options::None) { continue; } auto groupNames = child->GetProgramLine(params); names.insert( std::end(names), std::make_move_iterator(std::begin(groupNames)), std::make_move_iterator(std::end(groupNames))); } return names; } virtual std::vector GetCommands() override { std::vector res; for (const auto &child : Children()) { auto subparsers = child->GetCommands(); res.insert(std::end(res), std::begin(subparsers), std::end(subparsers)); } return res; } virtual bool IsGroup() const override { return true; } virtual void Reset() noexcept override { Base::Reset(); for (auto &child: Children()) { child->Reset(); } #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { if (error != Error::None) { return error; } auto it = std::find_if(Children().begin(), Children().end(), [](const Base *child){return child->GetError() != Error::None;}); if (it == Children().end()) { return Error::None; } else { return (*it)->GetError(); } } #endif }; /** Class for using global options in ArgumentParser. */ class GlobalOptions : public Group { public: GlobalOptions(Group &base, Base &options_) : Group(base, {}, Group::Validators::DontCare, Options::Global) { Add(options_); } }; /** Utility class for building subparsers with coroutines/callbacks. * * Brief example: * \code * Command command(argumentParser, "command", "my command", [](args::Subparser &s) * { * // your command flags/positionals * s.Parse(); //required * //your command code * }); * \endcode * * For ARGS_NOEXCEPT mode don't forget to check `s.GetError()` after `s.Parse()` * and return if it isn't equals to args::Error::None. * * \sa Command */ class Subparser : public Group { private: std::vector args; std::vector kicked; ArgumentParser *parser = nullptr; const HelpParams &helpParams; const Command &command; bool isParsed = false; public: Subparser(std::vector args_, ArgumentParser &parser_, const Command &command_, const HelpParams &helpParams_) : args(std::move(args_)), parser(&parser_), helpParams(helpParams_), command(command_) { } Subparser(const Command &command_, const HelpParams &helpParams_) : helpParams(helpParams_), command(command_) { } Subparser(const Subparser&) = delete; Subparser(Subparser&&) = delete; Subparser &operator = (const Subparser&) = delete; Subparser &operator = (Subparser&&) = delete; const Command &GetCommand() { return command; } /** (INTERNAL) Determines whether Parse was called or not. */ bool IsParsed() const { return isParsed; } /** Continue parsing arguments for new command. */ void Parse(); /** Returns a vector of kicked out arguments. * * \sa Base::KickOut */ const std::vector &KickedOut() const noexcept { return kicked; } }; /** Main class for building subparsers. * * /sa Subparser */ class Command : public Group { private: friend class Subparser; std::string name; std::string help; std::string description; std::string epilog; std::string proglinePostfix; std::function parserCoroutine; bool commandIsRequired = true; Command *selectedCommand = nullptr; mutable std::vector> subparserDescription; mutable std::vector subparserProgramLine; mutable bool subparserHasFlag = false; mutable bool subparserHasPositional = false; mutable bool subparserHasCommand = false; #ifdef ARGS_NOEXCEPT mutable Error subparserError = Error::None; #endif mutable Subparser *subparser = nullptr; protected: class RaiiSubparser { public: RaiiSubparser(ArgumentParser &parser_, std::vector args_); RaiiSubparser(const Command &command_, const HelpParams ¶ms_); ~RaiiSubparser() { command.subparser = oldSubparser; } Subparser &Parser() { return parser; } private: const Command &command; Subparser parser; Subparser *oldSubparser; }; Command() = default; std::function &GetCoroutine() { return selectedCommand != nullptr ? selectedCommand->GetCoroutine() : parserCoroutine; } Command &SelectedCommand() { Command *res = this; while (res->selectedCommand != nullptr) { res = res->selectedCommand; } return *res; } const Command &SelectedCommand() const { const Command *res = this; while (res->selectedCommand != nullptr) { res = res->selectedCommand; } 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_)) { 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_; } /** The name of command */ const std::string &Name() const { return name; } /** The description of command */ const std::string &Help() const { return help; } /** If value is true, parser will fail if no command was parsed. * * Default: true. */ void RequireCommand(bool value) { commandIsRequired = value; } virtual bool IsGroup() const override { return false; } virtual bool Matched() const noexcept override { return Base::Matched(); } operator bool() const noexcept { return Matched(); } void Match() noexcept { matched = true; } void SelectCommand(Command *c) noexcept { selectedCommand = c; if (c != nullptr) { c->Match(); } } virtual FlagBase *Match(const EitherFlag &flag) override { if (selectedCommand != nullptr) { if (auto *res = selectedCommand->Match(flag)) { return res; } for (auto *child: Children()) { if ((child->GetOptions() & Options::Global) != Options::None) { if (auto *res = child->Match(flag)) { return res; } } } return nullptr; } if (subparser != nullptr) { return subparser->Match(flag); } 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 (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) { if (auto *res = selectedCommand->GetNextPositional()) { return res; } for (auto *child: Children()) { if ((child->GetOptions() & Options::Global) != Options::None) { if (auto *res = child->GetNextPositional()) { return res; } } } return nullptr; } if (subparser != nullptr) { return subparser->GetNextPositional(); } return Matched() ? Group::GetNextPositional() : nullptr; } virtual bool HasFlag() const override { return subparserHasFlag || Group::HasFlag(); } virtual bool HasPositional() const override { return subparserHasPositional || Group::HasPositional(); } virtual bool HasCommand() const override { return true; } std::vector GetCommandProgramLine(const HelpParams ¶ms) const { UpdateSubparserHelp(params); auto res = Group::GetProgramLine(params); res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end()); if (!params.proglineCommand.empty() && (Group::HasCommand() || subparserHasCommand)) { res.insert(res.begin(), commandIsRequired ? params.proglineCommand : "[" + params.proglineCommand + "]"); } if (!Name().empty()) { res.insert(res.begin(), Name()); } if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions && !params.proglineShowFlags) { res.push_back(params.proglineOptions); } if (!ProglinePostfix().empty()) { std::string line; for (char c : ProglinePostfix()) { if (isspace(c)) { if (!line.empty()) { res.push_back(line); line.clear(); } if (c == '\n') { res.push_back("\n"); } } else { line += c; } } if (!line.empty()) { res.push_back(line); } } return res; } virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { if (!Matched()) { return {}; } return GetCommandProgramLine(params); } virtual std::vector GetCommands() override { if (selectedCommand != nullptr) { return selectedCommand->GetCommands(); } if (Matched()) { return Group::GetCommands(); } return { this }; } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override { std::vector> descriptions; unsigned addindent = 0; UpdateSubparserHelp(params); if (!Matched()) { if (params.showCommandFullHelp) { std::ostringstream s; bool empty = true; for (const auto &progline: GetCommandProgramLine(params)) { if (!empty) { s << ' '; } else { empty = false; } s << progline; } descriptions.emplace_back(s.str(), "", indent); } else { descriptions.emplace_back(Name(), help, indent); } if (!params.showCommandChildren && !params.showCommandFullHelp) { return descriptions; } addindent = 1; } if (params.showCommandFullHelp && !Matched()) { descriptions.emplace_back("", "", indent + addindent); descriptions.emplace_back(Description().empty() ? Help() : Description(), "", indent + addindent); descriptions.emplace_back("", "", indent + addindent); } for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) { continue; } auto groupDescriptions = child->GetDescription(params, indent + addindent); descriptions.insert( std::end(descriptions), std::make_move_iterator(std::begin(groupDescriptions)), std::make_move_iterator(std::end(groupDescriptions))); } for (auto childDescription: subparserDescription) { std::get<2>(childDescription) += indent + addindent; descriptions.push_back(std::move(childDescription)); } if (params.showCommandFullHelp && !Matched()) { descriptions.emplace_back("", "", indent + addindent); if (!Epilog().empty()) { descriptions.emplace_back(Epilog(), "", indent + addindent); descriptions.emplace_back("", "", indent + addindent); } } return descriptions; } virtual void Validate(const std::string &shortprefix, const std::string &longprefix) const override { if (!Matched()) { return; } for (Base *child: Children()) { if (child->IsGroup() && !child->Matched()) { std::ostringstream problem; problem << "Group validation failed somewhere!"; #ifdef ARGS_NOEXCEPT error = Error::Validation; errorMsg = problem.str(); #else throw ValidationError(problem.str()); #endif } child->Validate(shortprefix, longprefix); } if (subparser != nullptr) { subparser->Validate(shortprefix, longprefix); } if (selectedCommand == nullptr && commandIsRequired && (Group::HasCommand() || subparserHasCommand)) { std::ostringstream problem; problem << "Command is required"; #ifdef ARGS_NOEXCEPT error = Error::Validation; errorMsg = problem.str(); #else throw ValidationError(problem.str()); #endif } } virtual void Reset() noexcept override { Group::Reset(); selectedCommand = nullptr; subparserProgramLine.clear(); subparserDescription.clear(); subparserHasFlag = false; subparserHasPositional = false; subparserHasCommand = false; #ifdef ARGS_NOEXCEPT subparserError = Error::None; #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { if (!Matched()) { return Error::None; } if (error != Error::None) { return error; } if (subparserError != Error::None) { return subparserError; } return Group::GetError(); } #endif }; /** The main user facing command line argument parser class */ class ArgumentParser : public Command { friend class Subparser; private: std::string longprefix; std::string shortprefix; std::string longseparator; std::string terminator; bool allowJoinedShortValue = true; bool allowJoinedLongValue = true; bool allowSeparateShortValue = true; bool allowSeparateLongValue = true; CompletionFlag *completion = nullptr; bool readCompletion = false; protected: enum class OptionType { LongFlag, ShortFlag, Positional }; OptionType ParseOption(const std::string &s, bool allowEmpty = false) { if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length())) { return OptionType::LongFlag; } if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length())) { return OptionType::ShortFlag; } 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 * \param[in, out] it The iterator to first value. It will point to the last value * \param end The end iterator * \param joinedArg Joined value (e.g. bar in --foo=bar) * \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd) * \param[out] values The vector to store parsed arg's values */ template std::string ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end, const bool allowSeparate, const bool allowJoined, const bool hasJoined, const std::string &joinedArg, const bool canDiscardJoined, std::vector &values) { values.clear(); Nargs nargs = flag.NumberOfArguments(); if (hasJoined && !allowJoined && nargs.min != 0) { return "Flag '" + arg + "' was passed a joined argument, but these are disallowed"; } if (hasJoined) { if (!canDiscardJoined || nargs.max != 0) { values.push_back(joinedArg); } } else if (!allowSeparate) { if (nargs.min != 0) { return "Flag '" + arg + "' was passed a separate argument, but these are disallowed"; } } else { auto valueIt = it; ++valueIt; while (valueIt != end && 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; ++valueIt; } } if (values.size() > nargs.max) { return "Passed an argument into a non-argument flag: " + arg; } else if (values.size() < nargs.min) { if (nargs.min == 1 && nargs.max == 1) { return "Flag '" + arg + "' requires an argument but received none"; } else if (nargs.min == 1) { return "Flag '" + arg + "' requires at least one argument but received none"; } else if (nargs.min != nargs.max) { return "Flag '" + arg + "' requires at least " + std::to_string(nargs.min) + " arguments but received " + std::to_string(values.size()); } else { return "Flag '" + arg + "' requires " + std::to_string(nargs.min) + " arguments but received " + std::to_string(values.size()); } } return {}; } template bool ParseLong(It &it, It end) { const auto &chunk = *it; const auto argchunk = chunk.substr(longprefix.size()); // Try to separate it, in case of a separator: const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator); // If the separator is in the argument, separate it. const auto arg = (separator != argchunk.npos ? std::string(argchunk, 0, separator) : argchunk); const auto joined = (separator != argchunk.npos ? argchunk.substr(separator + longseparator.size()) : std::string()); if (auto flag = Match(arg)) { std::vector values; const std::string errorMessage = ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue, separator != argchunk.npos, joined, false, values); if (!errorMessage.empty()) { #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } if (!readCompletion) { flag->ParseValue(values); } if (flag->KickOut()) { ++it; return false; } } else { const std::string errorMessage("Flag could not be matched: " + arg); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } return true; } template bool ParseShort(It &it, It end) { const auto &chunk = *it; const auto argchunk = chunk.substr(shortprefix.size()); for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit) { const auto arg = *argit; if (auto flag = Match(arg)) { const std::string value(argit + 1, std::end(argchunk)); std::vector values; const std::string errorMessage = ParseArgsValues(*flag, std::string(1, arg), it, end, allowSeparateShortValue, allowJoinedShortValue, !value.empty(), value, !value.empty(), values); if (!errorMessage.empty()) { #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } if (!readCompletion) { flag->ParseValue(values); } if (flag->KickOut()) { ++it; return false; } if (!values.empty()) { break; } } else { const std::string errorMessage("Flag could not be matched: '" + std::string(1, arg) + "'"); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } } 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 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(); 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 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) { terminated = true; } else if (!terminated && ParseOption(chunk) == OptionType::LongFlag) { if (!ParseLong(it, end)) { return it; } } else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag) { if (!ParseShort(it, end)) { return it; } } else if (!terminated && !commands.empty()) { auto itCommand = std::find_if(commands.begin(), commands.end(), [&chunk](Command *c) { return c->Name() == chunk; }); if (itCommand == commands.end()) { const std::string errorMessage("Unknown command: " + chunk); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return it; #endif } SelectCommand(*itCommand); if (const auto &coroutine = GetCoroutine()) { ++it; RaiiSubparser coro(*this, std::vector(it, end)); coroutine(coro.Parser()); #ifdef ARGS_NOEXCEPT error = GetError(); if (error != Error::None) { return end; } if (!coro.Parser().IsParsed()) { error = Error::Usage; return end; } #else if (!coro.Parser().IsParsed()) { throw UsageError("Subparser::Parse was not called"); } #endif break; } commands = GetCommands(); } else { auto pos = GetNextPositional(); if (pos) { pos->ParseValue(chunk); if (pos->KickOut()) { return ++it; } } else { const std::string errorMessage("Passed in argument, but no positional arguments were ready to receive it: " + chunk); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return it; #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); 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); return end; } public: HelpParams helpParams; 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; } void AddCompletion(CompletionFlag &completionFlag) { completion = &completionFlag; Add(completionFlag); } /** The program name for help generation */ const std::string &Prog() const { return helpParams.programName; } /** The program name for help generation */ void Prog(const std::string &prog_) { this->helpParams.programName = prog_; } /** The prefix for long flags */ const std::string &LongPrefix() const { return longprefix; } /** The prefix for long flags */ void LongPrefix(const std::string &longprefix_) { this->longprefix = longprefix_; this->helpParams.longPrefix = longprefix_; } /** The prefix for short flags */ const std::string &ShortPrefix() const { return shortprefix; } /** The prefix for short flags */ void ShortPrefix(const std::string &shortprefix_) { this->shortprefix = shortprefix_; this->helpParams.shortPrefix = shortprefix_; } /** The separator for long flags */ const std::string &LongSeparator() const { return longseparator; } /** The separator for long flags */ void LongSeparator(const std::string &longseparator_) { if (longseparator_.empty()) { const std::string errorMessage("longseparator can not be set to empty"); #ifdef ARGS_NOEXCEPT error = Error::Usage; errorMsg = errorMessage; #else throw UsageError(errorMessage); #endif } else { this->longseparator = longseparator_; this->helpParams.longSeparator = allowJoinedLongValue ? longseparator_ : " "; } } /** The terminator that forcibly separates flags from positionals */ const std::string &Terminator() const { return terminator; } /** The terminator that forcibly separates flags from positionals */ void Terminator(const std::string &terminator_) { this->terminator = terminator_; } /** Get the current argument separation parameters. * * See SetArgumentSeparations for details on what each one means. */ void GetArgumentSeparations( bool &allowJoinedShortValue_, bool &allowJoinedLongValue_, bool &allowSeparateShortValue_, bool &allowSeparateLongValue_) const { allowJoinedShortValue_ = this->allowJoinedShortValue; allowJoinedLongValue_ = this->allowJoinedLongValue; allowSeparateShortValue_ = this->allowSeparateShortValue; allowSeparateLongValue_ = this->allowSeparateLongValue; } /** Change allowed option separation. * * \param allowJoinedShortValue_ Allow a short flag that accepts an argument to be passed its argument immediately next to it (ie. in the same argv field) * \param allowJoinedLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by the longseparator (ie. in the same argv field) * \param allowSeparateShortValue_ Allow a short flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) * \param allowSeparateLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) */ void SetArgumentSeparations( const bool allowJoinedShortValue_, const bool allowJoinedLongValue_, const bool allowSeparateShortValue_, const bool allowSeparateLongValue_) { this->allowJoinedShortValue = allowJoinedShortValue_; this->allowJoinedLongValue = allowJoinedLongValue_; this->allowSeparateShortValue = allowSeparateShortValue_; this->allowSeparateLongValue = allowSeparateLongValue_; this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " "; this->helpParams.shortSeparator = allowJoinedShortValue ? "" : " "; } /** Pass the help menu into an ostream */ void Help(std::ostream &help_) const { auto &command = SelectedCommand(); const auto &commandDescription = command.Description().empty() ? command.Help() : command.Description(); const auto description_text = Wrap(commandDescription, helpParams.width - helpParams.descriptionindent); const auto epilog_text = Wrap(command.Epilog(), helpParams.width - helpParams.descriptionindent); const bool hasoptions = command.HasFlag(); const bool hasarguments = command.HasPositional(); std::vector prognameline; prognameline.push_back(helpParams.usageString); prognameline.push_back(Prog()); auto commandProgLine = command.GetProgramLine(helpParams); prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end()); const auto proglines = Wrap(prognameline.begin(), prognameline.end(), helpParams.width - (helpParams.progindent + helpParams.progtailindent), helpParams.width - helpParams.progindent); auto progit = std::begin(proglines); if (progit != std::end(proglines)) { help_ << std::string(helpParams.progindent, ' ') << *progit << '\n'; ++progit; } for (; progit != std::end(proglines); ++progit) { help_ << std::string(helpParams.progtailindent, ' ') << *progit << '\n'; } help_ << '\n'; if (!description_text.empty()) { for (const auto &line: description_text) { help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; } help_ << "\n"; } bool lastDescriptionIsNewline = false; if (!helpParams.optionsString.empty()) { help_ << std::string(helpParams.progindent, ' ') << helpParams.optionsString << "\n\n"; } for (const auto &desc: command.GetDescription(helpParams, 0)) { lastDescriptionIsNewline = std::get<0>(desc).empty() && std::get<1>(desc).empty(); 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)); const auto info = Wrap(std::get<1>(desc), helpParams.width - (helpParams.helpindent + groupindent)); std::string::size_type flagssize = 0; for (auto flagsit = std::begin(flags); flagsit != std::end(flags); ++flagsit) { if (flagsit != std::begin(flags)) { help_ << '\n'; } help_ << std::string(groupindent + helpParams.flagindent, ' ') << *flagsit; flagssize = Glyphs(*flagsit); } auto infoit = std::begin(info); // groupindent is on both sides of this inequality, and therefore can be removed if ((helpParams.flagindent + flagssize + helpParams.gutter) > helpParams.helpindent || infoit == std::end(info) || helpParams.addNewlineBeforeDescription) { help_ << '\n'; } else { // groupindent is on both sides of the minus sign, and therefore doesn't actually need to be in here help_ << std::string(helpParams.helpindent - (helpParams.flagindent + flagssize), ' ') << *infoit << '\n'; ++infoit; } for (; infoit != std::end(info); ++infoit) { help_ << std::string(groupindent + helpParams.helpindent, ' ') << *infoit << '\n'; } } if (hasoptions && hasarguments && helpParams.showTerminator) { lastDescriptionIsNewline = false; for (const auto &item: Wrap(std::string("\"") + terminator + "\" can be used to terminate flag options and force all following arguments to be treated as positional options", helpParams.width - helpParams.flagindent)) { help_ << std::string(helpParams.flagindent, ' ') << item << '\n'; } } if (!lastDescriptionIsNewline) { help_ << "\n"; } for (const auto &line: epilog_text) { help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; } } /** Generate a help menu as a string. * * \return the help text as a single string */ std::string Help() const { std::ostringstream help_; Help(help_); return help_.str(); } virtual void Reset() noexcept override { Command::Reset(); matched = true; readCompletion = false; } /** Parse all arguments. * * \param begin an iterator to the beginning of the argument list * \param end an iterator to the past-the-end element of the argument list * \return the iterator after the last parsed value. Only useful for kick-out */ template It ParseArgs(It begin, It end) { // Reset all Matched statuses and errors Reset(); #ifdef ARGS_NOEXCEPT error = GetError(); if (error != Error::None) { return end; } #endif return Parse(begin, end); } /** Parse all arguments. * * \param args an iterable of the arguments * \return the iterator after the last parsed value. Only useful for kick-out */ template auto ParseArgs(const T &args) -> decltype(std::begin(args)) { return ParseArgs(std::begin(args), std::end(args)); } /** Convenience function to parse the CLI from argc and argv * * Just assigns the program name and vectorizes arguments for passing into ParseArgs() * * \return whether or not all arguments were parsed. This works for detecting kick-out, but is generally useless as it can't do anything with it. */ bool ParseCLI(const int argc, const char * const * argv) { if (Prog().empty()) { Prog(argv[0]); } const std::vector args(argv + 1, argv + argc); return ParseArgs(args) == std::end(args); } template bool ParseCLI(const T &args) { return ParseArgs(args) == std::end(args); } }; inline Command::RaiiSubparser::RaiiSubparser(ArgumentParser &parser_, std::vector args_) : command(parser_.SelectedCommand()), parser(std::move(args_), parser_, command, parser_.helpParams), oldSubparser(command.subparser) { command.subparser = &parser; } inline Command::RaiiSubparser::RaiiSubparser(const Command &command_, const HelpParams ¶ms_): command(command_), parser(command, params_), oldSubparser(command.subparser) { command.subparser = &parser; } inline void Subparser::Parse() { isParsed = true; Reset(); command.subparserDescription = GetDescription(helpParams, 0); command.subparserHasFlag = HasFlag(); command.subparserHasPositional = HasPositional(); command.subparserHasCommand = HasCommand(); command.subparserProgramLine = GetProgramLine(helpParams); if (parser == nullptr) { #ifndef ARGS_NOEXCEPT throw args::SubparserError(); #else error = Error::Subparser; return; #endif } auto it = parser->Parse(args.begin(), args.end()); command.Validate(parser->ShortPrefix(), parser->LongPrefix()); kicked.assign(it, args.end()); #ifdef ARGS_NOEXCEPT command.subparserError = GetError(); #endif } inline std::ostream &operator<<(std::ostream &os, const ArgumentParser &parser) { parser.Help(os); return os; } /** Boolean argument matcher */ class Flag : public FlagBase { public: Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): FlagBase(name_, help_, std::move(matcher_), options_) { group_.Add(*this); } Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None) { } virtual ~Flag() {} /** Get whether this was matched */ bool Get() const { return Matched(); } virtual Nargs NumberOfArguments() const noexcept override { return 0; } virtual void ParseValue(const std::vector&) override { } }; /** Help flag class * * Works like a regular flag, but throws an instance of Help when it is matched */ class HelpFlag : public Flag { public: HelpFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_) {} virtual ~HelpFlag() {} virtual void ParseValue(const std::vector &) { #ifdef ARGS_NOEXCEPT error = Error::Help; errorMsg = Name(); #else throw Help(Name()); #endif } /** Get whether this was matched */ bool Get() const noexcept { return Matched(); } }; /** A flag class that simply counts the number of times it's matched */ class CounterFlag : public Flag { private: const int startcount; int count; public: CounterFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const int startcount_ = 0, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_), startcount(startcount_), count(startcount_) {} virtual ~CounterFlag() {} virtual FlagBase *Match(const EitherFlag &arg) override { auto me = FlagBase::Match(arg); if (me) { ++count; } return me; } /** Get the count */ int &Get() noexcept { return count; } virtual void Reset() noexcept override { FlagBase::Reset(); count = startcount; } }; /** A flag class that calls a function when it's matched */ class ActionFlag : public FlagBase { private: std::function &)> action; Nargs nargs; public: ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, std::function &)> action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), action(std::move(action_)), nargs(nargs_) { group_.Add(*this); } ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), nargs(1) { group_.Add(*this); action = [action_](const std::vector &a) { return action_(a.at(0)); }; } ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), nargs(0) { group_.Add(*this); action = [action_](const std::vector &) { return action_(); }; } virtual Nargs NumberOfArguments() const noexcept override { return nargs; } virtual void ParseValue(const std::vector &value) override { action(value); } }; /** A default Reader class for argument classes * * If destination type is assignable to std::string it uses an assignment to std::string. * Otherwise ValueReader simply uses a std::istringstream to read into the destination type, and * raises a ParseError if there are any characters left. */ struct ValueReader { template typename std::enable_if::value, bool>::type operator ()(const std::string &name, const std::string &value, T &destination) { std::istringstream ss(value); ss >> destination >> std::ws; if (ss.rdbuf()->in_avail() > 0) { #ifdef ARGS_NOEXCEPT (void)name; return false; #else std::ostringstream problem; problem << "Argument '" << name << "' received invalid value type '" << value << "'"; throw ParseError(problem.str()); #endif } return true; } template typename std::enable_if::value, bool>::type operator()(const std::string &, const std::string &value, T &destination) { destination = value; return true; } }; /** An argument-accepting flag class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class ValueFlag : public ValueFlagBase { protected: T value; T defaultValue; virtual std::string GetDefaultString(const HelpParams&) const override { return detail::ToString(defaultValue); } private: Reader reader; public: ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_), defaultValue(defaultValue_) { group_.Add(*this); } ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, extraError_ ? Options::Single : Options::None) { } ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_) { } virtual ~ValueFlag() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); #ifdef ARGS_NOEXCEPT if (!reader(name, value_, this->value)) { error = Error::Parse; } #else reader(name, value_, this->value); #endif } virtual void Reset() noexcept override { ValueFlagBase::Reset(); value = defaultValue; } /** Get the value */ T &Get() noexcept { return value; } /** Get the default value */ const T &GetDefault() noexcept { return defaultValue; } }; /** An optional argument-accepting flag class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class ImplicitValueFlag : public ValueFlag { protected: T implicitValue; public: ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {}) : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(implicitValue_) { } ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), Options options_ = {}) : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(defaultValue_) { } ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : ValueFlag(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue() { } virtual ~ImplicitValueFlag() {} virtual Nargs NumberOfArguments() const noexcept override { return {0, 1}; } virtual void ParseValue(const std::vector &value_) override { if (value_.empty()) { this->value = implicitValue; } else { ValueFlag::ParseValue(value_); } } }; /** A variadic arguments accepting flag class * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class NargsValueFlag : public FlagBase { protected: List values; const List defaultValues; Nargs nargs; Reader reader; public: typedef List Container; typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, const List &defaultValues_ = {}, Options options_ = {}) : FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_),nargs(nargs_) { group_.Add(*this); } virtual ~NargsValueFlag() {} virtual Nargs NumberOfArguments() const noexcept override { return nargs; } virtual void ParseValue(const std::vector &values_) override { values.clear(); for (const std::string &value : values_) { T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value, v)) { error = Error::Parse; } #else reader(name, value, v); #endif values.insert(std::end(values), v); } } List &Get() noexcept { return values; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } virtual void Reset() noexcept override { FlagBase::Reset(); values = defaultValues; } virtual FlagBase *Match(const EitherFlag &arg) override { const bool wasMatched = Matched(); auto me = FlagBase::Match(arg); if (me && !wasMatched) { values.clear(); } return me; } }; /** An argument-accepting flag class that pushes the found values into a list * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class ValueFlagList : public ValueFlagBase { private: using Container = List; Container values; const Container defaultValues; Reader reader; public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; ValueFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Container &defaultValues_ = Container(), Options options_ = {}): ValueFlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_) { group_.Add(*this); } virtual ~ValueFlagList() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, v)) { error = Error::Parse; } #else reader(name, value_, v); #endif values.insert(std::end(values), v); } /** Get the values */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { ValueFlagBase::Reset(); values = defaultValues; } virtual FlagBase *Match(const EitherFlag &arg) override { const bool wasMatched = Matched(); auto me = FlagBase::Match(arg); if (me && !wasMatched) { values.clear(); } return me; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A mapping value flag class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, typename Reader = ValueReader, template class Map = std::unordered_map> class MapFlag : public ValueFlagBase { private: const Map map; T value; const T defaultValue; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_), defaultValue(defaultValue_) { group_.Add(*this); } MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_ = T(), const bool extraError_ = false): MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_, extraError_ ? Options::Single : Options::None) { } MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, Options options_): MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_) { } virtual ~MapFlag() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->value = it->second; } } /** Get the value */ T &Get() noexcept { return value; } virtual void Reset() noexcept override { ValueFlagBase::Reset(); value = defaultValue; } }; /** A mapping value flag list class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, template class List = std::vector, typename Reader = ValueReader, template class Map = std::unordered_map> class MapFlagList : public ValueFlagBase { private: using Container = List; const Map map; Container values; const Container defaultValues; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; MapFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const Container &defaultValues_ = Container()): ValueFlagBase(name_, help_, std::move(matcher_)), map(map_), values(defaultValues_), defaultValues(defaultValues_) { group_.Add(*this); } virtual ~MapFlagList() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value = values_.at(0); K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value, key)) { error = Error::Parse; } #else reader(name, value, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->values.emplace_back(it->second); } } /** Get the value */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { ValueFlagBase::Reset(); values = defaultValues; } virtual FlagBase *Match(const EitherFlag &arg) override { const bool wasMatched = Matched(); auto me = FlagBase::Match(arg); if (me && !wasMatched) { values.clear(); } return me; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A positional argument class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class Positional : public PositionalBase { private: T value; const T defaultValue; Reader reader; public: Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), value(defaultValue_), defaultValue(defaultValue_) { group_.Add(*this); } Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_): Positional(group_, name_, help_, T(), options_) { } virtual ~Positional() {} virtual void ParseValue(const std::string &value_) override { #ifdef ARGS_NOEXCEPT if (!reader(name, value_, this->value)) { error = Error::Parse; } #else reader(name, value_, this->value); #endif ready = false; matched = true; } /** Get the value */ T &Get() noexcept { return value; } virtual void Reset() noexcept override { PositionalBase::Reset(); value = defaultValue; } }; /** A positional argument class that pushes the found values into a list * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class PositionalList : public PositionalBase { private: using Container = List; Container values; const Container defaultValues; Reader reader; public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; PositionalList(Group &group_, const std::string &name_, const std::string &help_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), values(defaultValues_), defaultValues(defaultValues_) { group_.Add(*this); } PositionalList(Group &group_, const std::string &name_, const std::string &help_, Options options_): PositionalList(group_, name_, help_, {}, options_) { } virtual ~PositionalList() {} virtual void ParseValue(const std::string &value_) override { T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, v)) { error = Error::Parse; } #else reader(name, value_, v); #endif values.insert(std::end(values), v); matched = true; } virtual std::string Name() const override { return name + std::string("..."); } /** Get the values */ Container &Get() noexcept { return values; } virtual void Reset() noexcept override { PositionalBase::Reset(); values = defaultValues; } virtual PositionalBase *GetNextPositional() override { const bool wasMatched = Matched(); auto me = PositionalBase::GetNextPositional(); if (me && !wasMatched) { values.clear(); } return me; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A positional argument mapping class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, typename Reader = ValueReader, template class Map = std::unordered_map> class MapPositional : public PositionalBase { private: const Map map; T value; const T defaultValue; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: MapPositional(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), map(map_), value(defaultValue_), defaultValue(defaultValue_) { group_.Add(*this); } virtual ~MapPositional() {} virtual void ParseValue(const std::string &value_) override { K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->value = it->second; ready = false; matched = true; } } /** Get the value */ T &Get() noexcept { return value; } virtual void Reset() noexcept override { PositionalBase::Reset(); value = defaultValue; } }; /** A positional argument mapping list class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, template class List = std::vector, typename Reader = ValueReader, template class Map = std::unordered_map> class MapPositionalList : public PositionalBase { private: using Container = List; const Map map; Container values; const Container defaultValues; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; MapPositionalList(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), map(map_), values(defaultValues_), defaultValues(defaultValues_) { group_.Add(*this); } virtual ~MapPositionalList() {} virtual void ParseValue(const std::string &value_) override { K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->values.emplace_back(it->second); matched = true; } } /** Get the value */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { PositionalBase::Reset(); values = defaultValues; } virtual PositionalBase *GetNextPositional() override { const bool wasMatched = Matched(); auto me = PositionalBase::GetNextPositional(); if (me && !wasMatched) { values.clear(); } return me; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; } #endif