arg_router  1.4.0
C++ command line argument parsing and routing
validator.hpp
1 // Copyright (C) 2022-2023 by Camden Mannett.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
4 
5 #pragma once
6 
7 #include "arg_router/arg.hpp"
8 #include "arg_router/counting_flag.hpp"
9 #include "arg_router/dependency/alias_group.hpp"
10 #include "arg_router/dependency/one_of.hpp"
11 #include "arg_router/flag.hpp"
12 #include "arg_router/forwarding_arg.hpp"
13 #include "arg_router/help.hpp"
14 #include "arg_router/mode.hpp"
15 #include "arg_router/multi_arg.hpp"
16 #include "arg_router/policy/alias.hpp"
17 #include "arg_router/policy/default_value.hpp"
18 #include "arg_router/policy/dependent.hpp"
19 #include "arg_router/policy/display_name.hpp"
20 #include "arg_router/policy/long_name.hpp"
21 #include "arg_router/policy/none_name.hpp"
22 #include "arg_router/policy/required.hpp"
23 #include "arg_router/policy/router.hpp"
24 #include "arg_router/policy/runtime_enable.hpp"
25 #include "arg_router/policy/short_form_expander.hpp"
26 #include "arg_router/policy/short_name.hpp"
27 #include "arg_router/positional_arg.hpp"
28 #include "arg_router/root.hpp"
29 #include "arg_router/utility/tree_recursor.hpp"
30 
31 namespace arg_router::policy
32 {
33 // We have to do this forward declaration and specialisation otherwise the rule key check below
34 // fails when the validator itself is tested
35 namespace validation
36 {
37 template <typename... Rules>
38 class validator;
39 } // namespace validation
40 
41 template <typename... Rules>
42 struct is_policy<validation::validator<Rules...>> : std::true_type {
43 };
44 
47 namespace validation
48 {
73 template <template <typename...> typename T, typename... Conditions>
74 using rule = std::tuple<boost::mp11::mp_quote<T>, Conditions...>;
75 
95 template <typename T, typename... Conditions>
96 using rule_q = std::tuple<T, Conditions...>;
97 
105 template <typename... Rules>
107 {
108 public:
110  using rules_type = std::tuple<Rules...>;
111 
112 private:
113  template <typename Current>
114  struct rule_lookup {
115  template <typename Rule>
116  struct fn {
117  constexpr static bool value = boost::mp11::mp_first<Rule>::template fn<Current>::value;
118  };
119  };
120 
121  struct validate_fn {
122  template <typename Current, typename... Parents>
123  constexpr static void fn() noexcept
124  {
125  // Find the matching rule
126 #ifdef MSVC_1936_WORKAROUND
127  constexpr auto rule_index = boost::mp11::mp_find_if< //
128  rules_type,
129  rule_lookup<Current>::fn>::value;
130 #else
131  constexpr auto rule_index = boost::mp11::mp_find_if_q< //
132  rules_type,
133  rule_lookup<Current>>::value;
134 #endif
135  static_assert(rule_index != std::tuple_size_v<rules_type>, "No rule for Current");
136 
137  // Remove the rule key so we just have a list of conditions
138  using conditions =
139  boost::mp11::mp_drop_c<std::tuple_element_t<rule_index, rules_type>, 1>;
140  utility::tuple_type_iterator<conditions>([](auto i) {
141  using condition = std::tuple_element_t<i, conditions>;
142  condition::template check<Current, Parents...>();
143  });
144  }
145  };
146 
147 public:
153  template <typename Root>
154  constexpr static void validate() noexcept
155  {
156  utility::tree_type_recursor<validate_fn, Root>();
157  }
158 };
159 
162 namespace common_rules
163 {
169 template <template <typename...> typename... T>
171  static_assert((sizeof...(T) > 0), "Must be at least one despecialised type");
172 
173  template <typename Current>
174  struct fn {
175  constexpr static bool value =
176  boost::mp11::mp_or<traits::is_specialisation_of<Current, T>...>::value;
177  };
178 };
179 } // namespace common_rules
180 
184  template <typename T, typename... Parents>
185  constexpr static void check() noexcept
186  {
187  if constexpr (sizeof...(Parents) > 0) {
188  using Owner = boost::mp11::mp_first<std::tuple<Parents...>>;
189 
190  // Check that there's only one in the owner (itself)
191  static_assert(algorithm::count_despecialised_v<T, typename Owner::policies_type> == 1,
192  "Policy must be present and unique in owner");
193  }
194  }
195 };
196 
202 template <template <typename...> typename... ModeTypes>
204  static_assert(sizeof...(ModeTypes), "Must be at least one mode type");
205 
206  template <typename Policy, typename PathToThis>
207  struct checker {
208  template <typename Current, typename... Parents>
209  constexpr static void fn() noexcept
210  {
211  using path_type = std::tuple<Parents...>;
212 
213  // Skip checking ourself
214  if constexpr (!std::is_same_v<PathToThis, path_type>) {
215  static_assert(!std::is_same_v<Policy, Current>,
216  "Policy must be unique in the parse tree up to "
217  "the nearest mode or root");
218  }
219  }
220  };
221 
222  template <typename T>
223  using is_mode =
224  boost::mp11::mp_any_of<std::tuple<traits::is_specialisation_of<T, ModeTypes>...>,
225  boost::mp11::mp_to_bool>;
226 
227  // Don't recurse into child modes, they effectively have their own namespace
228  template <typename StartType>
229  struct skipper {
230  template <typename Current, typename...>
231  [[nodiscard]] constexpr static bool fn() noexcept
232  {
233  // If the start node is a mode, don't skip it!
234  if constexpr (std::is_same_v<StartType, Current>) {
235  return false;
236  } else {
237  return is_mode<Current>::value;
238  }
239  }
240  };
241 
242  template <typename T, typename... Parents>
243  constexpr static void check() noexcept
244  {
245  using ParentTuple = std::tuple<Parents...>;
246  constexpr auto NumParents = sizeof...(Parents);
247 
248  // Make sure there's at least one parent beyond the owner
249  if constexpr (NumParents > 1) {
250  // Find a mode type, if there's one present we stop moving up through the ancestors at
251  // that point, otherwise we go up to the root
252  constexpr auto mode_index = boost::mp11::mp_find_if<ParentTuple, is_mode>::value;
253 
254  using path_type =
255  boost::mp11::mp_take_c<ParentTuple, std::min(mode_index + 1, NumParents)>;
256  using start_type = boost::mp11::mp_back<path_type>;
257 
258  // Recurse the tree from the oldest generation, testing that no other policy matches
259  // ours
260  utility::tree_type_recursor<checker<T, path_type>, skipper<start_type>, start_type>();
261  }
262  }
263 };
264 
270 template <std::size_t Index, template <typename...> typename ParentType>
273  constexpr static std::size_t index = Index;
274 
279  template <typename T>
281 };
282 
290 template <typename... ParentIndexTypes>
291 struct parent_types {
292  static_assert(sizeof...(ParentIndexTypes) > 0, "Must be at least one parent_index_pair_type");
293 
294  template <std::size_t MaxIndex>
295  struct index_filter {
296  template <typename Pair>
297  using fn = boost::mp11::mp_bool<(Pair::index < MaxIndex)>;
298  };
299 
300  template <typename... Parents>
301  struct checker {
302  template <typename Pair>
303  using fn =
304  typename Pair::template fn<std::tuple_element_t<Pair::index, std::tuple<Parents...>>>;
305  };
306 
307  template <typename T, typename... Parents>
308  constexpr static void check() noexcept
309  {
310  // Remove any entries whose index is beyond the Parents list size
311  using clamped_indices = boost::mp11::mp_filter_q<index_filter<sizeof...(Parents)>,
312  std::tuple<ParentIndexTypes...>>;
313 
314  // Check that each despecialised parent type matches the corresponding parent in the tree
315  using matches = boost::mp11::mp_transform_q<checker<Parents...>, clamped_indices>;
316 
317  static_assert(boost::mp11::mp_any_of<matches, boost::mp11::mp_to_bool>::value,
318  "Parent must be one of a set of types");
319  }
320 };
321 
326 template <template <typename...> typename... Policies>
328  template <typename T>
329  using checker = boost::mp11::mp_all_of<
330  std::tuple<algorithm::has_specialisation<Policies, typename T::policies_type>...>,
331  boost::mp11::mp_to_bool>;
332 
333  template <typename T, typename...>
334  constexpr static void check() noexcept
335  {
336  static_assert(checker<T>::value, "T must have all these policies");
337  }
338 };
339 
344 template <template <typename...> typename... Policies>
346  template <typename T>
347  using checker = boost::mp11::mp_none_of<
348  std::tuple<algorithm::has_specialisation<Policies, typename T::policies_type>...>,
349  boost::mp11::mp_to_bool>;
350 
351  template <typename T, typename...>
352  constexpr static void check() noexcept
353  {
354  static_assert(checker<T>::value, "T must have none of these policies");
355  }
356 };
357 
358 template <template <typename...> typename Policy>
359 struct basic_child_must_have_policy {
360  template <typename Child>
362 
363  template <typename T>
364  constexpr static bool value =
365  boost::mp11::mp_all_of<typename T::children_type, child_checker>::value;
366 };
367 
372 template <template <typename...> typename Policy>
374  template <typename T, typename...>
375  constexpr static void check() noexcept
376  {
377  static_assert(basic_child_must_have_policy<Policy>::template value<T>,
378  "All children of T must have this policy");
379  }
380 };
381 
386 template <template <typename...> typename Policy>
388  template <typename T, typename...>
389  constexpr static void check() noexcept
390  {
391  if constexpr ((std::tuple_size_v<typename T::children_type>) > 0) {
392  static_assert(!basic_child_must_have_policy<Policy>::template value<T>,
393  "All children of T must not have this policy");
394  }
395  }
396 };
397 
400 template <template <typename...> typename Policy>
402  template <typename T, typename... Parents>
403  constexpr static void check() noexcept
404  {
405  static_assert(policy::is_policy_v<T>, "T must be a policy");
406  static_assert(sizeof...(Parents) >= 1, "Must be at least one parent");
407 
408  using parent = boost::mp11::mp_first<std::tuple<Parents...>>;
409  static_assert(!algorithm::has_specialisation_v<Policy, typename parent::policies_type>,
410  "Parent must not have this policy");
411  }
412 };
413 
419 template <template <typename...> typename... ModeTypes>
421  static_assert(sizeof...(ModeTypes), "Must be at least one mode type");
422 
423  template <typename T>
424  using is_mode =
425  boost::mp11::mp_any_of<std::tuple<traits::is_specialisation_of<T, ModeTypes>...>,
426  boost::mp11::mp_to_bool>;
427 
428  template <typename T>
429  struct is_anonymous_mode {
430  constexpr static auto value = []() {
431  if constexpr (is_mode<T>::value) {
432  return T::is_anonymous;
433  }
434  return false;
435  }();
436  };
437 
438  template <typename T, typename...>
439  constexpr static void check() noexcept
440  {
441  constexpr auto num_anonymous = boost::mp11::mp_count_if< //
442  typename T::children_type,
443  is_anonymous_mode>::value;
444 
445  static_assert((num_anonymous <= 1), "Only one child mode can be anonymous");
446  }
447 };
448 
453 template <template <typename...> typename... Policies>
455  static_assert(sizeof...(Policies) >= 2, "Condition requires at least two policies");
456 
457  template <typename T, typename...>
458  constexpr static void check() noexcept
459  {
460  static_assert(
461  (algorithm::count_specialisation_v<Policies, typename T::policies_type> + ...) >= 1,
462  "T must have at least one of the policies");
463  }
464 };
465 
471 template <template <typename...> typename... NodeTypes>
473  static_assert(sizeof...(NodeTypes), "Must be at least one node type");
474 
475  template <typename T>
476  using is_target_node =
477  boost::mp11::mp_any_of<std::tuple<traits::is_specialisation_of<T, NodeTypes>...>,
478  boost::mp11::mp_to_bool>;
479 
480  template <typename T, typename...>
481  constexpr static void check() noexcept
482  {
483  // Find the first index of a target node, then for each element type after, check that it
484  // is also the same type of node
485 
486  // Remap the child types to a tuple of booleans, where true means that they are
487  // specialisations of one of the given node types
488  using children_type = typename T::children_type;
489  using is_target_node_map = boost::mp11::mp_transform_q<
490  boost::mp11::mp_bind<boost::mp11::mp_to_bool,
491  boost::mp11::mp_bind<is_target_node, boost::mp11::_1>>,
492 
493  children_type>;
494 
495  constexpr auto first_node_index =
496  boost::mp11::mp_find<is_target_node_map, boost::mp11::mp_true>::value;
497  if constexpr (first_node_index != std::tuple_size_v<is_target_node_map>) {
498  using drop_before_first = boost::mp11::mp_drop_c<is_target_node_map, first_node_index>;
499  static_assert(boost::mp11::mp_all_of<drop_before_first, boost::mp11::mp_to_bool>::value,
500  "Node types must all appear at the end of child list for a node");
501  }
502  }
503 };
504 
510 template <template <typename...> typename... ModeTypes>
512  static_assert(sizeof...(ModeTypes), "Must be at least one mode type");
513 
514  template <typename T>
515  using is_mode =
516  boost::mp11::mp_any_of<std::tuple<traits::is_specialisation_of<T, ModeTypes>...>,
517  boost::mp11::mp_to_bool>;
518 
519  template <typename T>
520  struct is_anonymous_mode {
521  constexpr static auto value = []() {
522  if constexpr (is_mode<T>::value) {
523  return T::is_anonymous;
524  }
525  return false;
526  }();
527  };
528 
529  template <typename T, typename...>
530  constexpr static void check() noexcept
531  {
532  constexpr auto has_anonymous_mode_child =
533  boost::mp11::mp_any_of<typename T::children_type, is_anonymous_mode>::value;
534 
535  if constexpr (has_anonymous_mode_child) {
536  node_types_must_be_at_end<ModeTypes...>::template check<T>();
537  }
538  }
539 };
540 
548  template <typename T>
549  struct is_list_like_node {
550  static constexpr bool value = []() {
551  if constexpr (traits::has_minimum_count_method_v<T> &&
552  traits::has_maximum_count_method_v<T>) {
553  return (T::minimum_count() != T::maximum_count()) &&
554  !policy::has_multi_stage_value_v<T> &&
555  !traits::has_token_end_marker_method_v<T>;
556  }
557 
558  return false;
559  }();
560  };
561 
562  template <typename T, typename...>
563  constexpr static void check() noexcept
564  {
565  using children_type = typename T::children_type;
566  constexpr auto count = boost::mp11::mp_count_if<children_type, is_list_like_node>::value;
567  static_assert(count <= 1, "There can only be one variable length list-like child");
568 
569  if constexpr (count == 1) {
570  constexpr auto index = boost::mp11::mp_find_if<children_type, is_list_like_node>::value;
571  static_assert(index == (std::tuple_size_v<children_type> - 1),
572  "Variable length list-like child must be at end of children");
573  }
574  }
575 };
576 
585  template <typename T, typename...>
586  constexpr static void check() noexcept
587  {
588  if constexpr (policy::is_required_v<T> && traits::has_minimum_count_method_v<T>) {
589  static_assert(
590  T::minimum_count() >= 1,
591  "T must have a minimum count of at least 1 if required (it improves help output)");
592  }
593  }
594 };
595 
596 namespace detail
597 {
598 template <typename RuleTuple>
599 struct validator_from_tuple_impl {
600  static_assert(traits::always_false_v<RuleTuple>, "RuleTuple must be a tuple-like type");
601 };
602 
603 template <template <typename...> typename Tuple, typename... Rules>
604 struct validator_from_tuple_impl<Tuple<Rules...>> {
605  using type = validator<Rules...>;
606 };
607 } // namespace detail
608 
613 template <typename RuleTuple>
614 using validator_from_tuple = typename detail::validator_from_tuple_impl<RuleTuple>::type;
615 
617 constexpr auto default_validator = validator<
618  // List the policies first as there are more of them and therefore more likely to be the target
619  // Name policy rules
623  // None name
630  // Display name
636  // Required
640  // Default value
644  // Router
648  // Exception translator
652  // Generic policy rule
655 
656  // Tree nodes
657  // Flag
663  // Arg
668  // Multi-arg
673  // Forwarding-arg
678  // Counting flag
683  // Positional arg
690  // one_of
698  // alias_group
704  // Mode
708  policy::runtime_enable_required,
714  // Help
722  // Root
728  policy::runtime_enable_required>,
733 } // namespace validation
734 } // namespace arg_router::policy
constexpr static void validate() noexcept
Definition: validator.hpp:154
constexpr auto default_validator
Definition: validator.hpp:617
std::tuple< T, Conditions... > rule_q
Definition: validator.hpp:96
typename detail::validator_from_tuple_impl< RuleTuple >::type validator_from_tuple
Definition: validator.hpp:614
std::tuple< boost::mp11::mp_quote< T >, Conditions... > rule
Definition: validator.hpp:74