From ce3081299825e6aaf5529b13f0b248a86b394722 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Thu, 26 Jan 2017 14:17:34 -0500 Subject: [PATCH] Adding better parser --- .gitignore | 1 + CLI.hpp | 434 +++++++++++++++++++++++++++++++++++++++++++---------- try.cpp | 8 +- 3 files changed, 358 insertions(+), 85 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d708fd8a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +a.out* diff --git a/CLI.hpp b/CLI.hpp index 689c5cd8..2931e588 100644 --- a/CLI.hpp +++ b/CLI.hpp @@ -9,12 +9,15 @@ #include #include #include +#include +#include +#include // This is unreachable outside this file; you should not use Combiner directly namespace { void logit(std::string output) { - std::cout << output << std::endl; + std::cout << "\033[1;31m" << output << "\033[0m" << std::endl; } template @@ -30,8 +33,6 @@ std::string join(const T& v, std::string delim = ",") { } - - struct Combiner { int num; bool positional; @@ -41,7 +42,7 @@ struct Combiner { /// Can be or-ed together Combiner operator | (Combiner b) const { Combiner self; - self.num = num + b.num; + self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num); self.positional = positional || b.positional; self.required = required || b.required; self.defaulted = defaulted || b.defaulted; @@ -56,40 +57,66 @@ struct Combiner { return *this | b; } }; - - } + namespace CLI { -class BadNameString : public std::runtime_error { +class Error : public std::runtime_error { public: - BadNameString(std::string name) : runtime_error("Failed to parse: " + name) {}; + Error(std::string name) : runtime_error(name) {}; }; -class CallForHelp : public std::runtime_error { +class BadNameString : public Error { public: - CallForHelp() : runtime_error("Help option passed") {}; + BadNameString(std::string name) : Error("Failed to parse: " + name) {}; }; -class ParseError : public std::runtime_error { +class CallForHelp : public Error { public: - ParseError(std::string info="") : runtime_error(info) {}; + CallForHelp() : Error("Help option passed") {}; }; -class OptionAlreadyAdded : public std::runtime_error { +class ParseError : public Error { public: - OptionAlreadyAdded(std::string name) : runtime_error("Already added:" + name) {}; + ParseError(std::string info="") : Error(info) {}; }; +class OptionAlreadyAdded : public Error { +public: + OptionAlreadyAdded(std::string name) : Error("Already added:" + name) {}; +}; +class OptionNotFound : public Error { +public: + OptionNotFound(std::string name) : Error(name) {}; +}; + +class RequiredError : public Error { +public: + RequiredError(std::string name="") : Error(name) {}; +}; + +class ExtraPositionalsError : public Error { +public: + ExtraPositionalsError(std::string name="") : Error(name) {}; +}; + +class HorribleError : public Error { +public: + HorribleError(std::string name="") : Error("You should never see this error! "+name) {}; +}; +class IncorrectConstruction : public Error { +public: + IncorrectConstruction(std::string name="") : Error(name) {}; +}; const std::regex reg_split{R"regex((?:([a-zA-Z0-9]?)(?:,|$)|^)([a-zA-Z0-9][a-zA-Z0-9_\-]*)?)regex"}; const std::regex reg_short{R"regex(-([^-])(.*))regex"}; const std::regex reg_long{R"regex(--([^-^=][^=]*)=?(.*))regex"}; -std::tuple split(std::string fullname) throw(BadNameString) { +std::tuple split(std::string fullname) { std::smatch match; if (std::regex_match(fullname, match, reg_split)) { @@ -101,11 +128,12 @@ std::tuple split(std::string fullname) throw(BadNameSt } else throw BadNameString(fullname); } -const Combiner NOTHING {0,false,false,false}; -const Combiner REQUIRED {0,false,true, false}; -const Combiner DEFAULT {0,false,false,true}; -const Combiner POSITIONAL{0,true, false,false}; -const Combiner ARGS {1,false,false,false}; +const Combiner NOTHING {0, false,false,false}; +const Combiner REQUIRED {1, false,true, false}; +const Combiner DEFAULT {1, false,false,true}; +const Combiner POSITIONAL{1, true, false,false}; +const Combiner ARGS {1, false,false,false}; +const Combiner UNLIMITED {-1,false,false,false}; typedef std::vector> results_t; typedef std::function callback_t; @@ -125,11 +153,27 @@ protected: public: - Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function callback=[](results_t){return true;}) throw (BadNameString) : + Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function callback=[](results_t){return true;}) : opts(opts), discription(discription), callback(callback){ std::tie(sname, lname) = split(name); } + bool required() const { + return opts.required; + } + + int expected() const { + return opts.num; + } + + bool positional() const { + return opts.positional; + } + + bool defaulted() const { + return opts.defaulted; + } + /// Process the callback bool run_callback() const { return callback(results); @@ -145,7 +189,7 @@ public: return sname==other.sname || lname==other.lname; } - std::string getName() const { + std::string get_name() const { if(sname=="") return "--" + lname; else if (lname=="") @@ -154,6 +198,10 @@ public: return "-" + sname + ", --" + lname; } + bool check_name(const std::string& name) const { + return name == sname || name == lname || name == sname + "," + lname; + } + bool check_sname(const std::string& name) const { return name == sname; } @@ -170,24 +218,23 @@ public: return lname; } - - int get_num() const { - return opts.num; - } - void add_result(int r, std::string s) { + logit("Adding result: " + s); results.at(r).push_back(s); } int get_new() { results.emplace_back(); return results.size() - 1; } - int count() { - return results.size(); + int count() const { + int out = 0; + for(const std::vector& v : results) + out += v.size(); + return out; } std::string string() const { - std::string val = "Option: " + getName() + "\n" + std::string val = "Option: " + get_name() + "\n" + " " + discription + "\n" + " ["; for(const auto& item : results) { @@ -199,8 +246,58 @@ public: return val; } + int help_len() const { + return std::min((int) get_name().length(), 40); + } + + std::string help(int len = 0) const { + std::stringstream out; + out << std::setw(len) << std::left << get_name() << discription; + return out.str(); + } + }; + +template +typename std::enable_if::value, bool>::type +lexical_cast(std::string input, T& output) { + logit("Int lexical cast " + input); + try{ + output = (T) std::stoll(input); + return true; + } catch (std::invalid_argument) { + return false; + } catch (std::out_of_range) { + return false; + } +} + +template +typename std::enable_if::value, bool>::type +lexical_cast(std::string input, T& output) { + logit("Floating lexical cast " + input); + try{ + output = (T) std::stold(input); + return true; + } catch (std::invalid_argument) { + return false; + } catch (std::out_of_range) { + return false; + } +} + +// String and similar +template +bool lexical_cast(std::string input, T& output) { + logit("Direct lexical cast: " + input); + output = input; + return true; +} + +enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; + + /// Creates a command line program, with very few defaults. /** To use, create a new Program() instance with argc, argv, and a help discription. The templated * add_option methods make it easy to prepare options. Remember to call `.start` before starting your @@ -210,19 +307,31 @@ public: protected: - std::string desc; + std::string name; + std::string discription; std::vector