mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
* 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
448 lines
12 KiB
C++
448 lines
12 KiB
C++
#include "app_helper.hpp"
|
|
#include "gmock/gmock.h"
|
|
#include <complex>
|
|
|
|
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, DefaultComplex) {
|
|
|
|
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());
|
|
}
|
|
|
|
TEST_F(TApp, BuiltinComplex) {
|
|
cx comp{1, 2};
|
|
app.add_complex("-c,--complex", comp, "", true);
|
|
|
|
args = {"-c", "4", "3"};
|
|
|
|
std::string help = app.help();
|
|
EXPECT_THAT(help, HasSubstr("1"));
|
|
EXPECT_THAT(help, HasSubstr("2"));
|
|
EXPECT_THAT(help, HasSubstr("COMPLEX"));
|
|
|
|
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());
|
|
}
|
|
|
|
TEST_F(TApp, BuiltinComplexWithDelimiter) {
|
|
cx comp{1, 2};
|
|
app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
|
|
|
|
args = {"-c", "4+3i"};
|
|
|
|
std::string help = app.help();
|
|
EXPECT_THAT(help, HasSubstr("1"));
|
|
EXPECT_THAT(help, HasSubstr("2"));
|
|
EXPECT_THAT(help, HasSubstr("COMPLEX"));
|
|
|
|
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());
|
|
|
|
args = {"-c", "5+-3i"};
|
|
run();
|
|
|
|
EXPECT_DOUBLE_EQ(5, comp.real());
|
|
EXPECT_DOUBLE_EQ(-3, comp.imag());
|
|
|
|
args = {"-c", "6", "-4i"};
|
|
run();
|
|
|
|
EXPECT_DOUBLE_EQ(6, comp.real());
|
|
EXPECT_DOUBLE_EQ(-4, comp.imag());
|
|
}
|
|
|
|
TEST_F(TApp, BuiltinComplexIgnoreI) {
|
|
cx comp{1, 2};
|
|
app.add_complex("-c,--complex", comp);
|
|
|
|
args = {"-c", "4", "3i"};
|
|
|
|
run();
|
|
|
|
EXPECT_DOUBLE_EQ(4, comp.real());
|
|
EXPECT_DOUBLE_EQ(3, comp.imag());
|
|
}
|
|
|
|
TEST_F(TApp, BuiltinComplexFail) {
|
|
cx comp{1, 2};
|
|
app.add_complex("-c,--complex", comp);
|
|
|
|
args = {"-c", "4"};
|
|
|
|
EXPECT_THROW(run(), CLI::ArgumentMismatch);
|
|
}
|
|
|
|
// an example of custom converter that can be used to add new parsing options
|
|
// On MSVC and possibly some other new compilers this can be a free standing function without the template
|
|
// specialization but this is compiler dependent
|
|
namespace CLI {
|
|
namespace detail {
|
|
|
|
template <>
|
|
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(':');
|
|
if((sep == std::string::npos) && (sep > 0)) {
|
|
return false;
|
|
}
|
|
output = {input.substr(0, sep), input.substr(sep + 1)};
|
|
return true;
|
|
}
|
|
} // namespace detail
|
|
} // namespace CLI
|
|
|
|
TEST_F(TApp, custom_string_converter) {
|
|
std::pair<std::string, std::string> val;
|
|
app.add_option("-d,--dual_string", val);
|
|
|
|
args = {"-d", "string1:string2"};
|
|
|
|
run();
|
|
EXPECT_EQ(val.first, "string1");
|
|
EXPECT_EQ(val.second, "string2");
|
|
}
|
|
|
|
TEST_F(TApp, custom_string_converterFail) {
|
|
std::pair<std::string, std::string> val;
|
|
app.add_option("-d,--dual_string", val);
|
|
|
|
args = {"-d", "string2"};
|
|
|
|
EXPECT_THROW(run(), CLI::ConversionError);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
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
|
|
|
|
/// 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);
|
|
}
|