cmdopt: add simple `requires` constraint callbacks

This commit is contained in:
Danny Robson 2020-01-17 07:58:23 +11:00
parent ac168e86b5
commit 0a7adfb037
3 changed files with 123 additions and 7 deletions

View File

@ -20,6 +20,12 @@ using namespace cruft::cmdopt::option;
///////////////////////////////////////////////////////////////////////////////
base::base ()
: m_required ([] () { return false; })
{ ; }
//-----------------------------------------------------------------------------
base::~base ()
{ ; }
@ -52,24 +58,32 @@ base::start (void)
void
base::finish (void)
{
if (m_required && !m_seen)
if (!fulfilled ())
throw invalid_required ();
}
//-----------------------------------------------------------------------------
void
base::required (std::function<bool(void)> _required)
{
m_required = std::move (_required);
}
//-----------------------------------------------------------------------------
bool
base::required (void) const
{
return m_required;
return m_required ();
}
//-----------------------------------------------------------------------------
bool
void
base::required (bool _required)
{
return m_required = _required;
m_required = [_required] (void) { return _required; };
}
@ -89,6 +103,16 @@ base::seen (bool _seen)
}
//-----------------------------------------------------------------------------
bool
base::fulfilled (void) const
{
if (!m_required ())
return true;
return seen ();
}
///////////////////////////////////////////////////////////////////////////////
void
null::execute (void)

View File

@ -72,14 +72,17 @@ namespace cruft::cmdopt {
const int m_index;
};
namespace option { class base; }
///////////////////////////////////////////////////////////////////////////
namespace option {
class base {
public:
base ();
// we deal almost exclusively with vtables, so disable copying
// just in case we do something stupid.
base () = default;
base (const base&) = delete;
base& operator= (const base&) = delete;
@ -92,14 +95,32 @@ namespace cruft::cmdopt {
virtual const std::string& example (void) const = 0;
/// Sets a callback query to determine if the option is required.
///
/// The default is `return false;`
void required (std::function<bool(void)>);
/// Sets a constant value function for the required query.
/// The value provided will be unconditionally returned.
void required (bool);
/// Tests if the option is required by invoking the stored query.
bool required (void) const;
bool required (bool);
/// Tests if the option has been seen.
///
/// Not to be confused with `fulfilled` which describes whether
/// the `seen` and `required` constraints are both satisfied.
bool seen (void) const;
/// Sets the seen flag of the option to the value provided.
bool seen (bool);
/// Tests if all `required` and `seen` constraints have been
/// satisfied.
///
/// If there are no `required` constraints it return true.
bool fulfilled (void) const;
private:
bool m_required = false;
std::function<bool(void)> m_required;
bool m_seen = false;
};
@ -184,6 +205,24 @@ namespace cruft::cmdopt {
seen (true);
}
/// Returns a callback that tests if this option has been set to
/// a specified value.
///
/// If the option has not been seen it is treated as
/// unconditionally not equal.
///
/// The returned function is handy to use in the `required`
/// clauses of dependent options.
template <typename TargetT>
std::function<bool(void)>
is (TargetT &&_target)&
{
return [&, target = std::forward<TargetT>(_target)] (void) {
return seen () && m_data == target;
};
}
const std::string& example (void) const override
{
return detail::value_example<T> ();

View File

@ -220,6 +220,58 @@ test_required (cruft::TAP::logger &tap)
}
///////////////////////////////////////////////////////////////////////////////
static
void
test_fulfilled (cruft::TAP::logger &tap)
{
// Test that a value option that has no requirements/seen constraints and no provided value is fullfilled.
{
cruft::cmdopt::parser p;
bool b_val;
auto &b_opt = p.add<cruft::cmdopt::option::value<bool>> ('b', "bool", "boolean value", b_val);
static constexpr char const* argv[] = { "./cpptest", };
tap.expect_nothrow ([&] () {
p.scan (std::size (argv), argv);
}, "fullfilled, empty, scan");
tap.expect (b_opt.fulfilled (), "fulfilled, empty, value");
}
// Test that an unseen dependent option means the option is unfulfilled.
{
cruft::cmdopt::parser p;
int a_val = 0, b_val = 0;
auto &a_opt = p.add<cruft::cmdopt::option::value<int>> ('a', "ay", "int value", a_val);
auto &b_opt = p.add<cruft::cmdopt::option::value<int>> ('b', "be", "int value", b_val);
b_opt.required(a_opt.is (1));
static constexpr char const* argv[] = { "./cpptest", "-a", "1"};
tap.expect_throw<cruft::cmdopt::invalid_required> ([&] () {
p.scan (std::size (argv), argv);
}, "fullfilled, unseen dependent, scan");
}
// Test that a seen dependent opion means the option is fulfilled.
{
cruft::cmdopt::parser p;
int a_val = 0, b_val = 0;
auto &a_opt = p.add<cruft::cmdopt::option::value<int>> ('a', "ay", "int value", a_val);
auto &b_opt = p.add<cruft::cmdopt::option::value<int>> ('b', "be", "int value", b_val);
b_opt.required(a_opt.is (1));
static constexpr char const* argv[] = { "./cpptest", "-a", "1", "-b", "10"};
tap.expect_nothrow ([&] () {
p.scan (std::size (argv), argv);
}, "fullfilled, unseen dependent, scan");
tap.expect_eq (a_val, 1, "fulfilled, dependent value");
tap.expect_eq (b_val, 10, "fulfilled, dependent value");
}
}
///////////////////////////////////////////////////////////////////////////////
static
void
@ -260,6 +312,7 @@ main (int, char **) {
test_numeric<uint64_t> (tap);
test_bytes (tap);
test_required (tap);
test_fulfilled (tap);
test_positional (tap);
});
}