From 494a65f8120145d5e923c8c62668d9e4d1f414d3 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 6 Feb 2017 20:52:12 -0500 Subject: [PATCH] Dropping Make syntax, moving to pointers from combiners, structured errors. --- CMakeLists.txt | 5 +++-- examples/try.cpp | 2 +- include/CLI/App.hpp | 289 ++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- include/CLI/CLI.hpp | 3 +-- include/CLI/Combiner.hpp | 99 --------------------------------------------------------------------------------------------------- include/CLI/Error.hpp | 58 +++++++++++++++++++++++++++++++++++++++------------------- include/CLI/Option.hpp | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- include/CLI/Validators.hpp | 42 ++++++++++++++++++++++++++++++++++++++++++ include/CLI/Value.hpp | 41 ----------------------------------------- include/Program.hpp | 140 -------------------------------------------------------------------------------------------------------------------------------------------- tests/CLITest.cpp | 147 +++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------- tests/SmallTest.cpp | 12 ++++++------ 12 files changed, 220 insertions(+), 703 deletions(-) delete mode 100644 include/CLI/Combiner.hpp create mode 100644 include/CLI/Validators.hpp delete mode 100644 include/CLI/Value.hpp delete mode 100644 include/Program.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e7afcf2..ef806a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,14 +20,15 @@ add_compile_options(-pedantic -Wall -Wextra) add_library(CLI INTERFACE) target_include_directories(CLI INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") +file(GLOB CLI_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") + # Single file test option(CLI_SINGLE_FILE "Generate a single header file (and test)" ${CUR_PROJ}) if(CLI_SINGLE_FILE) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" COMMAND python "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" - IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI_headers} ) add_custom_target(generate_cli_single_file DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") diff --git a/examples/try.cpp b/examples/try.cpp index 4414b7e..7c932de 100644 --- a/examples/try.cpp +++ b/examples/try.cpp @@ -12,7 +12,7 @@ int main (int argc, char** argv) { app.add_flag("-c,--count", count, "Counter"); double value = 3.14; - app.add_option("-d,--double", value, "Some Value", CLI::Default); + app.add_option("-d,--double", value, "Some Value", false); try { app.run(argc, argv); diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index bdba490..7f18768 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -20,9 +20,7 @@ #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 { @@ -92,8 +90,6 @@ public: } - //------------ ADD STYLE ---------// - /// Add an option, will automatically understand the type for common types. /** To use, create a variable with the expected type, and pass it in after the name. * After start is called, you can use count to see if the value was passed, and @@ -111,9 +107,9 @@ public: std::string name, callback_t callback, std::string description="", - detail::Combiner opts=Validators + bool defaulted=true ) { - Option myopt{name, description, opts, callback}; + Option myopt{name, description, callback, defaulted}; if(std::find(std::begin(options), std::end(options), myopt) == std::end(options)) options.push_back(myopt); else @@ -128,12 +124,10 @@ public: std::string name, T &variable, ///< The variable to set std::string description="", - detail::Combiner opts=Validators + bool defaulted=true ) { - if(opts.num!=1) - throw IncorrectConstruction("Must have Args(1) or be a vector."); CLI::callback_t fun = [&variable](CLI::results_t res){ if(res.size()!=1) { return false; @@ -144,9 +138,9 @@ public: return detail::lexical_cast(res[0][0], variable); }; - Option* retval = add_option(name, fun, description, opts); + Option* retval = add_option(name, fun, description, defaulted); retval->typeval = detail::type_name(); - if(opts.defaulted) { + if(defaulted) { std::stringstream out; out << variable; retval->defaultval = out.str(); @@ -160,11 +154,9 @@ public: std::string name, std::vector &variable, ///< The variable vector to set std::string description="", - detail::Combiner opts=Args + bool defaulted=true ) { - if(opts.num==0) - throw IncorrectConstruction("Must have Args or be a vector."); CLI::callback_t fun = [&variable](CLI::results_t res){ bool retval = true; variable.clear(); @@ -176,27 +168,16 @@ public: return variable.size() > 0 && retval; }; - Option* retval = add_option(name, fun, description, opts); + Option* retval = add_option(name, fun, description, defaulted); + retval->allow_vector = true; + retval->_expected = -1; retval->typeval = detail::type_name(); - if(opts.defaulted) { + if(defaulted) retval->defaultval = "[" + detail::join(variable) + "]"; - } return retval; } - /// Multiple options are supported - template - Option* add_option( - std::string name, - T &variable, ///< The variable to set - std::string description, - detail::Combiner opts, - detail::Combiner opts2, - Args... args ///< More options - ) { - return add_option(name, variable, description, opts|opts2, args...); - } /// Add option for flag Option* add_flag( std::string name, @@ -206,9 +187,10 @@ public: return true; }; - Option* opt = add_option(name, fun, description, Nothing); - if(opt->positional()) + Option* opt = add_option(name, fun, description, false); + if(opt->get_positional()) throw IncorrectConstruction("Flags cannot be positional"); + opt->_expected = 0; return opt; } @@ -227,9 +209,10 @@ public: return true; }; - Option* opt = add_option(name, fun, description, Nothing); - if(opt->positional()) + Option* opt = add_option(name, fun, description, false); + if(opt->get_positional()) throw IncorrectConstruction("Flags cannot be positional"); + opt->_expected = 0; return opt; } @@ -248,9 +231,10 @@ public: return res.size() == 1; }; - Option* opt = add_option(name, fun, description, Nothing); - if(opt->positional()) + Option* opt = add_option(name, fun, description, false); + if(opt->get_positional()) throw IncorrectConstruction("Flags cannot be positional"); + opt->_expected = 0; return opt; } @@ -262,12 +246,9 @@ public: T &member, ///< The selected member of the set std::set options, ///< The set of posibilities std::string description="", - detail::Combiner opts=Validators + bool defaulted=true ) { - if(opts.num!=1) - throw IncorrectConstruction("Must have Args(1)."); - CLI::callback_t fun = [&member, options](CLI::results_t res){ if(res.size()!=1) { return false; @@ -281,223 +262,16 @@ public: return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option* retval = add_option(name, fun, description, opts); + Option* retval = add_option(name, fun, description, defaulted); retval->typeval = detail::type_name(); retval->typeval += " in {" + detail::join(options) + "}"; - if(opts.defaulted) { - std::stringstream out; - out << member; - retval->defaultval = out.str(); - } + std::stringstream out; + out << member; + retval->defaultval = out.str(); return retval; } - template - Option* add_set( - std::string name, - T &member, - std::set options, ///< The set of posibilities - std::string description, - detail::Combiner opts, - detail::Combiner opts2, - Args... args - ) { - return add_set(name, member, options, description, opts|opts2, args...); - } - - - //------------ MAKE STYLE ---------// - - /// Prototype for new output style - template::value, detail::enabler> = detail::dummy> - Value make_option( - std::string name, - std::string description="", - detail::Combiner opts=Validators - ) { - - if(opts.num!=1) - throw IncorrectConstruction("Must have Args(1)."); - - Value out(name); - std::shared_ptr> ptr = out.value; - - CLI::callback_t fun = [ptr](CLI::results_t res){ - if(res.size()!=1) { - return false; - } - if(res[0].size()!=1) { - return false; - } - ptr->reset(new T()); // resets the internal ptr - return detail::lexical_cast(res[0][0], **ptr); - }; - Option* retval = add_option(name, fun, description, opts); - retval->typeval = detail::type_name(); - return out; - } - - template - Value make_option( - std::string name, - std::string description, - detail::Combiner opts, - detail::Combiner opts2, - Args... args - ) { - return make_option(name, description, opts|opts2, args...); - } - - /// Prototype for new output style with default - template::value, detail::enabler> = detail::dummy> - Value make_option( - std::string name, - const T& default_value, - std::string description="", - detail::Combiner opts=Validators - ) { - - if(opts.num!=1) - throw IncorrectConstruction("Must have Args(1)."); - - Value out(name); - std::shared_ptr> ptr = out.value; - ptr->reset(new T(default_value)); // resets the internal ptr - - CLI::callback_t fun = [ptr](CLI::results_t res){ - if(res.size()!=1) { - return false; - } - if(res[0].size()!=1) { - return false; - } - ptr->reset(new T()); // resets the internal ptr - return detail::lexical_cast(res[0][0], **ptr); - }; - Option* retval = add_option(name, fun, description, opts); - retval->typeval = detail::type_name(); - std::stringstream ot; - ot << default_value; - retval->defaultval = ot.str(); - return out; - } - - /// Prototype for new output style, vector - template::value, detail::enabler> = detail::dummy> - Value make_option( - std::string name, - std::string description="", - detail::Combiner opts=Args - ) { - - if(opts.num==0) - throw IncorrectConstruction("Must have Args or be a vector."); - - Value out(name); - std::shared_ptr> ptr = out.value; - - CLI::callback_t fun = [ptr](CLI::results_t res){ - ptr->reset(new T()); // resets the internal ptr - bool retval = true; - for(const auto &a : res) - for(const auto &b : a) { - (*ptr)->emplace_back(); - retval &= detail::lexical_cast(b, (*ptr)->back()); - } - return (*ptr)->size() > 0 && retval; - }; - Option* retval = add_option(name, fun, description, opts); - retval->typeval = detail::type_name(); - return out; - } - - - template - Value make_option( - std::string name, - const T& default_value, - std::string description, - detail::Combiner opts, - detail::Combiner opts2, - Args... args - ) { - return make_option(name, default_value, description, opts|opts2, args...); - } - - /// Prototype for new output style: flag - Value make_flag( - std::string name, - std::string description="" - ) { - - Value out(name); - std::shared_ptr> ptr = out.value; - ptr->reset(new int()); // resets the internal ptr - **ptr = 0; - - CLI::callback_t fun = [ptr](CLI::results_t res){ - **ptr = (int) res.size(); - return true; - }; - - Option* opt = add_option(name, fun, description, Nothing); - if(opt->positional()) - throw IncorrectConstruction("Flags cannot be positional"); - return out; - } - - /// Add set of options - template - Value make_set( - std::string name, - std::set options, ///< The set of posibilities - std::string description="", - detail::Combiner opts=Validators - ) { - - Value out(name); - std::shared_ptr> ptr = out.value; - - if(opts.num!=1) - throw IncorrectConstruction("Must have Args(1)."); - - CLI::callback_t fun = [ptr, options](CLI::results_t res){ - if(res.size()!=1) { - return false; - } - if(res[0].size()!=1) { - return false; - } - ptr->reset(new T()); - bool retval = detail::lexical_cast(res[0][0], **ptr); - if(!retval) - return false; - return std::find(std::begin(options), std::end(options), **ptr) != std::end(options); - }; - - Option* retval = add_option(name, fun, description, opts); - retval->typeval = detail::type_name(); - retval->typeval += " in {" + detail::join(options) + "}"; - return out; - } - - - template - Value make_set( - std::string name, - std::set options, - std::string description, - detail::Combiner opts, - detail::Combiner opts2, - Args... args - ) { - return make_set(name, options, description, opts|opts2, args...); - } - /// This allows subclasses to inject code before callbacks but after parse virtual void pre_callback() {} @@ -510,6 +284,7 @@ public: parse(args); } + /// The real work is done here. Expects a reversed vector void parse(std::vector & args) { parsed = true; @@ -546,16 +321,16 @@ public: for(Option& opt : options) { - while (opt.positional() && opt.count() < opt.expected() && positionals.size() > 0) { + while (opt.get_positional() && opt.count() < opt.get_expected() && positionals.size() > 0) { opt.get_new(); opt.add_result(0, positionals.front()); positionals.pop_front(); } - if (opt.required() && opt.count() < opt.expected()) + if (opt.get_required() && opt.count() < opt.get_expected()) throw RequiredError(opt.get_name()); if (opt.count() > 0) { if(!opt.run_callback()) - throw ParseError(opt.get_name()); + throw ConversionError(opt.get_name()); } } @@ -595,7 +370,7 @@ public: } int vnum = op->get_new(); - int num = op->expected(); + int num = op->get_expected(); if(num == 0) op->add_result(vnum, ""); @@ -660,7 +435,7 @@ public: int vnum = op->get_new(); - int num = op->expected(); + int num = op->get_expected(); if(value != "") { @@ -742,7 +517,7 @@ public: // Positionals bool pos=false; for(const Option &opt : options) - if(opt.positional()) { + if(opt.get_positional()) { out << " " << opt.help_positional(); if(opt.has_description()) pos=true; @@ -754,7 +529,7 @@ public: if(pos) { out << "Positionals:" << std::endl; for(const Option &opt : options) - if(opt.positional() && opt.has_description()) + if(opt.get_positional() && opt.has_description()) detail::format_help(out, opt.get_pname(), opt.get_description(), wid); out << std::endl; diff --git a/include/CLI/CLI.hpp b/include/CLI/CLI.hpp index cdfa522..e95f676 100644 --- a/include/CLI/CLI.hpp +++ b/include/CLI/CLI.hpp @@ -8,8 +8,7 @@ #include "CLI/TypeTools.hpp" #include "CLI/StringTools.hpp" #include "CLI/Split.hpp" -#include "CLI/Combiner.hpp" +#include "CLI/Validators.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 deleted file mode 100644 index be0ee68..0000000 --- a/include/CLI/Combiner.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#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 index de74d14..a8d0edf 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -11,54 +11,74 @@ namespace CLI { // Error definitions - +/// All errors derive from this one 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) {} }; +/// This is a successful completion on parsing, supposed to exit struct Success : public Error { Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {} }; +/// -h or --help on command line 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) {} +// Construction errors (not in parsing) + +struct ConstructionError : public Error { + using Error::Error; }; +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +struct IncorrectConstruction : public ConstructionError { + IncorrectConstruction(std::string name) : ConstructionError("ConstructionError", name, 8) {} +}; -struct ParseError : public Error { - ParseError(std::string name) : Error("ParseError", name, 2) {} +/// Thrown on construction of a bad name +struct BadNameString : public ConstructionError { + BadNameString(std::string name) : ConstructionError("BadNameString", name, 1) {} }; -struct OptionAlreadyAdded : public Error { - OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {} +/// Thrown when an option already exists +struct OptionAlreadyAdded : public ConstructionError { + OptionAlreadyAdded(std::string name) : ConstructionError("OptionAlreadyAdded", name, 3) {} }; -struct OptionNotFound : public Error { - OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} +// Parsing errors + +struct ParseError : public Error { + using Error::Error; }; -struct RequiredError : public Error { - RequiredError(std::string name) : Error("RequiredError", name, 5) {} +/// Thrown when conversion call back fails, such as when an int fails to coerse to a string +struct ConversionError : public ParseError { + ConversionError(std::string name) : ParseError("ConversionError", name, 2) {} }; -struct PositionalError : public Error { - PositionalError(std::string name) : Error("PositionalError", name, 6) {} +/// Thrown when a required option is missing +struct RequiredError : public ParseError { + RequiredError(std::string name) : ParseError("RequiredError", name, 5) {} }; -struct HorribleError : public Error { - HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {} +/// Thrown when too many positionals are found +struct PositionalError : public ParseError { + PositionalError(std::string name) : ParseError("PositionalError", name, 6) {} }; -struct IncorrectConstruction : public Error { - IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {} + +/// This is just a safety check to verify selection and parsing match +struct HorribleError : public ParseError { + HorribleError(std::string name) : ParseError("HorribleError", "(You should never see this error) " + name, 7) {} }; -struct EmptyError : public Error { - EmptyError(std::string name) : Error("EmptyError", name, 9) {} + +/// Thrown when counting a non-existent option +struct OptionNotFound : public Error { + OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} }; + } diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 74511c3..2828216 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -9,16 +9,15 @@ #include #include +#include "CLI/Error.hpp" #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 { @@ -29,7 +28,6 @@ protected: std::vector lnames; std::string pname; - detail::Combiner opts; std::string description; callback_t callback; @@ -37,33 +35,66 @@ protected: std::string defaultval; std::string typeval; + + bool _default {false}; + bool _required {false}; + int _expected {1}; + bool allow_vector {false}; + std::vector> _validators; + // 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){ + Option(std::string name, std::string description = "", std::function callback=[](results_t){return true;}, bool _default=true) : + description(description), callback(callback), _default(_default) { std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); } + + // This class is "true" if optio passed. + operator bool() const { + return results.size() > 0; + } + /// Clear the parsed results (mostly for testing) void clear() { results.clear(); } - /// True if option is required - bool required() const { - return opts.required; + /// Set the option as required + Option* required(bool value = true) { + _required = value; + return this; + } + + bool get_required() const { + return _required; + } + + /// Set the number of expected arguments (Flags bypass this) + Option* expected(int value) { + if(value == 0) + throw IncorrectConstruction("Cannot set 0 expected, use a flag instead"); + if(!allow_vector && value != 1) + throw IncorrectConstruction("You can only change the Expected arguments for vectors"); + _expected = value; + return this; } /// The number of arguments the option expects - int expected() const { - return opts.num; + int get_expected() const { + return _expected; + } + + /// True if this has a default value + int get_default() const { + return _default; } /// True if the argument can be given directly - bool positional() const { + bool get_positional() const { return pname.length() > 0; } @@ -72,16 +103,18 @@ public: 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; } + /// Adds a validator + Option* check(std::function validator) { + + _validators.push_back(validator); + return this; + } + /// Get the description const std::string& get_description() const { return description; @@ -90,11 +123,11 @@ public: /// 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) + if(get_expected()<1) + out = out + "x" + std::to_string(get_expected()); + else if(get_expected()==-1) out = out + "..."; - out = required() ? out : "["+out+"]"; + out = get_required() ? out : "["+out+"]"; return out; } @@ -105,9 +138,9 @@ public: /// Process the callback bool run_callback() const { - if(opts.validators.size()>0) { + if(_validators.size()>0) { for(const std::string & result : flatten_results()) - for(const std::function &vali : opts.validators) + for(const std::function &vali : _validators) if(!vali(result)) return false; } @@ -196,14 +229,14 @@ public: std::string help_name() const { std::stringstream out; out << get_name(); - if(expected() != 0) { + if(get_expected() != 0) { if(typeval != "") out << " " << typeval; if(defaultval != "") out << "=" << defaultval; - if(expected() > 1) - out << " x " << expected(); - if(expected() == -1) + if(get_expected() > 1) + out << " x " << get_expected(); + if(get_expected() == -1) out << " ..."; } return out.str(); diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp new file mode 100644 index 0000000..2343c08 --- /dev/null +++ b/include/CLI/Validators.hpp @@ -0,0 +1,42 @@ +#pragma once + +// Distributed under the LGPL version 3.0 license. See accompanying +// file LICENSE or https://github.com/henryiii/CLI11 for details. + +#include + + +// C standard library +// Only needed for existence checking +// Could be swapped for filesystem in C++17 +#include +#include + +namespace CLI { + + +/// 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; +} + + +} diff --git a/include/CLI/Value.hpp b/include/CLI/Value.hpp deleted file mode 100644 index 019ea78..0000000 --- a/include/CLI/Value.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#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/include/Program.hpp b/include/Program.hpp deleted file mode 100644 index b2d7c09..0000000 --- a/include/Program.hpp +++ /dev/null @@ -1,140 +0,0 @@ -#pragma once - -#include - -#include - - -// This is unreachable outside this file; you should not use Combiner directly -namespace { - -struct Combiner { - int positional; - bool required; - bool defaulted; - - /// Can be or-ed together - Combiner operator | (Combiner b) const { - Combiner self; - 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, required, defaulted}; - } - Combiner operator, (Combiner b) const { - return *this | b; - } -}; -} - - - -/// 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 Program { -public: - static constexpr Combiner REQUIRED{0,true,false}; - static constexpr Combiner DEFAULT{0,false,true}; - static constexpr Combiner POSITIONAL{1,false,false}; - -protected: - boost::program_options::options_description desc; - boost::program_options::positional_options_description p; - boost::program_options::variables_map vm; - - int argc; - char **argv; - - /// Parses the command line (internal function) - void parse() { - try { - boost::program_options::store(boost::program_options::command_line_parser(argc, argv) - .options(desc).positional(p).run(), vm); - - if(vm.count("help")){ - std::cout << desc; - exit(0); - } - - boost::program_options::notify(vm); - } catch(const boost::program_options::error& e) { - std::cerr << "ERROR: " << e.what() << std::endl << std::endl; - std::cerr << desc << std::endl; - exit(1); - } - } - - -public: - - /// Create a new program. Pass in the same arguments as main(), along with a help string. - Program(int argc, char** argv, std::string discription) - : argc(argc), argv(argv), desc(discription) { - desc.add_options() - ("help,h", "Display this help message"); - } - - /// Allows you to manually add options in the boost style. - /** Usually the specialized methods are easier, but this remains for people used to Boost and for - * unusual situations. */ - boost::program_options::options_description_easy_init add_options() { - return desc.add_options(); - } - - /// Add an option, will automatically understand the type for common types. - /** To use, create a variable with the expected type, and pass it in after the name. - * After start is called, you can use count to see if the value was passed, and - * the value will be initialized properly. - * - * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|` - * together. The positional options take an optional number of arguments. - * - * For example, - * - * std::string filename - * program.add_option("filename", filename, "description of filename"); - */ - template - void add_option( - std::string name, ///< The name, long,short - T &value, ///< The value - std::string description, ///< Discription string - Combiner options ///< The options (REQUIRED, DEFAULT, POSITIONAL) - ) { - auto po_value = boost::program_options::value(&value); - if(options.defaulted) - po_value = po_value->default_value(value); - if(options.required) - po_value = po_value->required(); - desc.add_options()(name.c_str(),po_value,description.c_str()); - if(options.positional!=0) - p.add(name.c_str(), options.positional); - } - - /// Adds a flag style option - void add_option(std::string name, std::string description) { - desc.add_options()(name.c_str(),description.c_str()); - } - - - /// This must be called after the options are in but before the rest of the program. - /** Calls the Boost boost::program_options initialization, causing the program to exit - * if -h or an invalid option is passed. */ - void start() { - parse(); - } - - /// Counts the number of times the given option was passed. - int count(std::string name) const { - return vm.count(name.c_str()); - } - - -}; diff --git a/tests/CLITest.cpp b/tests/CLITest.cpp index 7516967..a6ae4ac 100644 --- a/tests/CLITest.cpp +++ b/tests/CLITest.cpp @@ -165,7 +165,7 @@ TEST_F(TApp, BoolAndIntFlags) { app.reset(); args = {"-b", "-b"}; - EXPECT_THROW(run(), CLI::ParseError); + EXPECT_THROW(run(), CLI::ConversionError); app.reset(); bflag = false; @@ -199,8 +199,8 @@ TEST_F(TApp, Flags) { int i = 3; std::string s = "HI"; - app.add_option("-i,i", i, "", CLI::Default); - app.add_option("-s,s", s, "", CLI::Default); + app.add_option("-i,i", i, "", false); + app.add_option("-s,s", s, "", true); args = {"-i2", "9"}; @@ -276,10 +276,10 @@ TEST_F(TApp, Reset) { TEST_F(TApp, FileNotExists) { std::string myfile{"TestNonFileNotUsed.txt"}; - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); + EXPECT_TRUE(CLI::NonexistentPath(myfile)); std::string filename; - app.add_option("--file", filename, "", CLI::NonexistentPath); + app.add_option("--file", filename)->check(CLI::NonexistentPath); args = {"--file", myfile}; EXPECT_NO_THROW(run()); @@ -290,21 +290,21 @@ TEST_F(TApp, FileNotExists) { bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); - EXPECT_THROW(run(), CLI::ParseError); + EXPECT_THROW(run(), CLI::ConversionError); std::remove(myfile.c_str()); - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); + EXPECT_FALSE(CLI::ExistingFile(myfile)); } TEST_F(TApp, FileExists) { std::string myfile{"TestNonFileNotUsed.txt"}; - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); + EXPECT_FALSE(CLI::ExistingFile(myfile)); std::string filename = "Failed"; - app.add_option("--file", filename, "", CLI::ExistingFile); + app.add_option("--file", filename)->check(CLI::ExistingFile); args = {"--file", myfile}; - EXPECT_THROW(run(), CLI::ParseError); + EXPECT_THROW(run(), CLI::ConversionError); EXPECT_EQ("Failed", filename); app.reset(); @@ -315,7 +315,7 @@ TEST_F(TApp, FileExists) { EXPECT_EQ(myfile, filename); std::remove(myfile.c_str()); - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); + EXPECT_FALSE(CLI::ExistingFile(myfile)); } TEST_F(TApp, InSet) { @@ -331,15 +331,15 @@ TEST_F(TApp, InSet) { app.reset(); args = {"--quick", "four"}; - EXPECT_THROW(run(), CLI::ParseError); + EXPECT_THROW(run(), CLI::ConversionError); } TEST_F(TApp, VectorFixedString) { std::vector strvec; std::vector answer{"mystring", "mystring2", "mystring3"}; - CLI::Option* opt = app.add_option("-s,--string", strvec, "", CLI::Args(3)); - EXPECT_EQ(3, opt->expected()); + CLI::Option* opt = app.add_option("-s,--string", strvec)->expected(3); + EXPECT_EQ(3, opt->get_expected()); args = {"--string", "mystring", "mystring2", "mystring3"}; run(); @@ -354,7 +354,7 @@ TEST_F(TApp, VectorUnlimString) { std::vector answer{"mystring", "mystring2", "mystring3"}; CLI::Option* opt = app.add_option("-s,--string", strvec); - EXPECT_EQ(-1, opt->expected()); + EXPECT_EQ(-1, opt->get_expected()); args = {"--string", "mystring", "mystring2", "mystring3"}; EXPECT_NO_THROW(run()); @@ -363,6 +363,27 @@ TEST_F(TApp, VectorUnlimString) { } +TEST_F(TApp, VectorFancyOpts) { + std::vector strvec; + std::vector answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option* opt = app.add_option("-s,--string", strvec)->required()->expected(3); + EXPECT_EQ(3, opt->get_expected()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + EXPECT_NO_THROW(run()); + EXPECT_EQ(3, app.count("--string")); + EXPECT_EQ(answer, strvec); + + app.reset(); + args = {"one", "two"}; + EXPECT_THROW(run(), CLI::RequiredError); + + app.reset(); + EXPECT_THROW(run(), CLI::ConversionError); +} + + TEST_F(TApp, BasicSubcommands) { auto sub1 = app.add_subcommand("sub1"); @@ -450,99 +471,5 @@ TEST_F(SubcommandProgram, SpareSub) { EXPECT_THROW(run(), CLI::PositionalError); } -class TAppValue : public TApp {}; - -TEST_F(TAppValue, OneString) { - auto str = app.make_option("-s,--string"); - std::string v; - args = {"--string", "mystring"}; - EXPECT_FALSE((bool) str); - EXPECT_THROW(v = *str, CLI::EmptyError); - //EXPECT_THROW(v = str, CLI::EmptyError); - EXPECT_FALSE((bool) str); - EXPECT_NO_THROW(run()); - EXPECT_TRUE((bool) str); - EXPECT_NO_THROW(v = *str); - EXPECT_NO_THROW(v = str); - - EXPECT_EQ(1, app.count("-s")); - EXPECT_EQ(1, app.count("--string")); - EXPECT_EQ(*str, "mystring"); - -} - -TEST_F(TAppValue, SeveralInts) { - auto value = app.make_option("--first"); - CLI::Value value2 = app.make_option("-s"); - int v; - args = {"--first", "12", "-s", "19"}; - EXPECT_FALSE((bool) value); - EXPECT_FALSE((bool) value2); - - EXPECT_THROW(v = *value, CLI::EmptyError); - //EXPECT_THROW(v = str, CLI::EmptyError); - EXPECT_NO_THROW(run()); - EXPECT_TRUE((bool) value); - EXPECT_NO_THROW(v = *value); - EXPECT_NO_THROW(v = value); - - EXPECT_EQ(1, app.count("-s")); - EXPECT_EQ(1, app.count("--first")); - EXPECT_EQ(*value, 12); - EXPECT_EQ(*value2, 19); - -} - -TEST_F(TAppValue, Vector) { - auto value = app.make_option>("--first", "", CLI::Args); - auto value2 = app.make_option>("--second"); - - std::vector i; - std::vector s; - - args = {"--first", "12", "3", "9", "--second", "thing", "try"}; - - EXPECT_FALSE((bool) value); - EXPECT_FALSE((bool) value2); - - EXPECT_THROW(i = *value, CLI::EmptyError); - EXPECT_THROW(s = *value2, CLI::EmptyError); - - EXPECT_NO_THROW(run()); - - EXPECT_TRUE((bool) value); - EXPECT_TRUE((bool) value2); - - EXPECT_NO_THROW(i = *value); - //EXPECT_NO_THROW(i = value); - - EXPECT_NO_THROW(s = *value2); - //EXPECT_NO_THROW(s = value2); - - EXPECT_EQ(3, app.count("--first")); - EXPECT_EQ(2, app.count("--second")); - - EXPECT_EQ(std::vector({12,3,9}), *value); - EXPECT_EQ(std::vector({"thing", "try"}), *value2); - -} - -TEST_F(TAppValue, DoubleVector) { - auto value = app.make_option>("--simple"); - std::vector d; - - args = {"--simple", "1.2", "3.4", "-1"}; - - EXPECT_THROW(d = *value, CLI::EmptyError); - - EXPECT_NO_THROW(run()); - - EXPECT_NO_THROW(d = *value); - - EXPECT_EQ(3, app.count("--simple")); - EXPECT_EQ(std::vector({1.2, 3.4, -1}), *value); -} -// TODO: Check help output, better formatting -// TODO: Add default/type info to help -// TODO: Add README +// TODO: Check help output and formatting diff --git a/tests/SmallTest.cpp b/tests/SmallTest.cpp index a5a2dd1..941e08d 100644 --- a/tests/SmallTest.cpp +++ b/tests/SmallTest.cpp @@ -11,24 +11,24 @@ TEST(Validators, FileExists) { std::string myfile{"TestFileNotUsed.txt"}; - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); + EXPECT_FALSE(CLI::ExistingFile(myfile)); bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); - EXPECT_TRUE(CLI::detail::_ExistingFile(myfile)); + EXPECT_TRUE(CLI::ExistingFile(myfile)); std::remove(myfile.c_str()); - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); + EXPECT_FALSE(CLI::ExistingFile(myfile)); } TEST(Validators, FileNotExists) { std::string myfile{"TestFileNotUsed.txt"}; - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); + EXPECT_TRUE(CLI::NonexistentPath(myfile)); bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); - EXPECT_FALSE(CLI::detail::_NonexistentPath(myfile)); + EXPECT_FALSE(CLI::NonexistentPath(myfile)); std::remove(myfile.c_str()); - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); + EXPECT_TRUE(CLI::NonexistentPath(myfile)); } TEST(Split, StringList) { -- libgit2 0.21.4