diff --git a/.appveyor.yml b/.appveyor.yml index 5a8df0c29..9d9394713 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,5 @@ +version: 1.8.0.{build} + branches: only: - master diff --git a/README.md b/README.md index 59f62ab2b..2a96e559b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature - [Contribute](#contribute) - [License](#license) -Features that were added in the last released major version are marked with "πŸ†•". Features only available in master are marked with "πŸ†•". +Features that were added in the last released major version are marked with "πŸ†•". Features only available in master are marked with "🚧". ## Background @@ -194,21 +194,25 @@ While all options internally are the same type, there are several ways to add an app.add_option(option_name, help_str="") // πŸ†• app.add_option(option_name, - variable_to_bind_to, // bool, int, float, vector, πŸ†• enum, or string-like, or anything with a defined conversion from a string + variable_to_bind_to, // bool, int, float, vector, πŸ†• enum, or string-like, or anything with a defined conversion from a string or that takes an int🚧, double🚧, or string in a constructor. help_string="") app.add_option_function(option_name, - function , // πŸ†• int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string + function , // πŸ†• int, bool, float, enum, or string-like, or anything with a defined conversion from a string, or a vector of any of the previous objects. help_string="") app.add_complex(... // Special case: support for complex numbers +//🚧There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string. +app.add_option(option_name, + T &output, // output must be assignable or constructible from a value of type XC + help_string="") // Add flags app.add_flag(option_name, help_string="") app.add_flag(option_name, - variable_to_bind_to, // bool, int, πŸ†• float, πŸ†• vector, πŸ†• enum, or πŸ†• string-like, or πŸ†• anything with a defined conversion from a string + variable_to_bind_to, // bool, int, πŸ†• float, πŸ†• vector, πŸ†• enum, or πŸ†• string-like, or πŸ†• anything with a defined conversion from a string like add_option help_string="") app.add_flag_function(option_name, // πŸ†• @@ -240,6 +244,25 @@ An option name must start with a alphabetic character, underscore, a number πŸ†• The `add_option_function(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. +🚧 The two parameter template overload can be used in cases where you want to restrict the input such as +``` +double val +app.add_option("-v",val); +``` +which would first verify the input is convertible to an int before assigning it. Or using some variant type +``` +using vtype=std::variant; + vtype v1; +app.add_option("--vs",v1); +app.add_option("--vi",v1); +app.add_option("--vf",v1); +``` +otherwise the output would default to a string. The add_option can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type. + +Type such as optional, optional, and optional are supported directly, other optional types can be added using the two parameter template. See [CLI11 Internals][] for information on how this could done and how you can add your own converters for additional types. + +Automatic direct capture of the default string is disabled when using the two parameter template. Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases. + πŸ†• Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example: ```cpp @@ -263,8 +286,6 @@ using any of those flags on the command line will result in the specified number On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. -On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. Optional values are only supported for types that support the `>>` operator. - #### Example - `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` @@ -300,8 +321,8 @@ Before parsing, you can set the following options: - `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. - `->configurable(false)`: Disable this option from being in a configuration file. `->capture_default_str()`: πŸ†• Store the current value attached and display it in the help string. - `->default_function(std::string())`: πŸ†• Advanced: Change the function that `capture_default_str()` uses. - `->always_capture_default()`: πŸ†• Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. +- `->default_function(std::string())`: πŸ†• Advanced: Change the function that `capture_default_str()` uses. +- `->always_capture_default()`: πŸ†• Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector` of results. diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 043de2092..c1e06b345 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -4,13 +4,11 @@ // file LICENSE or https://github.com/CLIUtils/CLI11 for details. #include -#include #include #include #include #include #include -#include #include #include #include @@ -469,18 +467,23 @@ class App { } /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) - template ::value & !std::is_const::value, detail::enabler> = detail::dummy> + + template ::value && !std::is_const::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, T &variable, ///< The variable to set std::string option_description = "", bool defaulted = false) { - auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; + auto fun = [&variable](CLI::results_t res) { // comment for spacing + return detail::lexical_assign(res[0], variable); + }; Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { - return std::string(CLI::detail::to_string(variable)); + return std::string(CLI::detail::checked_to_string(variable)); }); - opt->type_name(detail::type_name()); + opt->type_name(detail::type_name()); return opt; } diff --git a/include/CLI/CLI.hpp b/include/CLI/CLI.hpp index 2feb19247..c2dbe7c5f 100644 --- a/include/CLI/CLI.hpp +++ b/include/CLI/CLI.hpp @@ -10,8 +10,6 @@ #include "CLI/Macros.hpp" -#include "CLI/Optional.hpp" - #include "CLI/StringTools.hpp" #include "CLI/Error.hpp" diff --git a/include/CLI/Optional.hpp b/include/CLI/Optional.hpp deleted file mode 100644 index 510af3b3f..000000000 --- a/include/CLI/Optional.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -// Distributed under the 3-Clause BSD License. See accompanying -// file LICENSE or https://github.com/CLIUtils/CLI11 for details. - -#include - -#include "CLI/Macros.hpp" - -// [CLI11:verbatim] - -// You can explicitly enable or disable support -// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too. -// We nest the check for __has_include and it's usage -#ifndef CLI11_STD_OPTIONAL -#ifdef __has_include -#if defined(CLI11_CPP17) && __has_include() -#define CLI11_STD_OPTIONAL 1 -#else -#define CLI11_STD_OPTIONAL 0 -#endif -#else -#define CLI11_STD_OPTIONAL 0 -#endif -#endif - -#ifndef CLI11_EXPERIMENTAL_OPTIONAL -#define CLI11_EXPERIMENTAL_OPTIONAL 0 -#endif - -#ifndef CLI11_BOOST_OPTIONAL -#define CLI11_BOOST_OPTIONAL 0 -#endif - -#if CLI11_BOOST_OPTIONAL -#include -#if BOOST_VERSION < 106100 -#error "This boost::optional version is not supported, use 1.61 or better" -#endif -#endif - -#if CLI11_STD_OPTIONAL -#include -#endif -#if CLI11_EXPERIMENTAL_OPTIONAL -#include -#endif -#if CLI11_BOOST_OPTIONAL -#include -#include -#endif -// [CLI11:verbatim] - -namespace CLI { - -#if CLI11_STD_OPTIONAL -template std::istream &operator>>(std::istream &in, std::optional &val) { - T v; - in >> v; - val = v; - return in; -} -#endif - -#if CLI11_EXPERIMENTAL_OPTIONAL -template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { - T v; - in >> v; - val = v; - return in; -} -#endif - -#if CLI11_BOOST_OPTIONAL -template std::istream &operator>>(std::istream &in, boost::optional &val) { - T v; - in >> v; - val = v; - return in; -} -#endif - -// Export the best optional to the CLI namespace -#if CLI11_STD_OPTIONAL -using std::optional; -#elif CLI11_EXPERIMENTAL_OPTIONAL -using std::experimental::optional; -#elif CLI11_BOOST_OPTIONAL -using boost::optional; -#endif - -// This is true if any optional is found -#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL -#define CLI11_OPTIONAL 1 -#endif - -} // namespace CLI diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index c5961c1d5..4ed8ea057 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -25,14 +25,6 @@ std::ostream &operator<<(std::ostream &in, const T &item) { return in << static_cast::type>(item); } -/// input streaming for enumerations -template ::value>::type> -std::istream &operator>>(std::istream &in, T &item) { - typename std::underlying_type::type i; - in >> i; - item = static_cast(i); - return in; -} } // namespace enums /// Export to CLI namespace @@ -57,17 +49,6 @@ inline std::vector split(const std::string &s, char delim) { } return elems; } -/// simple utility to convert various types to a string -template inline std::string as_string(const T &v) { - std::ostringstream s; - s << v; - return s.str(); -} -// if the data type is already a string just forward it -template ::value>::type> -inline auto as_string(T &&v) -> decltype(std::forward(v)) { - return std::forward(v); -} /// Simple function to join a string template std::string join(const T &v, std::string delim = ",") { diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 3d8a2f953..b8cba544c 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -10,17 +10,6 @@ #include #include -// [CLI11:verbatim] -#if defined(CLI11_CPP17) -#if defined(__has_include) -#if __has_include() -#include -#define CLI11_HAS_STRING_VIEW -#endif -#endif -#endif -// [CLI11:verbatim] - namespace CLI { // Type tools @@ -83,10 +72,6 @@ template struct IsMemberType { using type = T; }; /// The main custom type needed here is const char * should be a string. template <> struct IsMemberType { using type = std::string; }; -#ifdef CLI11_HAS_STRING_VIEW -template <> struct IsMemberType { using type = std::string; }; -#endif - namespace detail { // These are utilities for IsMember @@ -139,19 +124,68 @@ struct pair_adaptor< } }; -// Check for streamability +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int) -> decltype(TT{std::declval()}, std::is_move_assignable()); + + template static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test(0))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream -template class is_streamable { - template +template class is_ostreamable { + template static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); template static auto test(...) -> std::false_type; public: - static const bool value = decltype(test(0))::value; + static const bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test(0))::value; }; +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + /// Convert an object to a string (directly forward if this can become a string) template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(value)) { @@ -160,8 +194,8 @@ auto to_string(T &&value) -> decltype(std::forward(value)) { /// Convert an object to a string (streaming must be supported for that type) template ::value && is_streamable::value, - detail::enabler> = detail::dummy> + enable_if_t::value && is_ostreamable::value, detail::enabler> = + detail::dummy> std::string to_string(T &&value) { std::stringstream stream; stream << value; @@ -170,12 +204,30 @@ std::string to_string(T &&value) { /// If conversion is not supported, return an empty string (streaming is not supported for that type) template ::value && !is_streamable::value, - detail::enabler> = detail::dummy> + enable_if_t::value && !is_ostreamable::value, detail::enabler> = + detail::dummy> std::string to_string(T &&) { return std::string{}; } +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} + // Type name print /// Was going to be based on @@ -277,7 +329,7 @@ template < typename T, enable_if_t::value && std::is_signed::value && !is_bool::value && !std::is_enum::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { try { size_t n = 0; long long output_ll = std::stoll(input, &n, 0); @@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &output) { template ::value && std::is_unsigned::value && !is_bool::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { if(!input.empty() && input.front() == '-') return false; // std::stoull happily converts negative values to junk without any errors. @@ -312,7 +364,7 @@ bool lexical_cast(std::string input, T &output) { /// Boolean values template ::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { try { auto out = to_flag_value(input); output = (out > 0); @@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &output) { /// Floats template ::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { try { size_t n = 0; output = static_cast(std::stold(input, &n)); @@ -336,19 +388,29 @@ bool lexical_cast(std::string input, T &output) { } } -/// String and similar +/// String and similar direct assignment template ::value && !std::is_integral::value && std::is_assignable::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { output = input; return true; } +/// String and similar constructible and copy assignment +template ::value && !std::is_integral::value && + !std::is_assignable::value && std::is_constructible::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + /// Enumerations template ::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { +bool lexical_cast(const std::string &input, T &output) { typename std::underlying_type::type val; bool retval = detail::lexical_cast(input, val); if(!retval) { @@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &output) { return true; } -/// Non-string parsable +/// Assignable from double or int template ::value && !std::is_integral::value && - !std::is_assignable::value && !std::is_enum::value, + !std::is_assignable::value && + !std::is_constructible::value && !std::is_enum::value && + is_direct_constructible::value && is_direct_constructible::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - std::istringstream is; +bool lexical_cast(const std::string &input, T &output) { + int val; + if(lexical_cast(input, val)) { + output = T(val); + return true; + } else { + double dval; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + } + return from_stream(input, output); +} - is.str(input); - is >> output; - return !is.fail() && !is.rdbuf()->in_avail(); +/// Assignable from int64 +template ::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !std::is_enum::value && + !is_direct_constructible::value && is_direct_constructible::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(lexical_cast(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template ::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !std::is_enum::value && + is_direct_constructible::value && !is_direct_constructible::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string parsable by a stream +template ::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !std::is_enum::value && + !is_direct_constructible::value && !is_direct_constructible::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); +} + +/// Assign a value through lexical cast operations +template ::value, detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + return lexical_cast(input, output); +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template < + class T, + class XC, + enable_if_t::value && std::is_assignable::value, detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + XC val; + auto parse_result = lexical_cast(input, val); + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template ::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + XC val; + bool parse_result = lexical_cast(input, val); + if(parse_result) { + output = T(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; } /// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// common true and false strings then uses stoll to convert the rest for summing template &flags, T &output) { } /// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// common true and false strings then uses stoll to convert the rest for summing template max) - input = detail::as_string(max); + input = detail::to_string(max); - return std::string(); + return std::string{}; }; } @@ -451,9 +451,11 @@ template std::string generate_map(const T &map, bool key_only = fal std::string out(1, '{'); out.append(detail::join(detail::smart_deref(map), [key_only](const iteration_type_t &v) { - auto res = detail::as_string(detail::pair_adaptor::first(v)); + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + if(!key_only) { - res += "->" + detail::as_string(detail::pair_adaptor::second(v)); + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); } return res; }, @@ -581,7 +583,7 @@ class IsMember : public Validator { if(res.first) { // Make sure the version in the input string is identical to the one in the set if(filter_fn) { - input = detail::as_string(detail::pair_adaptor::first(*(res.second))); + input = detail::to_string(detail::pair_adaptor::first(*(res.second))); } // Return empty error string (success) @@ -649,7 +651,7 @@ class Transformer : public Validator { } auto res = detail::search(mapping, b, filter_fn); if(res.first) { - input = detail::as_string(detail::pair_adaptor::second(*res.second)); + input = detail::to_string(detail::pair_adaptor::second(*res.second)); } return std::string{}; }; @@ -699,7 +701,7 @@ class CheckedTransformer : public Validator { out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; out += detail::join( detail::smart_deref(mapping), - [](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor::second(v)); }, + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, ","); out.push_back('}'); return out; @@ -716,12 +718,12 @@ class CheckedTransformer : public Validator { } auto res = detail::search(mapping, b, filter_fn); if(res.first) { - input = detail::as_string(detail::pair_adaptor::second(*res.second)); + input = detail::to_string(detail::pair_adaptor::second(*res.second)); return std::string{}; } } for(const auto &v : detail::smart_deref(mapping)) { - auto output_string = detail::as_string(detail::pair_adaptor::second(v)); + auto output_string = detail::to_string(detail::pair_adaptor::second(v)); if(output_string == input) { return std::string(); } @@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator { // perform safe multiplication bool ok = detail::checked_multiply(num, it->second); if(!ok) { - throw ValidationError(detail::as_string(num) + " multiplied by " + unit + + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + " factor would cause number overflow. Use smaller value."); } - input = detail::as_string(num); + input = detail::to_string(num); return {}; }; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index bb6bd864e..4742dfe1a 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) { EXPECT_EQ(app.count_all(), 3u); } +TEST_F(TApp, TwoParamTemplateOpts) { + + double funnyint; + auto opt = app.add_option("-y", funnyint); + + args = {"-y", "32"}; + + run(); + + EXPECT_EQ(32.0, funnyint); + + args = {"-y", "32.3"}; + EXPECT_THROW(run(), CLI::ConversionError); + + args = {"-y", "-19"}; + EXPECT_THROW(run(), CLI::ConversionError); + + opt->capture_default_str(); + EXPECT_TRUE(opt->get_default_str().empty()); +} + TEST_F(TApp, DefaultOpts) { int i = 3; diff --git a/tests/CreationTest.cpp b/tests/CreationTest.cpp index a1cf95fb1..682a0cd57 100644 --- a/tests/CreationTest.cpp +++ b/tests/CreationTest.cpp @@ -741,14 +741,19 @@ class Unstreamable { void set_x(int x) { x_ = x; } }; +// this needs to be a different check then the one after the function definition otherwise they conflict +static_assert(!CLI::detail::is_istreamable::value, "Unstreamable type is streamable"); + std::istream &operator>>(std::istream &in, Unstreamable &value) { int x; in >> x; value.set_x(x); return in; } +// these need to be different classes otherwise the definitions conflict +static_assert(CLI::detail::is_istreamable::value, "Unstreamable type is still unstreamable"); -TEST_F(TApp, MakeUnstreamableOptiions) { +TEST_F(TApp, MakeUnstreamableOptions) { Unstreamable value; app.add_option("--value", value); @@ -760,4 +765,13 @@ TEST_F(TApp, MakeUnstreamableOptiions) { // This used to fail to build, since it tries to stream from Unstreamable app.add_option("--values2", values, "", false); + + args = {"--value", "45"}; + run(); + EXPECT_EQ(value.get_x(), 45); + + args = {"--values", "45", "27", "34"}; + run(); + EXPECT_EQ(values.size(), 3u); + EXPECT_EQ(values[2].get_x(), 34); } diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index c7fa8eb18..bf1b44a09 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -855,6 +855,10 @@ TEST(Types, LexicalCastParsable) { EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const + EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output)); + EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble + EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const + EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); } diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index 89e7fbeea..dd5b8265d 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -137,7 +137,8 @@ namespace CLI { namespace detail { template <> -bool lexical_cast>(std::string input, std::pair &output) { +bool lexical_cast>(const std::string &input, + std::pair &output) { auto sep = input.find_first_of(':'); if((sep == std::string::npos) && (sep > 0)) { @@ -187,7 +188,7 @@ namespace detail { // On MSVC and possibly some other new compilers this can be a free standing function without the template // specialization but this is compiler dependent -template <> bool lexical_cast>(std::string input, std::complex &output) { +template <> bool lexical_cast>(const std::string &input, std::complex &output) { // regular expression to handle complex numbers of various formats static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); @@ -209,8 +210,9 @@ template <> bool lexical_cast>(std::string input, std::comp CLI::detail::trim(strval); worked = CLI::detail::lexical_cast(strval, y); } else { - CLI::detail::trim(input); - worked = CLI::detail::lexical_cast(input, x); + std::string ival = input; + CLI::detail::trim(ival); + worked = CLI::detail::lexical_cast(ival, x); } } if(worked) { @@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) { } } #endif + +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the +/// option assignments +template class objWrapper { + public: + objWrapper() = default; + explicit objWrapper(X obj) : val_{obj} {}; + objWrapper(const objWrapper &ow) = default; + template objWrapper(const TT &obj) = delete; + objWrapper &operator=(const objWrapper &) = default; + objWrapper &operator=(objWrapper &&) = default; + // delete all other assignment operators + template void operator=(TT &&obj) = delete; + + const X &value() const { return val_; } + + private: + X val_; +}; + +// I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions +// so this test will not compile in that compiler +#if !defined(_MSC_VER) || _MSC_VER >= 1910 + +static_assert(CLI::detail::is_direct_constructible, std::string>::value, + "string wrapper isn't properly constructible"); + +static_assert(!std::is_assignable, std::string>::value, + "string wrapper is improperly assignable"); +TEST_F(TApp, stringWrapper) { + objWrapper sWrapper; + app.add_option("-v", sWrapper); + args = {"-v", "string test"}; + + run(); + + EXPECT_EQ(sWrapper.value(), "string test"); +} + +static_assert(CLI::detail::is_direct_constructible, double>::value, + "double wrapper isn't properly assignable"); + +static_assert(!CLI::detail::is_direct_constructible, int>::value, + "double wrapper can be assigned from int"); + +static_assert(!CLI::detail::is_istreamable>::value, + "double wrapper is input streamable and it shouldn't be"); + +TEST_F(TApp, doubleWrapper) { + objWrapper dWrapper; + app.add_option("-v", dWrapper); + args = {"-v", "2.36"}; + + run(); + + EXPECT_EQ(dWrapper.value(), 2.36); + + args = {"-v", "thing"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +static_assert(CLI::detail::is_direct_constructible, int>::value, + "int wrapper is not constructible from int64"); + +static_assert(!CLI::detail::is_direct_constructible, double>::value, + "int wrapper is constructible from double"); + +static_assert(!CLI::detail::is_istreamable>::value, + "int wrapper is input streamable and it shouldn't be"); + +TEST_F(TApp, intWrapper) { + objWrapper iWrapper; + app.add_option("-v", iWrapper); + args = {"-v", "45"}; + + run(); + + EXPECT_EQ(iWrapper.value(), 45); + args = {"-v", "thing"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +static_assert(!CLI::detail::is_direct_constructible, int>::value, + "float wrapper is constructible from int"); +static_assert(!CLI::detail::is_direct_constructible, double>::value, + "float wrapper is constructible from double"); + +static_assert(!CLI::detail::is_istreamable>::value, + "float wrapper is input streamable and it shouldn't be"); + +TEST_F(TApp, floatWrapper) { + objWrapper iWrapper; + app.add_option, float>("-v", iWrapper); + args = {"-v", "45.3"}; + + run(); + + EXPECT_EQ(iWrapper.value(), 45.3f); + args = {"-v", "thing"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +#endif +/// simple class to wrap another with a very specific type constructor to test out some of the option assignments +class dobjWrapper { + public: + dobjWrapper() = default; + explicit dobjWrapper(double obj) : dval_{obj} {}; + explicit dobjWrapper(int obj) : ival_{obj} {}; + + double dvalue() const { return dval_; } + int ivalue() const { return ival_; } + + private: + double dval_{0.0}; + int ival_{0}; +}; + +TEST_F(TApp, dobjWrapper) { + dobjWrapper iWrapper; + app.add_option("-v", iWrapper); + args = {"-v", "45"}; + + run(); + + EXPECT_EQ(iWrapper.ivalue(), 45); + EXPECT_EQ(iWrapper.dvalue(), 0.0); + + args = {"-v", "thing"}; + + EXPECT_THROW(run(), CLI::ConversionError); + iWrapper = dobjWrapper{}; + + args = {"-v", "45.1"}; + + run(); + EXPECT_EQ(iWrapper.ivalue(), 0); + EXPECT_EQ(iWrapper.dvalue(), 45.1); +} + +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the +/// option assignments +template class AobjWrapper { + public: + AobjWrapper() = default; + // delete all other constructors + template AobjWrapper(TT &&obj) = delete; + // single assignment operator + void operator=(X val) { val_ = val; } + // delete all other assignment operators + template void operator=(TT &&obj) = delete; + + const X &value() const { return val_; } + + private: + X val_; +}; + +static_assert(std::is_assignable &, uint16_t>::value, + "AobjWrapper not assignable like it should be "); + +TEST_F(TApp, uint16Wrapper) { + AobjWrapper sWrapper; + app.add_option, uint16_t>("-v", sWrapper); + args = {"-v", "9"}; + + run(); + + EXPECT_EQ(sWrapper.value(), 9u); + args = {"-v", "thing"}; + + EXPECT_THROW(run(), CLI::ConversionError); + + args = {"-v", "72456245754"}; + + EXPECT_THROW(run(), CLI::ConversionError); + + args = {"-v", "-3"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} diff --git a/tests/OptionalTest.cpp b/tests/OptionalTest.cpp index 720e73b7a..32c29d303 100644 --- a/tests/OptionalTest.cpp +++ b/tests/OptionalTest.cpp @@ -3,6 +3,48 @@ #include "app_helper.hpp" +// You can explicitly enable or disable support +// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too. +// We nest the check for __has_include and it's usage +#ifndef CLI11_STD_OPTIONAL +#ifdef __has_include +#if defined(CLI11_CPP17) && __has_include() +#define CLI11_STD_OPTIONAL 1 +#else +#define CLI11_STD_OPTIONAL 0 +#endif +#else +#define CLI11_STD_OPTIONAL 0 +#endif +#endif + +#ifndef CLI11_EXPERIMENTAL_OPTIONAL +#define CLI11_EXPERIMENTAL_OPTIONAL 0 +#endif + +#ifndef CLI11_BOOST_OPTIONAL +#define CLI11_BOOST_OPTIONAL 0 +#endif + +#if CLI11_BOOST_OPTIONAL +#include +#if BOOST_VERSION < 106100 +#error "This boost::optional version is not supported, use 1.61 or better" +#endif +#endif + +#if CLI11_STD_OPTIONAL +#include +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include +#endif +#if CLI11_BOOST_OPTIONAL +#include +#include +#endif +// [CLI11:verbatim] + #if CLI11_STD_OPTIONAL TEST_F(TApp, StdOptionalTest) { @@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) { run(); EXPECT_TRUE(opt); EXPECT_EQ(*opt, 1); + opt = {}; + args = {"--count", "3"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 3); +} +TEST_F(TApp, BoostOptionalint64Test) { + boost::optional opt; + app.add_option("-c,--count", opt); + run(); + EXPECT_FALSE(opt); + + args = {"-c", "1"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 1); + opt = {}; args = {"--count", "3"}; run(); EXPECT_TRUE(opt); EXPECT_EQ(*opt, 3); } +TEST_F(TApp, BoostOptionalStringTest) { + boost::optional opt; + app.add_option("-s,--string", opt); + run(); + EXPECT_FALSE(opt); + + args = {"-s", "strval"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, "strval"); + opt = {}; + args = {"--string", "strv"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, "strv"); +} + +TEST_F(TApp, BoostOptionalEnumTest) { + enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; + boost::optional opt; + auto optptr = app.add_option("-v,--val", opt); + optptr->capture_default_str(); + + auto dstring = optptr->get_default_str(); + EXPECT_TRUE(dstring.empty()); + run(); + EXPECT_FALSE(opt); + + args = {"-v", "3"}; + run(); + EXPECT_TRUE(opt); + EXPECT_TRUE(*opt == eval::val3); + opt = {}; + args = {"--val", "1"}; + run(); + EXPECT_TRUE(opt); + EXPECT_TRUE(*opt == eval::val1); +} + TEST_F(TApp, BoostOptionalVector) { boost::optional> opt; app.add_option_function>("-v,--vec", [&opt](const std::vector &v) { opt = v; }, "some vector") diff --git a/tests/TransformTest.cpp b/tests/TransformTest.cpp index 4884a3eea..ad3ce1382 100644 --- a/tests/TransformTest.cpp +++ b/tests/TransformTest.cpp @@ -2,6 +2,15 @@ #include +#if defined(CLI11_CPP17) +#if defined(__has_include) +#if __has_include() +#include +#define CLI11_HAS_STRING_VIEW +#endif +#endif +#endif + TEST_F(TApp, SimpleTransform) { int value; auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}})); @@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) { run(); EXPECT_EQ(value, "mapped"); } + #endif TEST_F(TApp, SimpleNumericalTransformFn) {