From 0a7adfb03787b706ed992e288094003cae0ce9f9 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Fri, 17 Jan 2020 07:58:23 +1100 Subject: [PATCH] cmdopt: add simple `requires` constraint callbacks --- cmdopt.cpp | 32 +++++++++++++++++++++++++---- cmdopt.hpp | 45 ++++++++++++++++++++++++++++++++++++++--- test/cmdopt.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/cmdopt.cpp b/cmdopt.cpp index 33e9c993..c7da027e 100644 --- a/cmdopt.cpp +++ b/cmdopt.cpp @@ -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 _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) diff --git a/cmdopt.hpp b/cmdopt.hpp index abc31f5b..ca750958 100644 --- a/cmdopt.hpp +++ b/cmdopt.hpp @@ -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); + /// 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 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 + std::function + is (TargetT &&_target)& + { + return [&, target = std::forward(_target)] (void) { + return seen () && m_data == target; + }; + } + const std::string& example (void) const override { return detail::value_example (); diff --git a/test/cmdopt.cpp b/test/cmdopt.cpp index 5caf0175..ce8bb883 100644 --- a/test/cmdopt.cpp +++ b/test/cmdopt.cpp @@ -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> ('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> ('a', "ay", "int value", a_val); + auto &b_opt = p.add> ('b', "be", "int value", b_val); + + b_opt.required(a_opt.is (1)); + + static constexpr char const* argv[] = { "./cpptest", "-a", "1"}; + tap.expect_throw ([&] () { + 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> ('a', "ay", "int value", a_val); + auto &b_opt = p.add> ('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 (tap); test_bytes (tap); test_required (tap); + test_fulfilled (tap); test_positional (tap); }); }