1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00
CLI11/tests/ComplexTypeTest.cpp
Philip Top 27da2f952e
Container options (#423)
* Update options.md book chapter and the readme to better reflect current usage and the modifications to the add_options templates.

add support in add_option for wrapper types, such as std::optional, boost::optional or other types with a value_type trait.  Add support for generalized containers beyond vector,  add support for nested tuples and vectors, and complex numbers directly in add_option.  This includes several new type traits and object categories.

Upgrade the google test version to better support templated tests.

add support for vector argument separator `%%`

* update formatting to match recent changes

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
2020-03-22 14:06:34 -04:00

186 lines
5.4 KiB
C++

#include "app_helper.hpp"
#include "gmock/gmock.h"
#include <complex>
#include <cstdint>
using ::testing::HasSubstr;
using cx = std::complex<double>;
CLI::Option *
add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
double x, y;
bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
if(worked)
variable = cx(x, y);
return worked;
};
CLI::Option *opt = app.add_option(name, fun, description, defaulted);
opt->type_name("COMPLEX")->type_size(2);
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
TEST_F(TApp, AddingComplexParser) {
cx comp{0, 0};
add_option(app, "-c,--complex", comp);
args = {"-c", "1.5", "2.5"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
}
TEST_F(TApp, DefaultedComplex) {
cx comp{1, 2};
add_option(app, "-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
}
// an example of custom complex number converter that can be used to add new parsing options
#if defined(__has_include)
#if __has_include(<regex>)
// an example of custom converter that can be used to add new parsing options
#define HAS_REGEX_INCLUDE
#endif
#endif
#ifdef HAS_REGEX_INCLUDE
// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
// simplify compilation and prevent a much more complicated #if expression
#include <regex>
namespace CLI {
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::complex<double>>(const std::string &input, std::complex<double> &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]*)");
std::smatch m;
double x{0.0}, y{0.0};
bool worked;
std::regex_search(input, m, creg);
if(m.size() == 9) {
worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
if(worked) {
if(*m[5].first == '-') {
y = -y;
}
}
} else {
if((input.back() == 'j') || (input.back() == 'i')) {
auto strval = input.substr(0, input.size() - 1);
CLI::detail::trim(strval);
worked = CLI::detail::lexical_cast(strval, y);
} else {
std::string ival = input;
CLI::detail::trim(ival);
worked = CLI::detail::lexical_cast(ival, x);
}
}
if(worked) {
output = cx{x, y};
}
return worked;
}
} // namespace detail
} // namespace CLI
TEST_F(TApp, AddingComplexParserDetail) {
bool skip_tests = false;
try { // check if the library actually supports regex, it is possible to link against a non working regex in the
// standard library
std::smatch m;
std::string input = "1.5+2.5j";
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
auto rsearch = std::regex_search(input, m, creg);
if(!rsearch) {
skip_tests = true;
} else {
EXPECT_EQ(m.size(), 9u);
}
} catch(...) {
skip_tests = true;
}
static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
if(!skip_tests) {
cx comp{0, 0};
app.add_option("-c,--complex", comp, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
args = {"-c", "1.5-2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(-2.5, comp.imag());
}
}
#endif
// defining a new complex class
class complex_new {
public:
complex_new() = default;
complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
double real() { return val1_; }
double imag() { return val2_; }
private:
double val1_{0.0};
double val2_{0.0};
};
TEST_F(TApp, newComplex) {
complex_new cval;
static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
"complex new does not result in complex number categorization");
app.add_option("-c,--complex", cval, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, cval.real());
EXPECT_DOUBLE_EQ(2.5, cval.imag());
args = {"-c", "1.5-2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, cval.real());
EXPECT_DOUBLE_EQ(-2.5, cval.imag());
}