#pragma once #include #include #include #include #include #include #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 << "\033[1;31m" << output << "\033[0m" << std::endl; } template std::string join(const T& v, std::string delim = ",") { std::ostringstream s; for (const auto& i : v) { if (&i != &v[0]) { s << delim; } s << i; } return s.str(); } struct Combiner { int num; bool positional; bool required; bool defaulted; /// Can be or-ed together Combiner operator | (Combiner b) const { Combiner self; 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; return self; } /// Call to give the number of arguments expected on cli Combiner operator() (int n) const { return Combiner{n, positional, required, defaulted}; } Combiner operator, (Combiner b) const { return *this | b; } }; } namespace CLI { class Error : public std::runtime_error { public: Error(std::string name) : runtime_error(name) {}; }; class BadNameString : public Error { public: BadNameString(std::string name) : Error("Failed to parse: " + name) {}; }; class CallForHelp : public Error { public: CallForHelp() : Error("Help option passed") {}; }; class ParseError : public Error { public: 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) { std::smatch match; if (std::regex_match(fullname, match, reg_split)) { std::string sname = match[1]; std::string lname = match[2]; if(sname == "" and lname == "") throw BadNameString("EMPTY"); return std::tuple(sname, lname); } else throw BadNameString(fullname); } 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; class Option { public: protected: // Config std::string sname; std::string lname; Combiner opts; std::string discription; callback_t callback; // Results results_t results {}; public: 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); } /// Indistinguishible options are equal bool operator== (const Option& other) const { if(sname=="" && other.sname=="") return lname==other.lname; else if(lname=="" && other.lname=="") return sname==other.sname; else return sname==other.sname || lname==other.lname; } std::string get_name() const { if(sname=="") return "--" + lname; else if (lname=="") return "-" + sname; else 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; } bool check_lname(const std::string& name) const { return name == lname; } std::string get_sname() const { return sname; } std::string get_lname() const { return lname; } 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() const { int out = 0; for(const std::vector& v : results) out += v.size(); return out; } std::string string() const { std::string val = "Option: " + get_name() + "\n" + " " + discription + "\n" + " ["; for(const auto& item : results) { if(&item!=&results[0]) val+="],["; val += join(item); } val += "]"; 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 * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ class App { public: protected: std::string name; std::string discription; std::vector