Skip to content

Commit

Permalink
#317 Enable/disable nodes at runtime
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
cmannett85 committed May 8, 2023
1 parent 1c9583e commit 0ca2107
Show file tree
Hide file tree
Showing 14 changed files with 610 additions and 35 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ path_prefixer(HEADERS
include/arg_router/policy/program_version.hpp
include/arg_router/policy/required.hpp
include/arg_router/policy/router.hpp
include/arg_router/policy/runtime_enable.hpp
include/arg_router/policy/short_form_expander.hpp
include/arg_router/policy/short_name.hpp
include/arg_router/policy/token_end_marker.hpp
Expand Down
1 change: 1 addition & 0 deletions cmake/build_types/unit_test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ path_prefixer(TEST_SRCS
policy/min_max_value_t_test.cpp
policy/required_test.cpp
policy/router_test.cpp
policy/runtime_enable_test.cpp
policy/short_form_expander_test.cpp
policy/short_name_test.cpp
policy/token_end_marker_test.cpp
Expand Down
1 change: 0 additions & 1 deletion include/arg_router/arg_router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include "arg_router/multi_lang/string_selector.hpp"
#include "arg_router/policy/colour_help_formatter.hpp"
#include "arg_router/policy/custom_parser.hpp"
#include "arg_router/policy/dependent.hpp"
#include "arg_router/policy/description.hpp"
#include "arg_router/policy/min_max_value.hpp"
#include "arg_router/policy/token_end_marker.hpp"
Expand Down
6 changes: 6 additions & 0 deletions include/arg_router/help.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ class help_t :
const Node& node,
const TargetFn& fn)
{
if constexpr (traits::has_runtime_enabled_method_v<Node>) {
if (!node.runtime_enabled()) {
throw multi_lang_exception{error_code::unknown_argument, tokens.front()};
}
}

if (tokens.empty()) {
fn(node);
return;
Expand Down
5 changes: 2 additions & 3 deletions include/arg_router/mode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,11 @@ class mode_t : public tree_node<policy::no_result_value<>, std::decay_t<Params>.
"Anonymous mode cannot have a child mode");

static_assert(!parent_type::template any_phases_v<value_type,
policy::has_pre_parse_phase_method,
policy::has_parse_phase_method,
policy::has_validation_phase_method,
policy::has_missing_phase_method>,
"Mode does not support policies with pre-parse, parse, validation, "
"or missing phases; as it delegates those to its children");
"Mode does not support policies with parse, validation, or missing phases; as it "
"delegates those to its children");

template <typename Child>
struct child_has_routing_phase {
Expand Down
2 changes: 0 additions & 2 deletions include/arg_router/policy/default_value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
#include "arg_router/config.hpp"
#include "arg_router/policy/policy.hpp"

#include <optional>

namespace arg_router::policy
{
/** Provides a default value for non-required arguments.
Expand Down
127 changes: 127 additions & 0 deletions include/arg_router/policy/runtime_enable.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (C) 2023 by Camden Mannett.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include "arg_router/parsing/parsing.hpp"
#include "arg_router/policy/required.hpp"

namespace arg_router::policy
{
/** Policy that allows a node to be ignored during the parse phase depending upon it's runtime
* constructor argument.
*
* This policy allows nodes or entire modes of a parse tree to be disabled, for example a feature
* may not be available on a particular application license type - this policy can hide the feature
* from the user.
*
* This policy does not affect the arguments dispatched to appropriate router, so values associated
* with disabled nodes come from an attached policy::default_value or a default constructed
* instance if no policy::default_value is attached.
*/
template <typename T = void>
class runtime_enable
{
public:
/** Policy priority. */
constexpr static auto priority = std::size_t{800};

/** Constructor.
*
* @param enable True to enable the node
*/
explicit runtime_enable(bool enable) noexcept : enabled_{enable} {}

/** @return Enabled state */
[[nodiscard]] bool runtime_enabled() const noexcept { return enabled_; }

/** Calls the enabled_type function object and skips further processing for the token parsing if
* disabled.
*
* @tparam ProcessedTarget @a processed_target payload type
* @tparam Parents Pack of parent tree nodes in ascending ancestry order
* @param tokens Currently processed tokens
* @param processed_target Previously processed parse_target of parent node, or empty is there
* is no non-root parent
* @param target Pre-parse generated target
* @param parents Parent node instances
* @return parsing::pre_parse_action::valid_node if enabled, otherwise
* parsing::pre_parse_action::skip_node
*/
template <typename ProcessedTarget, typename... Parents>
[[nodiscard]] parsing::pre_parse_result pre_parse_phase(
[[maybe_unused]] parsing::dynamic_token_adapter& tokens,
[[maybe_unused]] utility::compile_time_optional<ProcessedTarget> processed_target,
[[maybe_unused]] parsing::parse_target& target,
[[maybe_unused]] const Parents&... parents) const
{
static_assert(sizeof...(Parents) >= 1, "Runtime enable requires at least 1 parent");
using node_type = boost::mp11::mp_first<std::tuple<Parents...>>;
static_assert(!policy::is_required_v<node_type>,
"Runtime enable must not be used with policy::required");

return runtime_enabled() ? parsing::pre_parse_action::valid_node
: parsing::pre_parse_action::skip_node;
}

protected:
bool enabled_;
};

template <typename T>
struct is_policy<runtime_enable<T>> : std::true_type {
};

template <typename T>
class runtime_enable_required : public runtime_enable<T>
{
public:
using runtime_enable<T>::pre_parse_phase;

/** Alias of @a T. */
using value_type = T;

/** Constructor.
*
* This value is used as the parsed value when not enabled. This is only required when the
* owning node has a policy::required attached, as it prevents requirement failures from
* occuring when the node is disabled.
* @param enable True to enable the node
* @param default_value Default parsed value when not enabled
*/
explicit runtime_enable_required(bool enable, T default_value = {}) noexcept :
runtime_enable<T>{enable}, default_value_{std::move(default_value)}
{
}

/** Throw an error if the owning node is enabled, otherwise returns the default value.
*
* @tparam ValueType Parsed value type, not used in this method
* @tparam Parents Pack of parent tree nodes in ascending ancestry order
* @param parents Parents instances pack, not used in this method
* @return Default value
* @exception multi_lang_exception Thrown if the owning node is enabled
*/
template <typename ValueType, typename... Parents>
[[nodiscard]] ValueType missing_phase([[maybe_unused]] const Parents&... parents) const
{
if (this->enabled_) {
static_assert(sizeof...(Parents) >= 1, "Runtime enable requires at least 1 parent");
using node_type = boost::mp11::mp_first<std::tuple<Parents...>>;

throw multi_lang_exception{error_code::missing_required_argument,
parsing::node_token_type<node_type>()};
}

return default_value_;
}

private:
value_type default_value_;
};

template <typename T>
struct is_policy<runtime_enable_required<T>> : std::true_type {
};
} // namespace arg_router::policy
15 changes: 12 additions & 3 deletions include/arg_router/policy/validator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
#include "arg_router/multi_arg.hpp"
#include "arg_router/policy/alias.hpp"
#include "arg_router/policy/default_value.hpp"
#include "arg_router/policy/dependent.hpp"
#include "arg_router/policy/display_name.hpp"
#include "arg_router/policy/long_name.hpp"
#include "arg_router/policy/none_name.hpp"
#include "arg_router/policy/required.hpp"
#include "arg_router/policy/router.hpp"
#include "arg_router/policy/runtime_enable.hpp"
#include "arg_router/policy/short_form_expander.hpp"
#include "arg_router/policy/short_name.hpp"
#include "arg_router/positional_arg.hpp"
Expand Down Expand Up @@ -697,7 +699,10 @@ constexpr auto default_validator = validator<
// Mode
rule_q<common_rules::despecialised_any_of_rule<mode_t>,
must_not_have_policies<policy::multi_stage_value, //
policy::required_t>,
policy::required_t,
policy::runtime_enable_required,
policy::alias_t,
policy::dependent_t>,
node_types_must_be_at_end<positional_arg_t>,
list_like_nodes_must_have_fixed_count_if_not_at_end,
parent_types<parent_index_pair_type<0, root_t>, parent_index_pair_type<0, mode_t>>>,
Expand All @@ -706,12 +711,16 @@ constexpr auto default_validator = validator<
must_not_have_policies<policy::multi_stage_value,
policy::required_t,
policy::short_form_expander_t,
policy::validation::validator>,
policy::validation::validator,
policy::runtime_enable>,
parent_types<parent_index_pair_type<0, root_t>>>,
// Root
rule_q<common_rules::despecialised_any_of_rule<root_t>,
must_have_policies<policy::validation::validator>,
must_not_have_policies<policy::multi_stage_value, policy::no_result_value>,
must_not_have_policies<policy::multi_stage_value,
policy::no_result_value,
policy::runtime_enable,
policy::runtime_enable_required>,
child_must_not_have_policy<policy::required_t>,
child_must_not_have_policy<policy::alias_t>,
single_anonymous_mode<arg_router::mode_t>,
Expand Down
20 changes: 20 additions & 0 deletions include/arg_router/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,26 @@ struct has_generate_help_method {
template <typename T>
constexpr bool has_generate_help_method_v = has_generate_help_method<T>::value;

/** Determine if a type has a <TT>runtime_enabled()</TT> method.
*
* @tparam T Type to query
*/
template<typename T>
struct has_runtime_enabled_method
{
template<typename U>
using type = decltype(std::declval<const U &>().runtime_enabled());

constexpr static bool value = boost::mp11::mp_valid<type, T>::value;
};

/** Helper variable for has_runtime_enabled_method.
*
* @tparam T Type to query
*/
template<typename T>
constexpr bool has_runtime_enabled_method_v = has_runtime_enabled_method<T>::value;

/** Determine if a type has a <TT>translate_exception</TT> method.
*
* @tparam T Type to query
Expand Down
31 changes: 6 additions & 25 deletions test/mode_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1405,25 +1405,6 @@ int main() {
R"(
#include "arg_router/flag.hpp"
#include "arg_router/mode.hpp"
#include "arg_router/policy/alias.hpp"
#include "arg_router/policy/long_name.hpp"
#include "arg_router/utility/compile_time_string.hpp"
using namespace arg_router;
int main() {
const auto m = mode(policy::alias(policy::long_name<AR_STRING("other-mode")>),
flag(policy::long_name<AR_STRING("hello")>));
return 0;
}
)",
"Mode does not support policies with pre-parse, parse, validation, "
"or missing phases; as it delegates those to its children",
"pre_parse_phase_test"},
{
R"(
#include "arg_router/flag.hpp"
#include "arg_router/mode.hpp"
#include "arg_router/policy/custom_parser.hpp"
#include "arg_router/policy/long_name.hpp"
#include "arg_router/utility/compile_time_string.hpp"
Expand All @@ -1436,8 +1417,8 @@ int main() {
return 0;
}
)",
"Mode does not support policies with pre-parse, parse, validation, "
"or missing phases; as it delegates those to its children",
"Mode does not support policies with parse, validation, or missing phases; as it "
"delegates those to its children",
"parse_phase_test"},
{
R"(
Expand All @@ -1455,8 +1436,8 @@ int main() {
return 0;
}
)",
"Mode does not support policies with pre-parse, parse, validation, "
"or missing phases; as it delegates those to its children",
"Mode does not support policies with parse, validation, or missing phases; as it "
"delegates those to its children",
"validation_phase_test"},
{
R"(
Expand All @@ -1474,8 +1455,8 @@ int main() {
return 0;
}
)",
"Mode does not support policies with pre-parse, parse, validation, "
"or missing phases; as it delegates those to its children",
"Mode does not support policies with parse, validation, or missing phases; as it "
"delegates those to its children",
"missing_phase_test"},
{
R"(
Expand Down
2 changes: 1 addition & 1 deletion test/policy/required_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(missing_phase_test)

BOOST_AUTO_TEST_SUITE(death_suite)

BOOST_AUTO_TEST_CASE(post_parse_phase_test)
BOOST_AUTO_TEST_CASE(missing_phase_test)
{
test::death_test_compile(
R"(
Expand Down
Loading

0 comments on commit 0ca2107

Please sign in to comment.