mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-03 14:03:52 +00:00
modified option template (#285)
* add some tests with default capture on the two parameter template and some notes about it in the README.md remove the test from visual studio 2015 vs2015 doesn't seem to properly deal with is_assignable in the cases we care about so make a standalone version that is more direct in what we are doing add version to appveyor and add some notes to the readme fix a few test cases to make sure code is covered and test a few other paths remove unneeded enum streaming operator add some diagnostic escapes around trait code to eliminate gcc Wnarrowing warnings work specification of the template operations remove optional add some templates for options conversions add the two parameter template for add_option * Fix some comments from Code review and add more description * fix case when string_view doesn't work to append to a string. * This PR also addressed #300 * modify lexical_cast to take const std::string &, instead of by value to allow string_view in a few cases
This commit is contained in:
parent
dbd4933506
commit
eab92ed988
@ -1,3 +1,5 @@
|
|||||||
|
version: 1.8.0.{build}
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
37
README.md
37
README.md
@ -55,7 +55,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
|
|||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
- [License](#license)
|
- [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
|
## 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, help_str="") // 🆕
|
||||||
|
|
||||||
app.add_option(option_name,
|
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="")
|
help_string="")
|
||||||
|
|
||||||
app.add_option_function<type>(option_name,
|
app.add_option_function<type>(option_name,
|
||||||
function <void(const type &value)>, // 🆕 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string
|
function <void(const type &value)>, // 🆕 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="")
|
help_string="")
|
||||||
|
|
||||||
app.add_complex(... // Special case: support for complex numbers
|
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<typename T, typename XC>(option_name,
|
||||||
|
T &output, // output must be assignable or constructible from a value of type XC
|
||||||
|
help_string="")
|
||||||
|
|
||||||
// Add flags
|
// Add flags
|
||||||
app.add_flag(option_name,
|
app.add_flag(option_name,
|
||||||
help_string="")
|
help_string="")
|
||||||
|
|
||||||
app.add_flag(option_name,
|
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="")
|
help_string="")
|
||||||
|
|
||||||
app.add_flag_function(option_name, // 🆕
|
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<type>(...` 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 `add_option_function<type>(...` 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<double,unsigned int>("-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<int, double, std::string>;
|
||||||
|
vtype v1;
|
||||||
|
app.add_option<vtype,std:string>("--vs",v1);
|
||||||
|
app.add_option<vtype,int>("--vi",v1);
|
||||||
|
app.add_option<vtype,double>("--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<int>, optional<double>, and optional<string> 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:
|
🆕 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
|
```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 `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
|
#### 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`
|
- `"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.
|
- `->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.
|
- `->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.
|
`->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.
|
- `->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`.
|
- `->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<std::string>` of results.
|
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<std::string>` of results.
|
||||||
|
@ -4,13 +4,11 @@
|
|||||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -469,18 +467,23 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
|
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
|
||||||
template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy>
|
|
||||||
|
template <typename T,
|
||||||
|
typename XC = T,
|
||||||
|
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
|
||||||
Option *add_option(std::string option_name,
|
Option *add_option(std::string option_name,
|
||||||
T &variable, ///< The variable to set
|
T &variable, ///< The variable to set
|
||||||
std::string option_description = "",
|
std::string option_description = "",
|
||||||
bool defaulted = false) {
|
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<T, XC>(res[0], variable);
|
||||||
|
};
|
||||||
|
|
||||||
Option *opt = add_option(option_name, fun, option_description, defaulted, [&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<T, XC>(variable));
|
||||||
});
|
});
|
||||||
opt->type_name(detail::type_name<T>());
|
opt->type_name(detail::type_name<XC>());
|
||||||
|
|
||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
|
|
||||||
#include "CLI/Macros.hpp"
|
#include "CLI/Macros.hpp"
|
||||||
|
|
||||||
#include "CLI/Optional.hpp"
|
|
||||||
|
|
||||||
#include "CLI/StringTools.hpp"
|
#include "CLI/StringTools.hpp"
|
||||||
|
|
||||||
#include "CLI/Error.hpp"
|
#include "CLI/Error.hpp"
|
||||||
|
@ -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 <istream>
|
|
||||||
|
|
||||||
#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(<optional>)
|
|
||||||
#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 <boost/version.hpp>
|
|
||||||
#if BOOST_VERSION < 106100
|
|
||||||
#error "This boost::optional version is not supported, use 1.61 or better"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if CLI11_STD_OPTIONAL
|
|
||||||
#include <optional>
|
|
||||||
#endif
|
|
||||||
#if CLI11_EXPERIMENTAL_OPTIONAL
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
#if CLI11_BOOST_OPTIONAL
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
#include <boost/optional/optional_io.hpp>
|
|
||||||
#endif
|
|
||||||
// [CLI11:verbatim]
|
|
||||||
|
|
||||||
namespace CLI {
|
|
||||||
|
|
||||||
#if CLI11_STD_OPTIONAL
|
|
||||||
template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) {
|
|
||||||
T v;
|
|
||||||
in >> v;
|
|
||||||
val = v;
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if CLI11_EXPERIMENTAL_OPTIONAL
|
|
||||||
template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) {
|
|
||||||
T v;
|
|
||||||
in >> v;
|
|
||||||
val = v;
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if CLI11_BOOST_OPTIONAL
|
|
||||||
template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &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
|
|
@ -25,14 +25,6 @@ std::ostream &operator<<(std::ostream &in, const T &item) {
|
|||||||
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// input streaming for enumerations
|
|
||||||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
|
||||||
std::istream &operator>>(std::istream &in, T &item) {
|
|
||||||
typename std::underlying_type<T>::type i;
|
|
||||||
in >> i;
|
|
||||||
item = static_cast<T>(i);
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
} // namespace enums
|
} // namespace enums
|
||||||
|
|
||||||
/// Export to CLI namespace
|
/// Export to CLI namespace
|
||||||
@ -57,17 +49,6 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
|
|||||||
}
|
}
|
||||||
return elems;
|
return elems;
|
||||||
}
|
}
|
||||||
/// simple utility to convert various types to a string
|
|
||||||
template <typename T> 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 <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>
|
|
||||||
inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {
|
|
||||||
return std::forward<T>(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple function to join a string
|
/// Simple function to join a string
|
||||||
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||||||
|
@ -10,17 +10,6 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// [CLI11:verbatim]
|
|
||||||
#if defined(CLI11_CPP17)
|
|
||||||
#if defined(__has_include)
|
|
||||||
#if __has_include(<string_view>)
|
|
||||||
#include <string_view>
|
|
||||||
#define CLI11_HAS_STRING_VIEW
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
// [CLI11:verbatim]
|
|
||||||
|
|
||||||
namespace CLI {
|
namespace CLI {
|
||||||
|
|
||||||
// Type tools
|
// Type tools
|
||||||
@ -83,10 +72,6 @@ template <typename T> struct IsMemberType { using type = T; };
|
|||||||
/// The main custom type needed here is const char * should be a string.
|
/// The main custom type needed here is const char * should be a string.
|
||||||
template <> struct IsMemberType<const char *> { using type = std::string; };
|
template <> struct IsMemberType<const char *> { using type = std::string; };
|
||||||
|
|
||||||
#ifdef CLI11_HAS_STRING_VIEW
|
|
||||||
template <> struct IsMemberType<std::string_view> { using type = std::string; };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// These are utilities for IsMember
|
// 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 <typename T, typename C> class is_direct_constructible {
|
||||||
|
template <typename TT, typename CC>
|
||||||
|
static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
|
||||||
|
|
||||||
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const bool value = decltype(test<T, C>(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
|
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
|
||||||
|
|
||||||
template <typename S, typename T> class is_streamable {
|
template <typename T, typename S = std::ostringstream> class is_ostreamable {
|
||||||
template <typename SS, typename TT>
|
template <typename TT, typename SS>
|
||||||
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
|
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
|
||||||
|
|
||||||
template <typename, typename> static auto test(...) -> std::false_type;
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const bool value = decltype(test<S, T>(0))::value;
|
static const bool value = decltype(test<T, S>(0))::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Check for input streamability
|
||||||
|
template <typename T, typename S = std::istringstream> class is_istreamable {
|
||||||
|
template <typename TT, typename SS>
|
||||||
|
static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
|
||||||
|
|
||||||
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const bool value = decltype(test<T, S>(0))::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Templated operation to get a value from a stream
|
||||||
|
template <typename T, enable_if_t<is_istreamable<T>::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 <typename T, enable_if_t<!is_istreamable<T>::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)
|
/// Convert an object to a string (directly forward if this can become a string)
|
||||||
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
|
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
|
||||||
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
||||||
@ -160,8 +194,8 @@ auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
|||||||
|
|
||||||
/// Convert an object to a string (streaming must be supported for that type)
|
/// Convert an object to a string (streaming must be supported for that type)
|
||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::value,
|
enable_if_t<!std::is_constructible<std::string, T>::value && is_ostreamable<T>::value, detail::enabler> =
|
||||||
detail::enabler> = detail::dummy>
|
detail::dummy>
|
||||||
std::string to_string(T &&value) {
|
std::string to_string(T &&value) {
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
stream << value;
|
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)
|
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
|
||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::value,
|
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =
|
||||||
detail::enabler> = detail::dummy>
|
detail::dummy>
|
||||||
std::string to_string(T &&) {
|
std::string to_string(T &&) {
|
||||||
return std::string{};
|
return std::string{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// special template overload
|
||||||
|
template <typename T1,
|
||||||
|
typename T2,
|
||||||
|
typename T,
|
||||||
|
enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
|
||||||
|
auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
|
||||||
|
return to_string(std::forward<T>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// special template overload
|
||||||
|
template <typename T1,
|
||||||
|
typename T2,
|
||||||
|
typename T,
|
||||||
|
enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
|
||||||
|
std::string checked_to_string(T &&) {
|
||||||
|
return std::string{};
|
||||||
|
}
|
||||||
|
|
||||||
// Type name print
|
// Type name print
|
||||||
|
|
||||||
/// Was going to be based on
|
/// Was going to be based on
|
||||||
@ -277,7 +329,7 @@ template <
|
|||||||
typename T,
|
typename T,
|
||||||
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
|
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
|
||||||
detail::enabler> = detail::dummy>
|
detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
try {
|
try {
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
long long output_ll = std::stoll(input, &n, 0);
|
long long output_ll = std::stoll(input, &n, 0);
|
||||||
@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &output) {
|
|||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
|
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
|
||||||
detail::dummy>
|
detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
if(!input.empty() && input.front() == '-')
|
if(!input.empty() && input.front() == '-')
|
||||||
return false; // std::stoull happily converts negative values to junk without any errors.
|
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
|
/// Boolean values
|
||||||
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
|
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
try {
|
try {
|
||||||
auto out = to_flag_value(input);
|
auto out = to_flag_value(input);
|
||||||
output = (out > 0);
|
output = (out > 0);
|
||||||
@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &output) {
|
|||||||
|
|
||||||
/// Floats
|
/// Floats
|
||||||
template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
try {
|
try {
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
output = static_cast<T>(std::stold(input, &n));
|
output = static_cast<T>(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 <typename T,
|
template <typename T,
|
||||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
std::is_assignable<T &, std::string>::value,
|
std::is_assignable<T &, std::string>::value,
|
||||||
detail::enabler> = detail::dummy>
|
detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
output = input;
|
output = input;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// String and similar constructible and copy assignment
|
||||||
|
template <typename T,
|
||||||
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
|
!std::is_assignable<T &, std::string>::value && std::is_constructible<T, std::string>::value,
|
||||||
|
detail::enabler> = detail::dummy>
|
||||||
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
|
output = T(input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Enumerations
|
/// Enumerations
|
||||||
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
|
template <typename T, enable_if_t<std::is_enum<T>::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<T>::type val;
|
typename std::underlying_type<T>::type val;
|
||||||
bool retval = detail::lexical_cast(input, val);
|
bool retval = detail::lexical_cast(input, val);
|
||||||
if(!retval) {
|
if(!retval) {
|
||||||
@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &output) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-string parsable
|
/// Assignable from double or int
|
||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
!std::is_assignable<T &, std::string>::value && !std::is_enum<T>::value,
|
!std::is_assignable<T &, std::string>::value &&
|
||||||
|
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
|
||||||
|
is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value,
|
||||||
detail::enabler> = detail::dummy>
|
detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(std::string input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
std::istringstream is;
|
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);
|
/// Assignable from int64
|
||||||
is >> output;
|
template <typename T,
|
||||||
return !is.fail() && !is.rdbuf()->in_avail();
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
|
!std::is_assignable<T &, std::string>::value &&
|
||||||
|
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
|
||||||
|
!is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::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 <typename T,
|
||||||
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
|
!std::is_assignable<T &, std::string>::value &&
|
||||||
|
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
|
||||||
|
is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::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 <typename T,
|
||||||
|
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
|
||||||
|
!std::is_assignable<T &, std::string>::value &&
|
||||||
|
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
|
||||||
|
!is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value,
|
||||||
|
detail::enabler> = detail::dummy>
|
||||||
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
|
static_assert(is_istreamable<T>::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<T, XC>(...) with XC being the known type");
|
||||||
|
return from_stream(input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assign a value through lexical cast operations
|
||||||
|
template <class T, class XC, enable_if_t<std::is_same<T, XC>::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<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
|
||||||
|
bool lexical_assign(const std::string &input, T &output) {
|
||||||
|
XC val;
|
||||||
|
auto parse_result = lexical_cast<XC>(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 <class T,
|
||||||
|
class XC,
|
||||||
|
enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
|
||||||
|
std::is_move_assignable<T>::value,
|
||||||
|
detail::enabler> = detail::dummy>
|
||||||
|
bool lexical_assign(const std::string &input, T &output) {
|
||||||
|
XC val;
|
||||||
|
bool parse_result = lexical_cast<XC>(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
|
/// 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
|
/// "-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
|
/// common true and false strings then uses stoll to convert the rest for summing
|
||||||
template <typename T,
|
template <typename T,
|
||||||
@ -386,7 +539,8 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sum a vector of flag representations
|
/// 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
|
/// "-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
|
/// common true and false strings then uses stoll to convert the rest for summing
|
||||||
template <typename T,
|
template <typename T,
|
||||||
|
@ -407,11 +407,11 @@ class Bound : public Validator {
|
|||||||
return "Value " + input + " could not be converted";
|
return "Value " + input + " could not be converted";
|
||||||
}
|
}
|
||||||
if(val < min)
|
if(val < min)
|
||||||
input = detail::as_string(min);
|
input = detail::to_string(min);
|
||||||
else if(val > max)
|
else if(val > max)
|
||||||
input = detail::as_string(max);
|
input = detail::to_string(max);
|
||||||
|
|
||||||
return std::string();
|
return std::string{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,9 +451,11 @@ template <typename T> std::string generate_map(const T &map, bool key_only = fal
|
|||||||
std::string out(1, '{');
|
std::string out(1, '{');
|
||||||
out.append(detail::join(detail::smart_deref(map),
|
out.append(detail::join(detail::smart_deref(map),
|
||||||
[key_only](const iteration_type_t &v) {
|
[key_only](const iteration_type_t &v) {
|
||||||
auto res = detail::as_string(detail::pair_adaptor<element_t>::first(v));
|
std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
|
||||||
|
|
||||||
if(!key_only) {
|
if(!key_only) {
|
||||||
res += "->" + detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
res.append("->");
|
||||||
|
res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
@ -581,7 +583,7 @@ class IsMember : public Validator {
|
|||||||
if(res.first) {
|
if(res.first) {
|
||||||
// Make sure the version in the input string is identical to the one in the set
|
// Make sure the version in the input string is identical to the one in the set
|
||||||
if(filter_fn) {
|
if(filter_fn) {
|
||||||
input = detail::as_string(detail::pair_adaptor<element_t>::first(*(res.second)));
|
input = detail::to_string(detail::pair_adaptor<element_t>::first(*(res.second)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return empty error string (success)
|
// Return empty error string (success)
|
||||||
@ -649,7 +651,7 @@ class Transformer : public Validator {
|
|||||||
}
|
}
|
||||||
auto res = detail::search(mapping, b, filter_fn);
|
auto res = detail::search(mapping, b, filter_fn);
|
||||||
if(res.first) {
|
if(res.first) {
|
||||||
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
|
input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||||
}
|
}
|
||||||
return std::string{};
|
return std::string{};
|
||||||
};
|
};
|
||||||
@ -699,7 +701,7 @@ class CheckedTransformer : public Validator {
|
|||||||
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
|
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
|
||||||
out += detail::join(
|
out += detail::join(
|
||||||
detail::smart_deref(mapping),
|
detail::smart_deref(mapping),
|
||||||
[](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor<element_t>::second(v)); },
|
[](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
|
||||||
",");
|
",");
|
||||||
out.push_back('}');
|
out.push_back('}');
|
||||||
return out;
|
return out;
|
||||||
@ -716,12 +718,12 @@ class CheckedTransformer : public Validator {
|
|||||||
}
|
}
|
||||||
auto res = detail::search(mapping, b, filter_fn);
|
auto res = detail::search(mapping, b, filter_fn);
|
||||||
if(res.first) {
|
if(res.first) {
|
||||||
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
|
input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||||
return std::string{};
|
return std::string{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(const auto &v : detail::smart_deref(mapping)) {
|
for(const auto &v : detail::smart_deref(mapping)) {
|
||||||
auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
auto output_string = detail::to_string(detail::pair_adaptor<element_t>::second(v));
|
||||||
if(output_string == input) {
|
if(output_string == input) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator {
|
|||||||
// perform safe multiplication
|
// perform safe multiplication
|
||||||
bool ok = detail::checked_multiply(num, it->second);
|
bool ok = detail::checked_multiply(num, it->second);
|
||||||
if(!ok) {
|
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.");
|
" factor would cause number overflow. Use smaller value.");
|
||||||
}
|
}
|
||||||
input = detail::as_string(num);
|
input = detail::to_string(num);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) {
|
|||||||
EXPECT_EQ(app.count_all(), 3u);
|
EXPECT_EQ(app.count_all(), 3u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(TApp, TwoParamTemplateOpts) {
|
||||||
|
|
||||||
|
double funnyint;
|
||||||
|
auto opt = app.add_option<double, unsigned int>("-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) {
|
TEST_F(TApp, DefaultOpts) {
|
||||||
|
|
||||||
int i = 3;
|
int i = 3;
|
||||||
|
@ -741,14 +741,19 @@ class Unstreamable {
|
|||||||
void set_x(int x) { x_ = x; }
|
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<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
|
||||||
|
|
||||||
std::istream &operator>>(std::istream &in, Unstreamable &value) {
|
std::istream &operator>>(std::istream &in, Unstreamable &value) {
|
||||||
int x;
|
int x;
|
||||||
in >> x;
|
in >> x;
|
||||||
value.set_x(x);
|
value.set_x(x);
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
// these need to be different classes otherwise the definitions conflict
|
||||||
|
static_assert(CLI::detail::is_istreamable<Unstreamable>::value, "Unstreamable type is still unstreamable");
|
||||||
|
|
||||||
TEST_F(TApp, MakeUnstreamableOptiions) {
|
TEST_F(TApp, MakeUnstreamableOptions) {
|
||||||
Unstreamable value;
|
Unstreamable value;
|
||||||
app.add_option("--value", 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
|
// This used to fail to build, since it tries to stream from Unstreamable
|
||||||
app.add_option("--values2", values, "", false);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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.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_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(fail_input, output));
|
||||||
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
|
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,8 @@ namespace CLI {
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool lexical_cast<std::pair<std::string, std::string>>(std::string input, std::pair<std::string, std::string> &output) {
|
bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
|
||||||
|
std::pair<std::string, std::string> &output) {
|
||||||
|
|
||||||
auto sep = input.find_first_of(':');
|
auto sep = input.find_first_of(':');
|
||||||
if((sep == std::string::npos) && (sep > 0)) {
|
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
|
// On MSVC and possibly some other new compilers this can be a free standing function without the template
|
||||||
// specialization but this is compiler dependent
|
// specialization but this is compiler dependent
|
||||||
template <> bool lexical_cast<std::complex<double>>(std::string input, std::complex<double> &output) {
|
template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
|
||||||
// regular expression to handle complex numbers of various formats
|
// regular expression to handle complex numbers of various formats
|
||||||
static const std::regex creg(
|
static const std::regex creg(
|
||||||
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
|
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
|
||||||
@ -209,8 +210,9 @@ template <> bool lexical_cast<std::complex<double>>(std::string input, std::comp
|
|||||||
CLI::detail::trim(strval);
|
CLI::detail::trim(strval);
|
||||||
worked = CLI::detail::lexical_cast(strval, y);
|
worked = CLI::detail::lexical_cast(strval, y);
|
||||||
} else {
|
} else {
|
||||||
CLI::detail::trim(input);
|
std::string ival = input;
|
||||||
worked = CLI::detail::lexical_cast(input, x);
|
CLI::detail::trim(ival);
|
||||||
|
worked = CLI::detail::lexical_cast(ival, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(worked) {
|
if(worked) {
|
||||||
@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#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 X> class objWrapper {
|
||||||
|
public:
|
||||||
|
objWrapper() = default;
|
||||||
|
explicit objWrapper(X obj) : val_{obj} {};
|
||||||
|
objWrapper(const objWrapper &ow) = default;
|
||||||
|
template <class TT> objWrapper(const TT &obj) = delete;
|
||||||
|
objWrapper &operator=(const objWrapper &) = default;
|
||||||
|
objWrapper &operator=(objWrapper &&) = default;
|
||||||
|
// delete all other assignment operators
|
||||||
|
template <typename TT> 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<objWrapper<std::string>, std::string>::value,
|
||||||
|
"string wrapper isn't properly constructible");
|
||||||
|
|
||||||
|
static_assert(!std::is_assignable<objWrapper<std::string>, std::string>::value,
|
||||||
|
"string wrapper is improperly assignable");
|
||||||
|
TEST_F(TApp, stringWrapper) {
|
||||||
|
objWrapper<std::string> sWrapper;
|
||||||
|
app.add_option("-v", sWrapper);
|
||||||
|
args = {"-v", "string test"};
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
EXPECT_EQ(sWrapper.value(), "string test");
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(CLI::detail::is_direct_constructible<objWrapper<double>, double>::value,
|
||||||
|
"double wrapper isn't properly assignable");
|
||||||
|
|
||||||
|
static_assert(!CLI::detail::is_direct_constructible<objWrapper<double>, int>::value,
|
||||||
|
"double wrapper can be assigned from int");
|
||||||
|
|
||||||
|
static_assert(!CLI::detail::is_istreamable<objWrapper<double>>::value,
|
||||||
|
"double wrapper is input streamable and it shouldn't be");
|
||||||
|
|
||||||
|
TEST_F(TApp, doubleWrapper) {
|
||||||
|
objWrapper<double> 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<objWrapper<int>, int>::value,
|
||||||
|
"int wrapper is not constructible from int64");
|
||||||
|
|
||||||
|
static_assert(!CLI::detail::is_direct_constructible<objWrapper<int>, double>::value,
|
||||||
|
"int wrapper is constructible from double");
|
||||||
|
|
||||||
|
static_assert(!CLI::detail::is_istreamable<objWrapper<int>>::value,
|
||||||
|
"int wrapper is input streamable and it shouldn't be");
|
||||||
|
|
||||||
|
TEST_F(TApp, intWrapper) {
|
||||||
|
objWrapper<int> 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<objWrapper<float>, int>::value,
|
||||||
|
"float wrapper is constructible from int");
|
||||||
|
static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, double>::value,
|
||||||
|
"float wrapper is constructible from double");
|
||||||
|
|
||||||
|
static_assert(!CLI::detail::is_istreamable<objWrapper<float>>::value,
|
||||||
|
"float wrapper is input streamable and it shouldn't be");
|
||||||
|
|
||||||
|
TEST_F(TApp, floatWrapper) {
|
||||||
|
objWrapper<float> iWrapper;
|
||||||
|
app.add_option<objWrapper<float>, 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 X> class AobjWrapper {
|
||||||
|
public:
|
||||||
|
AobjWrapper() = default;
|
||||||
|
// delete all other constructors
|
||||||
|
template <class TT> AobjWrapper(TT &&obj) = delete;
|
||||||
|
// single assignment operator
|
||||||
|
void operator=(X val) { val_ = val; }
|
||||||
|
// delete all other assignment operators
|
||||||
|
template <typename TT> void operator=(TT &&obj) = delete;
|
||||||
|
|
||||||
|
const X &value() const { return val_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
X val_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(std::is_assignable<AobjWrapper<uint16_t> &, uint16_t>::value,
|
||||||
|
"AobjWrapper not assignable like it should be ");
|
||||||
|
|
||||||
|
TEST_F(TApp, uint16Wrapper) {
|
||||||
|
AobjWrapper<uint16_t> sWrapper;
|
||||||
|
app.add_option<AobjWrapper<uint16_t>, 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);
|
||||||
|
}
|
||||||
|
@ -3,6 +3,48 @@
|
|||||||
|
|
||||||
#include "app_helper.hpp"
|
#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(<optional>)
|
||||||
|
#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 <boost/version.hpp>
|
||||||
|
#if BOOST_VERSION < 106100
|
||||||
|
#error "This boost::optional version is not supported, use 1.61 or better"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CLI11_STD_OPTIONAL
|
||||||
|
#include <optional>
|
||||||
|
#endif
|
||||||
|
#if CLI11_EXPERIMENTAL_OPTIONAL
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
#if CLI11_BOOST_OPTIONAL
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/optional/optional_io.hpp>
|
||||||
|
#endif
|
||||||
|
// [CLI11:verbatim]
|
||||||
|
|
||||||
#if CLI11_STD_OPTIONAL
|
#if CLI11_STD_OPTIONAL
|
||||||
|
|
||||||
TEST_F(TApp, StdOptionalTest) {
|
TEST_F(TApp, StdOptionalTest) {
|
||||||
@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) {
|
|||||||
run();
|
run();
|
||||||
EXPECT_TRUE(opt);
|
EXPECT_TRUE(opt);
|
||||||
EXPECT_EQ(*opt, 1);
|
EXPECT_EQ(*opt, 1);
|
||||||
|
opt = {};
|
||||||
args = {"--count", "3"};
|
args = {"--count", "3"};
|
||||||
run();
|
run();
|
||||||
EXPECT_TRUE(opt);
|
EXPECT_TRUE(opt);
|
||||||
EXPECT_EQ(*opt, 3);
|
EXPECT_EQ(*opt, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(TApp, BoostOptionalint64Test) {
|
||||||
|
boost::optional<int64_t> 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<std::string> 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<eval> opt;
|
||||||
|
auto optptr = app.add_option<decltype(opt), eval>("-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) {
|
TEST_F(TApp, BoostOptionalVector) {
|
||||||
boost::optional<std::vector<int>> opt;
|
boost::optional<std::vector<int>> opt;
|
||||||
app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
|
app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
|
||||||
|
@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#if defined(CLI11_CPP17)
|
||||||
|
#if defined(__has_include)
|
||||||
|
#if __has_include(<string_view>)
|
||||||
|
#include <string_view>
|
||||||
|
#define CLI11_HAS_STRING_VIEW
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_F(TApp, SimpleTransform) {
|
TEST_F(TApp, SimpleTransform) {
|
||||||
int value;
|
int value;
|
||||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
|
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
|
||||||
@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) {
|
|||||||
run();
|
run();
|
||||||
EXPECT_EQ(value, "mapped");
|
EXPECT_EQ(value, "mapped");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_F(TApp, SimpleNumericalTransformFn) {
|
TEST_F(TApp, SimpleNumericalTransformFn) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user