#pragma once // Distributed under the LGPL version 3.0 license. See accompanying // file LICENSE or https://github.com/henryiii/CLI11 for details. #include #include #include #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 namespace CLI { // Error definitions struct Error : public std::runtime_error { int exit_code; bool print_help; Error(std::string parent, std::string name, int exit_code=255, bool print_help=true) : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {} }; struct Success : public Error { Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {} }; struct CallForHelp : public Error { CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 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) {} }; struct EmptyError : public Error { EmptyError(std::string name) : Error("EmptyError", name, 9) {} }; // Type tools // // 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 template struct is_vector { static const bool value = false; }; template struct is_vector > { static bool const value = true; }; template struct is_bool { static const bool value = false; }; template<> struct is_bool { static bool const value = true; }; namespace detail { // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; /// Simple function to join a string template std::string join(const T& v, std::string delim = ",") { std::ostringstream s; size_t start = 0; for (const auto& i : v) { if(start++ > 0) s << delim; s << i; } return s.str(); } /// Was going to be based on /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template /// But this is cleaner and works better in this case template::value && std::is_signed::value, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "INT"; } template::value && std::is_unsigned::value, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "UINT"; } template::value, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "FLOAT"; } /// This one should not be used, since vector types print the internal type template::value, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "VECTOR"; } template::value && !std::is_integral::value && !is_vector::value , detail::enabler> = detail::dummy> constexpr const char* type_name() { return "STRING"; } 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 self; } /// Call to give a validator Combiner operator() (std::function func) const { Combiner self = *this; self.validators.push_back(func); return self; } }; 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; } template bool valid_first_char(T c) { return std::isalpha(c) || c=='_'; } template bool valid_later_char(T c) { return std::isalnum(c) || c=='_' || c=='.' || c=='-'; } inline bool valid_name_string(const std::string &str) { if(str.size()<1 || !valid_first_char(str[0])) return false; for(auto c : str.substr(1)) if(!valid_later_char(c)) return false; return true; } // Returns false if not a short option. Otherwise, sets opt name and rest and returns true inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { if(current.size()>1 && current[0] == '-' && valid_first_char(current[1])) { name = current.substr(1,1); rest = current.substr(2); return true; } else return false; } // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { if(current.size()>2 && current.substr(0,2) == "--" && valid_first_char(current[2])) { auto loc = current.find("="); if(loc != std::string::npos) { name = current.substr(2,loc-2); value = current.substr(loc+1); } else { name = current.substr(2); value = ""; } return true; } else return false; } // Splits a string into multiple long and short names inline std::vector split_names(std::string current) { std::vector output; size_t val; while((val = current.find(",")) != std::string::npos) { output.push_back(current.substr(0,val)); current = current.substr(val+1); } output.push_back(current); return output; } inline std::tuple,std::vector> get_names(const std::vector &input) { std::vector short_names; std::vector long_names; for(std::string name : input) { if(name.length() == 0) continue; else if(name.length() == 1) if(valid_first_char(name[0])) short_names.push_back(name); else throw BadNameString("Invalid one char name: "+name); else if(name.length() == 2 && name[0] == '-' && name[1] != '-') { if(valid_first_char(name[1])) short_names.push_back(std::string(1,name[1])); else throw BadNameString("Invalid one char name: "+name); } else { if(name.substr(0,2) == "--") name = name.substr(2); if(valid_name_string(name)) long_names.push_back(name); else throw BadNameString("Bad long name"+name); } } return std::tuple,std::vector>(short_names, long_names); } // Integers template::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T& output) { try{ output = (T) std::stoll(input); return true; } catch (std::invalid_argument) { return false; } catch (std::out_of_range) { return false; } } // Floats template::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T& output) { try{ output = (T) std::stold(input); return true; } catch (std::invalid_argument) { return false; } catch (std::out_of_range) { return false; } } // Vector template::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T& output) { if(output.size() == input.size()) output.resize(input.size()); for(size_t i=0; i::value && !std::is_integral::value && !is_vector::value , detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T& output) { output = input; return true; } } // Defines for common Combiners (don't use combiners directly) const detail::Combiner NOTHING {0, false,false,false, {}}; const detail::Combiner REQUIRED {1, false,true, false, {}}; const detail::Combiner DEFAULT {1, false,false,true, {}}; const detail::Combiner POSITIONAL {1, true, false,false, {}}; const detail::Combiner ARGS {-1, false,false,false, {}}; const detail::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 detail::Combiner ExistingFile {1, false, false, false, {detail::_ExistingFile}}; const detail::Combiner ExistingDirectory {1, false, false, false, {detail::_ExistingDirectory}}; const detail::Combiner NonexistentPath {1, false, false, false, {detail::_NonexistentPath}}; typedef std::vector> results_t; typedef std::function callback_t; class App; class Option { friend App; protected: // Config std::vector snames; std::vector lnames; detail::Combiner opts; std::string discription; callback_t callback; // These are for help strings std::string defaultval; std::string typeval; // Results results_t results {}; public: Option(std::string name, std::string discription = "", detail::Combiner opts=NOTHING, std::function callback=[](results_t){return true;}) : opts(opts), discription(discription), callback(callback){ std::tie(snames, lnames) = detail::get_names(detail::split_names(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); } /// If options share any of the same names, they are equal bool operator== (const Option& other) const { for(const std::string &sname : snames) for(const std::string &othersname : other.snames) if(sname == othersname) return true; for(const std::string &lname : lnames) for(const std::string &otherlname : other.lnames) if(lname == otherlname) return true; return false; } std::string get_name() const { std::vector name_list; for(const std::string& sname : snames) name_list.push_back("-"+sname); for(const std::string& lname : lnames) name_list.push_back("--"+lname); return detail::join(name_list); } bool check_name(std::string name) const { for(int i=0; i<2; i++) if(name.length()>2 && name[0] == '-') name = name.substr(1); return check_sname(name) || check_lname(name); } bool check_sname(const std::string& name) const { return std::find(std::begin(snames), std::end(snames), name) != std::end(snames); } bool check_lname(const std::string& name) const { return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames); } 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() 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 += detail::join(item); } val += "]"; return val; } std::string help_name() const { std::stringstream out; out << " " << get_name(); if(expected() != 0) { if(typeval != "") out << " " << typeval; if(defaultval != "") out << "=" << defaultval; if(expected() > 1) out << " x " << expected(); if(expected() == -1) out << " ..."; } return out.str(); } int help_len() const { return help_name().length(); } std::string help(int len = 0) const { std::stringstream out; if(help_len() > len) { out << help_name() << "\n"; out << std::setw(len) << " "; } else { out << std::setw(len) << std::left << help_name(); } out << 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; } }; enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; // Prototype return value test template class Value { friend App; protected: std::shared_ptr> value {new std::unique_ptr()}; std::string name; public: Value(std::string name) : name(name) {} operator bool() const {return (bool) *value;} T& get() const { if(*value) return **value; else throw EmptyError(name); } /// Note this does not throw on assignment, though /// afterwards it seems to work fine. Best to use /// explicit * notation. T& operator *() const { return get(); } }; /// Creates a command line program, with very few defaults. /** To use, create a new Program() instance with argc, argv, and a help description. 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