#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include // C standard library // Only needed for existence checking // Could be swapped for filesystem in C++17 #include #include //#define CLI_LOG 1 namespace CLI { /// Log a message, can be enabled with CLI_LOG void logit(std::string) { #ifdef CLI_LOG std::cout << "\033[1;31m" << output << "\033[0m" << std::endl; #endif } /// Simple fucntion to join a string 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(); } // Based generally on https://rmf.io/cxx11/almost-static-if namespace detail { /// Simple empty scoped class enum class enabler {}; } /// An instance to use in EnableIf constexpr detail::enabler dummy = {}; // Copied from C++14 #if __cplusplus < 201402L template< bool B, class T = void > using enable_if_t = typename std::enable_if::type; #else using std::enable_if_t; #endif // If your compiler supports C++14, you can use that definition instead struct Combiner { int num; bool positional; bool required; bool defaulted; std::vector> validators; /// 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; self.validators.reserve(validators.size() + b.validators.size()); self.validators.insert(self.validators.end(), validators.begin(), validators.end()); self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end()); return self; } /// Call to give the number of arguments expected on cli Combiner operator() (int n) const { Combiner self = *this; self.num = n; return *this; } /// Call to give a validator Combiner operator() (std::function func) const { Combiner self = *this; self.validators.push_back(func); return self; } Combiner operator, (Combiner b) const { return *this | b; } }; bool _ExistingFile(std::string filename) { // std::fstream f(name.c_str()); // return f.good(); // Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c struct stat buffer; return (stat(filename.c_str(), &buffer) == 0); } bool _ExistingDirectory(std::string filename) { struct stat buffer; if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) ) return true; return false; } bool _NonexistentPath(std::string filename) { struct stat buffer; return stat(filename.c_str(), &buffer) != 0; } struct Error : public std::runtime_error { int exit_code; Error(std::string parent, std::string name, int exit_code=255) : runtime_error(parent + ": " + name), exit_code(exit_code) {} }; struct CallForHelp : public Error { CallForHelp() : Error("CallForHelp","", 0) {} }; struct BadNameString : public Error { BadNameString(std::string name) : Error("BadNameString", name, 1) {} }; struct ParseError : public Error { ParseError(std::string name) : Error("ParseError", name, 2) {} }; struct OptionAlreadyAdded : public Error { OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {} }; struct OptionNotFound : public Error { OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} }; struct RequiredError : public Error { RequiredError(std::string name) : Error("RequiredError", name, 5) {} }; struct PositionalError : public Error { PositionalError(std::string name) : Error("PositionalError", name, 6) {} }; struct HorribleError : public Error { HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {} }; struct IncorrectConstruction : public Error { IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {} }; 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, {}}; const Combiner VALIDATORS {1, false, false, false, {}}; // Warning about using these validators: // The files could be added/deleted after the validation. This is not common, // but if this is a possibility, check the file you open afterwards const Combiner ExistingFile {1, false, false, false, {_ExistingFile}}; const Combiner ExistingDirectory {1, false, false, false, {_ExistingDirectory}}; const Combiner NonexistentPath {1, false, false, false, {_NonexistentPath}}; 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); } void clear() { results.clear(); } 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 { if(opts.validators.size()>0) { for(const std::string & result : flatten_results()) for(const std::function &vali : opts.validators) if(!vali(result)) return false; } 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(); } std::vector flatten_results() const { std::vector output; for(const std::vector result : results) output.insert(std::end(output), std::begin(result), std::end(result)); return output; } }; template::value, detail::enabler> = dummy> bool 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::value, detail::enabler> = dummy> bool 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::value && !std::is_integral::value, detail::enabler> = dummy> 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}; class App; /// 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 { protected: std::string name; std::string prog_discription; std::vector