#pragma once #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 << output << 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 = 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 BadNameString : public std::runtime_error { public: BadNameString(std::string name) : runtime_error("Failed to parse: " + name) {}; }; class CallForHelp : public std::runtime_error { public: CallForHelp() : runtime_error("Help option passed") {}; }; class ParseError : public std::runtime_error { public: ParseError(std::string info="") : runtime_error(info) {}; }; class OptionAlreadyAdded : public std::runtime_error { public: OptionAlreadyAdded(std::string name) : runtime_error("Already added:" + 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::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 {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}; 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;}) throw (BadNameString) : opts(opts), discription(discription), callback(callback){ std::tie(sname, lname) = split(name); } /// 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 getName() const { if(sname=="") return "--" + lname; else if (lname=="") return "-" + sname; else return "-" + 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; } int get_num() const { return opts.num; } void add_result(int r, std::string s) { results.at(r).push_back(s); } int get_new() { results.emplace_back(); return results.size() - 1; } int count() { return results.size(); } std::string string() const { std::string val = "Option: " + getName() + "\n" + " " + discription + "\n" + " ["; for(const auto& item : results) { if(&item!=&results[0]) val+="],["; val += join(item); } val += "]"; return val; } }; /// 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 desc; std::vector