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:
virtual ~Value() = default;
virtual
std::shared_ptr<Value>
clone() const = 0;
@ -273,9 +275,6 @@ namespace cxxopts
virtual void
parse() const = 0;
virtual bool
has_arg() const = 0;
virtual bool
has_default() const = 0;
@ -296,6 +295,9 @@ namespace cxxopts
virtual std::shared_ptr<Value>
implicit_value(const std::string& value) = 0;
virtual bool
is_boolean() const = 0;
};
class OptionException : public std::exception
@ -394,7 +396,7 @@ namespace cxxopts
)
: OptionParseException(
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"
)
{
@ -441,6 +443,10 @@ namespace cxxopts
{
std::basic_regex<char> integer_pattern
("(-)?(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
@ -641,11 +647,25 @@ namespace cxxopts
inline
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
//so that we can write --long=yes explicitly
value = true;
std::smatch result;
std::regex_match(text, result, truthy_pattern);
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
@ -673,18 +693,6 @@ namespace cxxopts
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>
struct type_is_container
{
@ -698,42 +706,40 @@ namespace cxxopts
};
template <typename T>
class standard_value : public Value
class abstract_value : public Value
{
using Self = standard_value<T>;
using Self = abstract_value<T>;
public:
standard_value()
abstract_value()
: m_result(std::make_shared<T>())
, m_store(m_result.get())
{
}
standard_value(T* t)
abstract_value(T* t)
: m_store(t)
{
}
std::shared_ptr<Value>
clone() const
{
std::shared_ptr<Self> copy;
virtual ~abstract_value() = default;
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
{
copy = std::make_shared<Self>(m_store);
m_store = rhs.m_store;
}
copy->m_default = m_default;
copy->m_implicit = m_implicit;
copy->m_default_value = m_default_value;
copy->m_implicit_value = m_implicit_value;
return copy;
m_default = rhs.m_default;
m_implicit = rhs.m_implicit;
m_default_value = rhs.m_default_value;
m_implicit_value = rhs.m_implicit_value;
}
void
@ -754,12 +760,6 @@ namespace cxxopts
parse_value(m_default_value, *m_store);
}
bool
has_arg() const
{
return value_has_arg<T>::value;
}
bool
has_default() const
{
@ -800,6 +800,12 @@ namespace cxxopts
return m_implicit_value;
}
bool
is_boolean() const
{
return std::is_same<T, bool>::value;
}
const T&
get() const
{
@ -823,6 +829,52 @@ namespace cxxopts
std::string m_default_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>
@ -874,12 +926,6 @@ namespace cxxopts
return m_desc;
}
bool
has_arg() const
{
return m_value->has_arg();
}
const Value& value() const {
return *m_value;
}
@ -915,13 +961,13 @@ namespace cxxopts
std::string s;
std::string l;
String desc;
bool has_arg;
bool has_default;
std::string default_value;
bool has_implicit;
std::string implicit_value;
std::string arg_help;
bool is_container;
bool is_boolean;
};
struct HelpGroupDetails
@ -1266,10 +1312,10 @@ namespace cxxopts
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)
{
result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
@ -1616,27 +1662,19 @@ ParseResult::parse(int& argc, char**& argv)
auto value = iter->second;
//if no argument then just add it
if (!value->has_arg())
if (i + 1 == s.size())
{
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
{
//it must be the last argument
if (i + 1 == s.size())
{
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);
}
//error
throw option_requires_argument_exception(name);
}
}
}
@ -1658,26 +1696,12 @@ ParseResult::parse(int& argc, char**& argv)
{
//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]);
}
else
{
if (opt->has_arg())
{
//parse the next argument
checked_parse_arg(argc, argv, current, opt, name);
}
else
{
//parse with empty argument
parse_option(opt, name);
}
//parse the next argument
checked_parse_arg(argc, argv, current, opt, name);
}
}
@ -1749,11 +1773,11 @@ Options::add_option
auto& options = m_help[group];
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
value->has_arg(),
value->has_default(), value->get_default_value(),
value->has_implicit(), value->get_implicit_value(),
std::move(arg_help),
value->is_container()});
value->is_container(),
value->is_boolean()});
}
inline

View File

@ -86,7 +86,7 @@ TEST_CASE("Basic options", "[options]")
auto& arguments = result.arguments();
REQUIRE(arguments.size() == 7);
CHECK(arguments[0].key() == "long");
CHECK(arguments[0].value() == "");
CHECK(arguments[0].value() == "true");
CHECK(arguments[0].as<bool>() == true);
CHECK(arguments[1].key() == "short");
@ -402,3 +402,26 @@ TEST_CASE("Floats", "[options]")
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);
}