Parse strings for booleans
Fixes #54. Allow default and implicit values for booleans with multiple boolean strings matched as values.
This commit is contained in:
parent
8d6a91ee27
commit
6c9bae4a07
@ -263,6 +263,8 @@ namespace cxxopts
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual ~Value() = default;
|
||||||
|
|
||||||
virtual
|
virtual
|
||||||
std::shared_ptr<Value>
|
std::shared_ptr<Value>
|
||||||
clone() const = 0;
|
clone() const = 0;
|
||||||
@ -273,9 +275,6 @@ namespace cxxopts
|
|||||||
virtual void
|
virtual void
|
||||||
parse() const = 0;
|
parse() const = 0;
|
||||||
|
|
||||||
virtual bool
|
|
||||||
has_arg() const = 0;
|
|
||||||
|
|
||||||
virtual bool
|
virtual bool
|
||||||
has_default() const = 0;
|
has_default() const = 0;
|
||||||
|
|
||||||
@ -296,6 +295,9 @@ namespace cxxopts
|
|||||||
|
|
||||||
virtual std::shared_ptr<Value>
|
virtual std::shared_ptr<Value>
|
||||||
implicit_value(const std::string& value) = 0;
|
implicit_value(const std::string& value) = 0;
|
||||||
|
|
||||||
|
virtual bool
|
||||||
|
is_boolean() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OptionException : public std::exception
|
class OptionException : public std::exception
|
||||||
@ -394,7 +396,7 @@ namespace cxxopts
|
|||||||
)
|
)
|
||||||
: OptionParseException(
|
: OptionParseException(
|
||||||
u8"Option " + LQUOTE + option + RQUOTE +
|
u8"Option " + LQUOTE + option + RQUOTE +
|
||||||
u8" does not take an argument, but argument" +
|
u8" does not take an argument, but argument " +
|
||||||
LQUOTE + arg + RQUOTE + " given"
|
LQUOTE + arg + RQUOTE + " given"
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -441,6 +443,10 @@ namespace cxxopts
|
|||||||
{
|
{
|
||||||
std::basic_regex<char> integer_pattern
|
std::basic_regex<char> integer_pattern
|
||||||
("(-)?(0x)?([1-9a-zA-Z][0-9a-zA-Z]*)|(0)");
|
("(-)?(0x)?([1-9a-zA-Z][0-9a-zA-Z]*)|(0)");
|
||||||
|
std::basic_regex<char> truthy_pattern
|
||||||
|
("t|true|T|True");
|
||||||
|
std::basic_regex<char> falsy_pattern
|
||||||
|
("(f|false|F|False)?");
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail
|
namespace detail
|
||||||
@ -641,11 +647,25 @@ namespace cxxopts
|
|||||||
|
|
||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
parse_value(const std::string& /*text*/, bool& value)
|
parse_value(const std::string& text, bool& value)
|
||||||
{
|
{
|
||||||
//TODO recognise on, off, yes, no, enable, disable
|
std::smatch result;
|
||||||
//so that we can write --long=yes explicitly
|
std::regex_match(text, result, truthy_pattern);
|
||||||
value = true;
|
|
||||||
|
if (!result.empty())
|
||||||
|
{
|
||||||
|
value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::regex_match(text, result, falsy_pattern);
|
||||||
|
if (!result.empty())
|
||||||
|
{
|
||||||
|
value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw argument_incorrect_type(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline
|
||||||
@ -673,18 +693,6 @@ namespace cxxopts
|
|||||||
value.push_back(v);
|
value.push_back(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct value_has_arg
|
|
||||||
{
|
|
||||||
static constexpr bool value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct value_has_arg<bool>
|
|
||||||
{
|
|
||||||
static constexpr bool value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct type_is_container
|
struct type_is_container
|
||||||
{
|
{
|
||||||
@ -698,42 +706,40 @@ namespace cxxopts
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class standard_value : public Value
|
class abstract_value : public Value
|
||||||
{
|
{
|
||||||
using Self = standard_value<T>;
|
using Self = abstract_value<T>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
standard_value()
|
abstract_value()
|
||||||
: m_result(std::make_shared<T>())
|
: m_result(std::make_shared<T>())
|
||||||
, m_store(m_result.get())
|
, m_store(m_result.get())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
standard_value(T* t)
|
abstract_value(T* t)
|
||||||
: m_store(t)
|
: m_store(t)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Value>
|
virtual ~abstract_value() = default;
|
||||||
clone() const
|
|
||||||
{
|
|
||||||
std::shared_ptr<Self> copy;
|
|
||||||
|
|
||||||
if (m_result)
|
abstract_value(const abstract_value& rhs)
|
||||||
|
{
|
||||||
|
if (rhs.m_result)
|
||||||
{
|
{
|
||||||
copy = std::make_shared<Self>();
|
m_result = std::make_shared<T>();
|
||||||
|
m_store = m_result.get();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
copy = std::make_shared<Self>(m_store);
|
m_store = rhs.m_store;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy->m_default = m_default;
|
m_default = rhs.m_default;
|
||||||
copy->m_implicit = m_implicit;
|
m_implicit = rhs.m_implicit;
|
||||||
copy->m_default_value = m_default_value;
|
m_default_value = rhs.m_default_value;
|
||||||
copy->m_implicit_value = m_implicit_value;
|
m_implicit_value = rhs.m_implicit_value;
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -754,12 +760,6 @@ namespace cxxopts
|
|||||||
parse_value(m_default_value, *m_store);
|
parse_value(m_default_value, *m_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
has_arg() const
|
|
||||||
{
|
|
||||||
return value_has_arg<T>::value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
has_default() const
|
has_default() const
|
||||||
{
|
{
|
||||||
@ -800,6 +800,12 @@ namespace cxxopts
|
|||||||
return m_implicit_value;
|
return m_implicit_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_boolean() const
|
||||||
|
{
|
||||||
|
return std::is_same<T, bool>::value;
|
||||||
|
}
|
||||||
|
|
||||||
const T&
|
const T&
|
||||||
get() const
|
get() const
|
||||||
{
|
{
|
||||||
@ -823,6 +829,52 @@ namespace cxxopts
|
|||||||
std::string m_default_value;
|
std::string m_default_value;
|
||||||
std::string m_implicit_value;
|
std::string m_implicit_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class standard_value : public abstract_value<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using abstract_value<T>::abstract_value;
|
||||||
|
|
||||||
|
std::shared_ptr<Value>
|
||||||
|
clone() const
|
||||||
|
{
|
||||||
|
return std::make_shared<standard_value<T>>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class standard_value<bool> : public abstract_value<bool>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~standard_value() = default;
|
||||||
|
|
||||||
|
standard_value()
|
||||||
|
{
|
||||||
|
set_implicit();
|
||||||
|
}
|
||||||
|
|
||||||
|
standard_value(bool* b)
|
||||||
|
: abstract_value(b)
|
||||||
|
{
|
||||||
|
set_implicit();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Value>
|
||||||
|
clone() const
|
||||||
|
{
|
||||||
|
return std::make_shared<standard_value<bool>>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void
|
||||||
|
set_implicit()
|
||||||
|
{
|
||||||
|
m_implicit = true;
|
||||||
|
m_implicit_value = "true";
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -874,12 +926,6 @@ namespace cxxopts
|
|||||||
return m_desc;
|
return m_desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
has_arg() const
|
|
||||||
{
|
|
||||||
return m_value->has_arg();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value& value() const {
|
const Value& value() const {
|
||||||
return *m_value;
|
return *m_value;
|
||||||
}
|
}
|
||||||
@ -915,13 +961,13 @@ namespace cxxopts
|
|||||||
std::string s;
|
std::string s;
|
||||||
std::string l;
|
std::string l;
|
||||||
String desc;
|
String desc;
|
||||||
bool has_arg;
|
|
||||||
bool has_default;
|
bool has_default;
|
||||||
std::string default_value;
|
std::string default_value;
|
||||||
bool has_implicit;
|
bool has_implicit;
|
||||||
std::string implicit_value;
|
std::string implicit_value;
|
||||||
std::string arg_help;
|
std::string arg_help;
|
||||||
bool is_container;
|
bool is_container;
|
||||||
|
bool is_boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HelpGroupDetails
|
struct HelpGroupDetails
|
||||||
@ -1266,10 +1312,10 @@ namespace cxxopts
|
|||||||
result += " --" + toLocalString(l);
|
result += " --" + toLocalString(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o.has_arg)
|
auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg";
|
||||||
{
|
|
||||||
auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg";
|
|
||||||
|
|
||||||
|
if (!o.is_boolean)
|
||||||
|
{
|
||||||
if (o.has_implicit)
|
if (o.has_implicit)
|
||||||
{
|
{
|
||||||
result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
|
result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
|
||||||
@ -1616,27 +1662,19 @@ ParseResult::parse(int& argc, char**& argv)
|
|||||||
|
|
||||||
auto value = iter->second;
|
auto value = iter->second;
|
||||||
|
|
||||||
//if no argument then just add it
|
if (i + 1 == s.size())
|
||||||
if (!value->has_arg())
|
|
||||||
{
|
{
|
||||||
parse_option(value, name);
|
//it must be the last argument
|
||||||
|
checked_parse_arg(argc, argv, current, value, name);
|
||||||
|
}
|
||||||
|
else if (value->value().has_implicit())
|
||||||
|
{
|
||||||
|
parse_option(value, name, value->value().get_implicit_value());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//it must be the last argument
|
//error
|
||||||
if (i + 1 == s.size())
|
throw option_requires_argument_exception(name);
|
||||||
{
|
|
||||||
checked_parse_arg(argc, argv, current, value, name);
|
|
||||||
}
|
|
||||||
else if (value->value().has_implicit())
|
|
||||||
{
|
|
||||||
parse_option(value, name, value->value().get_implicit_value());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//error
|
|
||||||
throw option_requires_argument_exception(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1658,26 +1696,12 @@ ParseResult::parse(int& argc, char**& argv)
|
|||||||
{
|
{
|
||||||
//parse the option given
|
//parse the option given
|
||||||
|
|
||||||
//but if it doesn't take an argument, this is an error
|
|
||||||
if (!opt->has_arg())
|
|
||||||
{
|
|
||||||
throw option_not_has_argument_exception(name, result[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_option(opt, name, result[3]);
|
parse_option(opt, name, result[3]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (opt->has_arg())
|
//parse the next argument
|
||||||
{
|
checked_parse_arg(argc, argv, current, opt, name);
|
||||||
//parse the next argument
|
|
||||||
checked_parse_arg(argc, argv, current, opt, name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//parse with empty argument
|
|
||||||
parse_option(opt, name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1749,11 +1773,11 @@ Options::add_option
|
|||||||
auto& options = m_help[group];
|
auto& options = m_help[group];
|
||||||
|
|
||||||
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
|
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
|
||||||
value->has_arg(),
|
|
||||||
value->has_default(), value->get_default_value(),
|
value->has_default(), value->get_default_value(),
|
||||||
value->has_implicit(), value->get_implicit_value(),
|
value->has_implicit(), value->get_implicit_value(),
|
||||||
std::move(arg_help),
|
std::move(arg_help),
|
||||||
value->is_container()});
|
value->is_container(),
|
||||||
|
value->is_boolean()});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline
|
||||||
|
|||||||
@ -86,7 +86,7 @@ TEST_CASE("Basic options", "[options]")
|
|||||||
auto& arguments = result.arguments();
|
auto& arguments = result.arguments();
|
||||||
REQUIRE(arguments.size() == 7);
|
REQUIRE(arguments.size() == 7);
|
||||||
CHECK(arguments[0].key() == "long");
|
CHECK(arguments[0].key() == "long");
|
||||||
CHECK(arguments[0].value() == "");
|
CHECK(arguments[0].value() == "true");
|
||||||
CHECK(arguments[0].as<bool>() == true);
|
CHECK(arguments[0].as<bool>() == true);
|
||||||
|
|
||||||
CHECK(arguments[1].key() == "short");
|
CHECK(arguments[1].key() == "short");
|
||||||
@ -402,3 +402,26 @@ TEST_CASE("Floats", "[options]")
|
|||||||
CHECK(positional[3] == -1.5e6);
|
CHECK(positional[3] == -1.5e6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Booleans", "[boolean]") {
|
||||||
|
cxxopts::Options options("parses_floats", "parses floats correctly");
|
||||||
|
options.add_options()
|
||||||
|
("bool", "A Boolean", cxxopts::value<bool>())
|
||||||
|
("debug", "Debugging", cxxopts::value<bool>())
|
||||||
|
("timing", "Timing", cxxopts::value<bool>())
|
||||||
|
;
|
||||||
|
|
||||||
|
Argv av({"booleans", "--bool=false", "--debug", "true", "--timing"});
|
||||||
|
|
||||||
|
char** argv = av.argv();
|
||||||
|
auto argc = av.argc();
|
||||||
|
|
||||||
|
auto result = options.parse(argc, argv);
|
||||||
|
|
||||||
|
REQUIRE(result.count("bool") == 1);
|
||||||
|
REQUIRE(result.count("debug") == 1);
|
||||||
|
REQUIRE(result.count("timing") == 1);
|
||||||
|
|
||||||
|
CHECK(result["bool"].as<bool>() == false);
|
||||||
|
CHECK(result["debug"].as<bool>() == true);
|
||||||
|
CHECK(result["timing"].as<bool>() == true);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user