diff options
-rw-r--r-- | args.hxx | 199 |
1 files changed, 194 insertions, 5 deletions
@@ -2,13 +2,56 @@ * This code is released under the license described in the LICENSE file */ +#include <algorithm> +#include <functional> +#include <list> +#include <sstream> #include <string> #include <vector> -#include <list> -#include <functional> +#include <exception> namespace args { + class ParseError : public std::runtime_error + { + public: + ParseError(const char *problem) : std::runtime_error(problem) {} + virtual ~ParseError() {}; + }; + + // A class of "matchers", specifying short and long options that can possibly be matched + class Matcher + { + private: + std::vector<char> shortOpts; + std::vector<std::string> longOpts; + + public: + // Specify short and long opts separately as iterators + template <typename ShortIt, typename LongIt> + Matcher(ShortIt shortOptsStart, ShortIt shortOptsEnd, LongIt longOptsStart, LongIt longOptsEnd) : + shortOpts(shortOptsStart, shortOptsEnd), + longOpts(longOptsStart, longOptsEnd) + {} + + // Specify short and long opts separately as iterables + template <typename Short, typename Long> + Matcher(Short &&shortIn, Long &&longIn) : + Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn)) + {} + + bool Match(const char opt) const + { + return std::find(shortOpts.begin(), shortOpts.end(), opt) != shortOpts.end(); + } + + bool Match(const std::string &opt) const + { + return std::find(longOpts.begin(), longOpts.end(), opt) != longOpts.end(); + } + }; + + // Base class for groups and individual argument types class Base { protected: @@ -18,10 +61,25 @@ namespace args Base() : matched(false) {} virtual ~Base() {} + virtual Base *Match(const std::string &arg) = 0; + virtual Base *Match(const char arg) = 0; + virtual bool Matched() const { return matched; } + + operator bool() const + { + return matched; + } + }; + + // Base class that takes arguments + class ArgBase : public Base + { + public: + virtual ~ArgBase() {} }; class Group : public Base @@ -35,6 +93,32 @@ namespace args Group(const std::function<bool(int, int)> &validator = Validators::DontCare) : validator(validator) {} virtual ~Group() {} + virtual Base *Match(const std::string &arg) override + { + for (Base *child: children) + { + Base *match = child->Match(arg); + if (match) + { + return match; + } + } + return nullptr; + } + + virtual Base *Match(const char arg) override + { + for (Base *child: children) + { + Base *match = child->Match(arg); + if (match) + { + return match; + } + } + return nullptr; + } + void Add(Base &child) { children.emplace_back(&child); @@ -104,20 +188,79 @@ namespace args std::string description; std::string epilog; - Group args; + std::string longprefix; + std::string shortprefix; + + Group group; public: ArgumentParser( - const std::string &prog, const std::string &description, - const std::string &epilog = std::string()) : prog(prog), description(description), epilog(epilog) {} + const std::string &longprefix = "--", + const std::string &shortprefix = "-", + const std::string &prog = std::string(), + const std::string &epilog = std::string() + ) : + prog(prog), + description(description), + epilog(epilog), + longprefix(longprefix), + shortprefix(shortprefix), + group(Group::Validators::DontCare) {} + + void Add(Base &item) + { + group.Add(item); + } void ParseArgs(const std::vector<std::string> &args) { + for (auto it = std::begin(args); it != std::end(args); ++it) + { + const std::string &chunk = *it; + if (chunk.find(longprefix) == 0 && chunk.size() > longprefix.size()) + { + const std::string argchunk(chunk.substr(longprefix.size())); + Base *base = group.Match(argchunk); + if (base) + { + // Do match logic here, specifically passing an + // argument, if necessary, to the base. Query the + // base and see if it takes an argument + } else + { + std::ostringstream problem; + problem << "Argument could not be matched: " << chunk; + throw ParseError(problem.str().c_str()); + } + } else if (chunk.find(shortprefix) == 0 && chunk.size() > shortprefix.size()) + { + const std::string argchunk(chunk.substr(shortprefix.size())); + for (const char &arg: argchunk) + { + Base *base = group.Match(arg); + if (base) + { + // Do match logic here, specifically passing an + // argument, if necessary, to the base. Query the + // base and see if it takes an argument + } else + { + std::ostringstream problem; + problem << "Argument could not be matched: " << arg; + throw ParseError(problem.str().c_str()); + } + } + } + } } void ParseCLI(const int argc, const char * const * const argv) { + if (prog.empty()) + { + prog.assign(argv[0]); + } std::vector<std::string> args; for (int i = 1; i < argc; ++i) { @@ -126,4 +269,50 @@ namespace args ParseArgs(args); } }; + + // Boolean argument matcher + class Flag : public Base + { + private: + Matcher matcher; + + public: + //template <typename ... Types> + //Flag(std::string help, Types ...args): matcher(args...) {} + + template <typename ... Types> + Flag(ArgumentParser &parser, std::string help, Types&& ...args): matcher(args...) + { + parser.Add(*this); + } + + template <typename Short, typename Long> + Flag(ArgumentParser &parser, std::string help, const std::initializer_list<Short> &shortIn, const std::initializer_list<Long> &longIn): matcher(shortIn, longIn) + { + parser.Add(*this); + } + + virtual ~Flag() {} + + virtual Base *Match(const std::string &arg) override + { + if (matcher.Match(arg)) + { + matched = true; + return this; + } + return nullptr; + } + + virtual Base *Match(const char arg) override + { + if (matcher.Match(arg)) + { + matched = true; + return this; + } + return nullptr; + } + }; + } |