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:
Jarryd Beck 2017-11-15 17:51:38 +11:00
parent 8d6a91ee27
commit 6c9bae4a07
2 changed files with 136 additions and 89 deletions

View File

@ -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

View File

@ -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);
}