diff --git a/examples/try.cpp b/examples/try.cpp index 93637047..4414b7e0 100644 --- a/examples/try.cpp +++ b/examples/try.cpp @@ -1,4 +1,4 @@ -#include "CLI.hpp" +#include "CLI/CLI.hpp" int main (int argc, char** argv) { diff --git a/examples/try1.cpp b/examples/try1.cpp index 1400179b..c4e5a1b5 100644 --- a/examples/try1.cpp +++ b/examples/try1.cpp @@ -1,4 +1,4 @@ -#include "CLI.hpp" +#include "CLI/CLI.hpp" int main (int argc, char** argv) { diff --git a/include/CLI.hpp b/include/CLI/App.hpp similarity index 55% rename from include/CLI.hpp rename to include/CLI/App.hpp index 3512e821..bdba4907 100644 --- a/include/CLI.hpp +++ b/include/CLI/App.hpp @@ -8,628 +8,26 @@ #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 +// CLI Library includes +#include "CLI/Error.hpp" +#include "CLI/TypeTools.hpp" +#include "CLI/StringTools.hpp" +#include "CLI/Split.hpp" +#include "CLI/Combiner.hpp" +#include "CLI/Option.hpp" +#include "CLI/Value.hpp" 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"; - } - - void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { - name = " " + name; - out << std::setw(wid) << std::left << name; - if(description != "") { - if(name.length()>=wid) - out << std::endl << std::setw(wid) << ""; - out << description << std::endl; - } - } - - struct Combiner { - int num; - 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.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, std::string> - get_names(const std::vector &input) { - - std::vector short_names; - std::vector long_names; - std::string pos_name; - - for(std::string name : input) { - if(name.length() == 0) - continue; - else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { - if(name.length()==2 && 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.length() > 2 && 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); - } else if(name == "-" || name == "--") { - throw BadNameString("Must have a name, not just dashes"); - } else { - if(pos_name.length() > 0) - throw BadNameString("Only one positional name allowed, remove: "+name); - pos_name = name; - - } - } - - return std::tuple,std::vector, std::string> - (short_names, long_names, pos_name); - } - - // 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, {}}; -const detail::Combiner Required {1, true, false, {}}; -const detail::Combiner Default {1, false, true, {}}; -const detail::Combiner Args {-1, false, false, {}}; -const detail::Combiner Validators {1, 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, {detail::_ExistingFile}}; -const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}}; -const detail::Combiner NonexistentPath {1, 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; - std::string pname; - - detail::Combiner opts; - std::string description; - 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 description = "", detail::Combiner opts=Nothing, std::function callback=[](results_t){return true;}) : - opts(opts), description(description), callback(callback){ - std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); - } - - /// Clear the parsed results (mostly for testing) - void clear() { - results.clear(); - } - - /// True if option is required - bool required() const { - return opts.required; - } - - /// The number of arguments the option expects - int expected() const { - return opts.num; - } - - /// True if the argument can be given directly - bool positional() const { - return pname.length() > 0; - } - - /// True if option has at least one non-positional name - bool nonpositional() const { - return (snames.size() + lnames.size()) > 0; - } - - /// True if this should print the default string - bool defaulted() const { - return opts.defaulted; - } - - /// True if option has description - bool has_description() const { - return description.length() > 0; - } - - /// Get the description - const std::string& get_description() const { - return description; - } - - /// The name and any extras needed for positionals - std::string help_positional() const { - std::string out = pname; - if(expected()<1) - out = out + "x" + std::to_string(expected()); - else if(expected()==-1) - out = out + "..."; - out = required() ? out : "["+out+"]"; - return out; - } - - // Just the pname - std::string get_pname() const { - return pname; - } - - /// 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 (not counting positional) - 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; - } - - /// Gets a , sep list of names. Does not include the positional name. - 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); - } - - /// Check a name. Requires "-" or "--" for short / long, supports positional name - bool check_name(std::string name) const { - - if(name.length()>2 && name.substr(0,2) == "--") - return check_lname(name.substr(2)); - else if (name.length()>1 && name.substr(0,1) == "-") - return check_sname(name.substr(1)); - else - return name == pname; - } - - /// Requires "-" to be removed from string - bool check_sname(const std::string& name) const { - return std::find(std::begin(snames), std::end(snames), name) != std::end(snames); - } - - /// Requires "--" to be removed from string - bool check_lname(const std::string& name) const { - return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames); - } - - - /// Puts a result at position r - void add_result(int r, std::string s) { - results.at(r).push_back(s); - } - - /// Starts a new results vector (used for r in add_result) - int get_new() { - results.emplace_back(); - return results.size() - 1; - } - - /// Count the total number of times an option was passed - int count() const { - int out = 0; - for(const std::vector& v : results) - out += v.size(); - return out; - } - - /// Diagnostic representation - std::string string() const { - std::string val = "Option: " + get_name() + "\n" - + " " + description + "\n" - + " ["; - for(const auto& item : results) { - if(&item!=&results[0]) - val+="],["; - val += detail::join(item); - } - val += "]"; - return val; - } - - /// The first half of the help print, name plus default, etc - 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(); - } - - /// Produce a flattened vector of results, vs. a vector of vectors. - 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 @@ -1391,4 +789,6 @@ public: return name; } }; + + } diff --git a/include/CLI/CLI.hpp b/include/CLI/CLI.hpp new file mode 100644 index 00000000..cdfa5222 --- /dev/null +++ b/include/CLI/CLI.hpp @@ -0,0 +1,15 @@ +#pragma once + +// Distributed under the LGPL version 3.0 license. See accompanying +// file LICENSE or https://github.com/henryiii/CLI11 for details. + +// CLI Library includes +#include "CLI/Error.hpp" +#include "CLI/TypeTools.hpp" +#include "CLI/StringTools.hpp" +#include "CLI/Split.hpp" +#include "CLI/Combiner.hpp" +#include "CLI/Option.hpp" +#include "CLI/Value.hpp" +#include "CLI/App.hpp" + diff --git a/include/CLI/Combiner.hpp b/include/CLI/Combiner.hpp new file mode 100644 index 00000000..be0ee68c --- /dev/null +++ b/include/CLI/Combiner.hpp @@ -0,0 +1,99 @@ +#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 + + +// C standard library +// Only needed for existence checking +// Could be swapped for filesystem in C++17 +#include +#include + +namespace CLI { + +namespace detail { + +struct Combiner { + int num; + 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.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; + } +}; + +/// Check for an existing file +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); +} + +/// Check for an existing directory +bool _ExistingDirectory(std::string filename) { + struct stat buffer; + if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) ) + return true; + return false; +} + +/// Check for a non-existing path +bool _NonexistentPath(std::string filename) { + struct stat buffer; + return stat(filename.c_str(), &buffer) != 0; +} + + + + +} + + + +// Defines for common Combiners (don't use combiners directly) + +const detail::Combiner Nothing {0, false, false, {}}; +const detail::Combiner Required {1, true, false, {}}; +const detail::Combiner Default {1, false, true, {}}; +const detail::Combiner Args {-1, false, false, {}}; +const detail::Combiner Validators {1, 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, {detail::_ExistingFile}}; +const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}}; +const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}}; + + +} diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp new file mode 100644 index 00000000..de74d14f --- /dev/null +++ b/include/CLI/Error.hpp @@ -0,0 +1,64 @@ +#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 + +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) {} +}; + +} diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp new file mode 100644 index 00000000..74511c38 --- /dev/null +++ b/include/CLI/Option.hpp @@ -0,0 +1,224 @@ +#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 "CLI/StringTools.hpp" +#include "CLI/Split.hpp" +#include "CLI/Combiner.hpp" + +namespace CLI { + +typedef std::vector> results_t; +typedef std::function callback_t; + + +class App; + +class Option { + friend App; +protected: + // Config + std::vector snames; + std::vector lnames; + std::string pname; + + detail::Combiner opts; + std::string description; + 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 description = "", detail::Combiner opts=Nothing, std::function callback=[](results_t){return true;}) : + opts(opts), description(description), callback(callback){ + std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); + } + + /// Clear the parsed results (mostly for testing) + void clear() { + results.clear(); + } + + /// True if option is required + bool required() const { + return opts.required; + } + + /// The number of arguments the option expects + int expected() const { + return opts.num; + } + + /// True if the argument can be given directly + bool positional() const { + return pname.length() > 0; + } + + /// True if option has at least one non-positional name + bool nonpositional() const { + return (snames.size() + lnames.size()) > 0; + } + + /// True if this should print the default string + bool defaulted() const { + return opts.defaulted; + } + + /// True if option has description + bool has_description() const { + return description.length() > 0; + } + + /// Get the description + const std::string& get_description() const { + return description; + } + + /// The name and any extras needed for positionals + std::string help_positional() const { + std::string out = pname; + if(expected()<1) + out = out + "x" + std::to_string(expected()); + else if(expected()==-1) + out = out + "..."; + out = required() ? out : "["+out+"]"; + return out; + } + + // Just the pname + std::string get_pname() const { + return pname; + } + + /// 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 (not counting positional) + 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; + } + + /// Gets a , sep list of names. Does not include the positional name. + 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); + } + + /// Check a name. Requires "-" or "--" for short / long, supports positional name + bool check_name(std::string name) const { + + if(name.length()>2 && name.substr(0,2) == "--") + return check_lname(name.substr(2)); + else if (name.length()>1 && name.substr(0,1) == "-") + return check_sname(name.substr(1)); + else + return name == pname; + } + + /// Requires "-" to be removed from string + bool check_sname(const std::string& name) const { + return std::find(std::begin(snames), std::end(snames), name) != std::end(snames); + } + + /// Requires "--" to be removed from string + bool check_lname(const std::string& name) const { + return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames); + } + + + /// Puts a result at position r + void add_result(int r, std::string s) { + results.at(r).push_back(s); + } + + /// Starts a new results vector (used for r in add_result) + int get_new() { + results.emplace_back(); + return results.size() - 1; + } + + /// Count the total number of times an option was passed + int count() const { + int out = 0; + for(const std::vector& v : results) + out += v.size(); + return out; + } + + /// Diagnostic representation + std::string string() const { + std::string val = "Option: " + get_name() + "\n" + + " " + description + "\n" + + " ["; + for(const auto& item : results) { + if(&item!=&results[0]) + val+="],["; + val += detail::join(item); + } + val += "]"; + return val; + } + + /// The first half of the help print, name plus default, etc + 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(); + } + + /// Produce a flattened vector of results, vs. a vector of vectors. + 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; + } + +}; + + + +} diff --git a/include/CLI/Split.hpp b/include/CLI/Split.hpp new file mode 100644 index 00000000..b390eb9e --- /dev/null +++ b/include/CLI/Split.hpp @@ -0,0 +1,93 @@ +#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 "CLI/Error.hpp" +#include "CLI/StringTools.hpp" + +namespace CLI { +namespace detail { + +// 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; + +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple,std::vector, std::string> + get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length()==2 && 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.length() > 2 && 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); + } else if(name == "-" || name == "--") { + throw BadNameString("Must have a name, not just dashes"); + } else { + if(pos_name.length() > 0) + throw BadNameString("Only one positional name allowed, remove: "+name); + pos_name = name; + + } + } + + return std::tuple,std::vector, std::string> + (short_names, long_names, pos_name); +} + + +} +} diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp new file mode 100644 index 00000000..6bf7c3ea --- /dev/null +++ b/include/CLI/StringTools.hpp @@ -0,0 +1,65 @@ +#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 + +namespace CLI { +namespace detail { + + +/// 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(); +} + +/// Print a two part "help" string +void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(wid) << std::left << name; + if(description != "") { + if(name.length()>=wid) + out << std::endl << std::setw(wid) << ""; + out << description << std::endl; + } +} + +/// Verify the first character of an option +template +bool valid_first_char(T c) { + return std::isalpha(c) || c=='_'; +} + +/// Verify following characters of an option +template +bool valid_later_char(T c) { + return std::isalnum(c) || c=='_' || c=='.' || c=='-'; +} + +/// Verify an option name +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; +} + + + +} +} diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp new file mode 100644 index 00000000..55707e9d --- /dev/null +++ b/include/CLI/TypeTools.hpp @@ -0,0 +1,149 @@ +#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 + +namespace CLI { + +// 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 +// If your compiler supports C++14, you can use that definition instead +using std::enable_if_t; +#endif + +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 = {}; + + + // Type name print + + /// 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"; + } + + + + // Lexical cast + + + /// 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; + } + + +} +} diff --git a/include/CLI/Value.hpp b/include/CLI/Value.hpp new file mode 100644 index 00000000..019ea781 --- /dev/null +++ b/include/CLI/Value.hpp @@ -0,0 +1,41 @@ +#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 "CLI/Error.hpp" + +namespace CLI { + +class App; + +// 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(); + } +}; + +} diff --git a/scripts/MakeSingleHeader.py b/scripts/MakeSingleHeader.py new file mode 100755 index 00000000..70965492 --- /dev/null +++ b/scripts/MakeSingleHeader.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Requires Python 3.6 + +from plumbum import local, cli, FG +import re + +includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) +includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) + +DIR = local.path(__file__).dirname +BDIR = DIR / '../include' + +class MakeHeader(cli.Application): + + def main(self, out : cli.NonexistentPath = BDIR / 'CLI11.hpp'): + main_header = BDIR / 'CLI/CLI.hpp' + header = main_header.read() + + include_files = includes_local.findall(header) + + headers = set() + output = '' + with open('output.hpp', 'w') as f: + for inc in include_files: + inner = (BDIR / inc).read() + headers |= set(includes_system.findall(inner)) + output += f'\n// From {inc}\n\n' + output += inner[inner.find('namespace'):] + + header_list = '\n'.join(f'#include <{h}>' for h in headers) + + output = f'''\ +#pragma once + +// Distributed under the LGPL version 3.0 license. See accompanying +// file LICENSE or https://github.com/henryiii/CLI11 for details. + +// This file was generated using MakeSingleHeader.py in CLI11/scripts +// This has the complete CLI library in one file. + +{header_list} +{output}''' + + with out.open('w') as f: + f.write(output) + + print(f"Created {out}") + +if __name__ == '__main__': + MakeHeader() + + diff --git a/tests/CLITest.cpp b/tests/CLITest.cpp index 23bfaea4..d2d7e766 100644 --- a/tests/CLITest.cpp +++ b/tests/CLITest.cpp @@ -1,5 +1,5 @@ -#include "CLI.hpp" +#include "CLI/CLI.hpp" #include "gtest/gtest.h" #include diff --git a/tests/SmallTest.cpp b/tests/SmallTest.cpp index 08a4f87c..a43991fc 100644 --- a/tests/SmallTest.cpp +++ b/tests/SmallTest.cpp @@ -1,4 +1,4 @@ -#include "CLI.hpp" +#include "CLI/CLI.hpp" #include "gtest/gtest.h" #include #include