arg_router  1.4.0
C++ command line argument parsing and routing
mode.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/parsing/unknown_argument_handling.hpp"
8 #include "arg_router/policy/description.hpp"
9 #include "arg_router/policy/error_name.hpp"
10 #include "arg_router/policy/multi_stage_value.hpp"
11 #include "arg_router/policy/no_result_value.hpp"
12 #include "arg_router/policy/none_name.hpp"
13 #include "arg_router/tree_node.hpp"
14 #include "arg_router/utility/string_to_policy.hpp"
15 #include "arg_router/utility/tree_recursor.hpp"
16 
17 #include <bitset>
18 
19 namespace arg_router
20 {
21 namespace detail
22 {
23 template <typename... Params>
24 class add_anonymous_error_name_to_mode
25 {
26  using params_tuple = std::tuple<std::decay_t<Params>...>;
27  using policies_tuple = boost::mp11::mp_remove_if<params_tuple, is_tree_node>;
28 
29 public:
30  constexpr static auto has_none_or_error_name =
31  (boost::mp11::mp_find_if<policies_tuple, traits::has_none_name_method>::value !=
32  std::tuple_size_v<policies_tuple>) ||
33  (boost::mp11::mp_find_if<policies_tuple, traits::has_error_name_method>::value !=
34  std::tuple_size_v<policies_tuple>);
35 
36  using error_name_type = policy::error_name_t<AR_STRING("(Anon mode)")>;
37 
38  using type = std::conditional_t<
39  has_none_or_error_name,
40  boost::mp11::mp_rename<params_tuple, tree_node>,
41  boost::mp11::mp_rename<boost::mp11::mp_push_front<params_tuple, error_name_type>,
42  tree_node>>;
43 };
44 } // namespace detail
45 
55 template <typename... Params>
56 class mode_t :
57  public detail::add_anonymous_error_name_to_mode<policy::no_result_value<>,
58  std::decay_t<Params>...>::type
59 {
60  using add_missing_error_name_type =
61  detail::add_anonymous_error_name_to_mode<policy::no_result_value<>,
62  std::decay_t<Params>...>;
63  using parent_type = typename add_missing_error_name_type::type;
64 
65  static_assert((std::tuple_size_v<typename mode_t::children_type> > 0),
66  "Mode must have at least one child node");
67  static_assert(!traits::has_long_name_method_v<mode_t>, "Mode must not have a long name policy");
68  static_assert(!traits::has_short_name_method_v<mode_t>,
69  "Mode must not have a short name policy");
70  static_assert(!traits::has_display_name_method_v<mode_t>,
71  "Mode must not have a display name policy");
72 
73  struct skip_tag {
74  };
75 
76  template <typename T>
77  using is_skip_tag = std::is_same<std::decay_t<T>, skip_tag>;
78 
79  template <typename T>
80  constexpr static bool is_skip_tag_v = is_skip_tag<T>::value;
81 
82  template <typename T>
83  using optional_value_type_or_skip = boost::mp11::mp_eval_if_c<
84  policy::has_no_result_value_v<T>,
85  skip_tag,
86  boost::mp11::mp_bind<
88  boost::mp11::mp_bind<traits::get_value_type, boost::mp11::_1>>::template fn,
89  T>;
90 
91  template <typename Child>
93 
94 public:
95  using typename parent_type::policies_type;
96  using typename parent_type::children_type;
97 
99  using value_type = boost::mp11::mp_transform<
101  boost::mp11::mp_remove_if<children_type, policy::has_no_result_value>>;
102 
104  constexpr static bool is_anonymous = !traits::has_none_name_method_v<mode_t>;
105 
106  static_assert(!is_anonymous || !traits::has_description_method_v<mode_t>,
107  "Anonymous modes cannot have a description policy");
108  static_assert(is_anonymous || (!is_anonymous && !traits::has_error_name_method_v<mode_t>),
109  "Named modes must not have an error name policy");
110 
112  template <bool Flatten>
114  {
115  public:
116  /* Provide an 'invisible' label when anonymous to have a preceding blank line, so it's
117  * contents aren't confused with any previously declared named modes (or their contents).
118  */
119  using label = std::conditional_t<
120  is_anonymous,
121  AR_STRING(' '),
122  typename parent_type::template default_leaf_help_data_type<Flatten>::label>;
123 
124  using description =
125  typename parent_type::template default_leaf_help_data_type<Flatten>::description;
126 
127  using children = std::conditional_t<
128  is_anonymous || Flatten,
129  typename parent_type::template default_leaf_help_data_type<Flatten>::all_children_help,
130  std::tuple<>>;
131 
132  template <typename OwnerNode, typename FilterFn>
133  [[nodiscard]] static vector<runtime_help_data> runtime_children(const OwnerNode& owner,
134  FilterFn&& f)
135  {
136  if constexpr (is_anonymous || Flatten) {
137  return parent_type::template default_leaf_help_data_type<true>::runtime_children(
138  owner,
139  std::forward<FilterFn>(f));
140  }
141 
142  return {};
143  }
144  };
145 
150  template <auto has_none_or_error_name = add_missing_error_name_type::has_none_or_error_name>
151  constexpr explicit mode_t(Params... params,
152  // NOLINTNEXTLINE(*-named-parameter)
153  std::enable_if_t<has_none_or_error_name>* = nullptr) noexcept :
154  parent_type{policy::no_result_value<>{}, std::move(params)...}
155  {
156  }
157 
158  template <auto has_none_or_error_name = add_missing_error_name_type::has_none_or_error_name>
159  constexpr explicit mode_t(Params... params,
160  // NOLINTNEXTLINE(*-named-parameter)
161  std::enable_if_t<!has_none_or_error_name>* = nullptr) noexcept :
162  parent_type{typename add_missing_error_name_type::error_name_type{},
163  policy::no_result_value<>{},
164  std::move(params)...}
165  {
166  }
167 
183  template <typename Validator, bool HasTarget, typename... Parents>
184  [[nodiscard]] std::optional<parsing::parse_target> pre_parse(
186  const Parents&... parents) const
187  {
188  return std::apply(
189  [&](auto&&... ancestors) { return pre_parse_impl(pre_parse_data, ancestors.get()...); },
190  parsing::clean_node_ancestry_list(*this, parents...));
191  }
192 
203  template <typename... Parents>
204  void parse(parsing::parse_target target, const Parents&... parents) const
205  {
206  return std::apply(
207  [&](auto&&... ancestors) { return parse_impl(target, ancestors.get()...); },
208  parsing::clean_node_ancestry_list(*this, parents...));
209  }
210 
211 private:
212  static_assert(!is_anonymous || boost::mp11::mp_none_of<children_type, is_child_mode>::value,
213  "Anonymous mode cannot have a child mode");
214 
215  static_assert(!parent_type::template any_phases_v<value_type,
219  "Mode does not support policies with parse, validation, or missing phases; as it "
220  "delegates those to its children");
221 
222  template <typename Child>
223  struct child_has_routing_phase {
224  using type = typename Child::template phase_finder_t<policy::has_routing_phase_method>;
225 
226  constexpr static bool value = !std::is_void_v<type>;
227  };
228  static_assert(boost::mp11::mp_none_of<boost::mp11::mp_remove_if<children_type, is_child_mode>,
229  child_has_routing_phase>::value,
230  "Non-mode children cannot have routing");
231 
232  template <typename Validator, bool HasTarget, typename DerivedMode, typename... Parents>
233  [[nodiscard]] std::optional<parsing::parse_target> pre_parse_impl(
234  parsing::pre_parse_data<Validator, HasTarget> pre_parse_data,
235  const DerivedMode& this_mode,
236  const Parents&... parents) const
237  {
238  using namespace utility::string_view_ops;
239  using namespace std::string_literals;
240 
241  static_assert(!HasTarget,
242  "Modes cannot receive pre_parse_data containing parent "
243  "parse_targets");
244  static_assert(!is_anonymous || (sizeof...(Parents) <= 1),
245  "Anonymous modes can only exist under the root");
246 
247  auto& args = pre_parse_data.args();
248 
249  // If we're not anonymous then check the leading token is a match. We can just delegate to
250  // the default implementation for this
251  if constexpr (!is_anonymous) {
252  auto match = parent_type::pre_parse(pre_parse_data, this_mode, parents...);
253  if (!match) {
254  return match;
255  }
256 
257  // Check if the next token (if any) matches a child mode. If so, delegate to that
258  if (!args.empty()) {
259  match.reset();
261  [&](auto /*i*/, const auto& child) {
262  using child_type = std::decay_t<decltype(child)>;
263  if constexpr (traits::is_specialisation_of_v<child_type, mode_t>) {
264  if (!match) {
265  match = child.pre_parse(pre_parse_data, this_mode, parents...);
266  }
267  }
268  },
269  this->children());
270  if (match) {
271  return match;
272  }
273  }
274  }
275 
276  if (!pre_parse_data.validator()(this_mode, parents...)) {
277  return {};
278  }
279 
280  auto target = parsing::parse_target{this_mode, parents...};
281 
282  // Iterate over the tokens until consumed, skipping children already processed that cannot
283  // be repeated on the command line
284  auto matched = std::bitset<std::tuple_size_v<children_type>>{};
285  while (!args.empty()) {
286  // Take a copy of the front token for the error messages
287  const auto front_token = args.front();
288 
289  auto match = std::optional<parsing::parse_target>{};
291  [&]([[maybe_unused]] auto i, const auto& child) {
292  using child_type = std::decay_t<decltype(child)>;
293 
294  // The token(s) have been processed so skip over any remaining children
295  if (match) {
296  return;
297  }
298 
299  // Skip past modes, as they're handled earlier
300  if constexpr (!traits::is_specialisation_of_v<child_type, mode_t>) {
301  match = child.pre_parse(
302  parsing::pre_parse_data{
303  args,
304  target,
305  [&](const auto& real_child, const auto&...) {
306  using real_child_type = std::decay_t<decltype(real_child)>;
307  return verify_match<real_child_type>(matched[i], front_token);
308  }},
309  this_mode,
310  parents...);
311 
312  // Update the matched bitset
313  if (match) {
314  matched.set(i);
315  }
316  }
317  },
318  this->children());
319 
320  if (!match) {
321  if (matched.all()) {
322  throw multi_lang_exception{error_code::unhandled_arguments, args};
323  }
324  parsing::unknown_argument_exception(*this, front_token);
325  }
326 
327  // Flatten out nested sub-targets
328  if (match->sub_targets().empty()) {
329  target.add_sub_target(std::move(*match));
330  } else {
331  for (auto& sub_target : match->sub_targets()) {
332  target.add_sub_target(std::move(sub_target));
333  }
334  }
335  }
336 
337  return target;
338  }
339 
340  template <typename DerivedMode, typename... Parents>
341  void parse_impl(parsing::parse_target target,
342  const DerivedMode& this_mode,
343  const Parents&... parents) const
344  {
345  // Create an internal-use results tuple, this is made from optional-wrapped children's
346  // value_types but any no_result_value children are replaced with skip_tag - this makes it
347  // easier to iterate
348  using results_type = boost::mp11::mp_transform<optional_value_type_or_skip, children_type>;
349  auto results = results_type{};
350 
351  for (auto& sub_target : target.sub_targets()) {
352  const auto node_hash = sub_target.node_type();
353  auto result = sub_target();
354 
355  if (result.has_value()) {
356  // We need to find the matching node to the sub_target. We do this by searching
357  // each child's subtree for a match - this gives us the index into the results tuple
358  // and the sub node type for the match
359  auto found = false;
361  [&](auto i, const auto& child) {
362  if (found) {
363  return;
364  }
365 
366  match_child(child, node_hash, [&](const auto& sub_child) {
367  found = true;
368  process_result<i>(results, result, sub_child);
369  });
370  },
371  this->children());
372  }
373  }
374 
375  // Handle missing tokens
377  [&]([[maybe_unused]] auto i, auto& result) {
378  if constexpr (!is_skip_tag_v<std::decay_t<decltype(result)>>) {
379  if (!result) {
380  const auto& child = std::get<i>(this->children());
381  process_missing_token(result, child, this_mode, parents...);
382  }
383  }
384  },
385  results);
386 
387  // Handle multi-stage value validation. Multi-stage value nodes cannot be validated during
388  // processing, as they will likely fail validation when partially processed. So the
389  // multi-stage nodes do not perform validation themselves, but have their owner (i.e. modes)
390  // do it at the end of processing - including after any default values have been generated
391  multi_stage_validation(results, this_mode, parents...);
392 
393  // Routing
394  using routing_policy =
395  typename parent_type::template phase_finder_t<policy::has_routing_phase_method>;
396  if constexpr (!std::is_void_v<routing_policy>) {
397  // Strip out the skipped results
398  auto stripped_results = algorithm::tuple_filter_and_construct<
399  boost::mp11::mp_not_fn<is_skip_tag>::template fn>(std::move(results));
400 
401  std::apply(
402  [&](auto&&... args) {
403  this->routing_phase(std::forward<std::decay_t<decltype(*args)>>(*args)...);
404  },
405  std::move(stripped_results));
406  } else if constexpr (is_anonymous) {
407  static_assert(traits::always_false_v<Params...>, "Anonymous modes must have routing");
408  } else if constexpr (!boost::mp11::mp_all_of<children_type, is_child_mode>::value) {
409  static_assert(traits::always_false_v<Params...>,
410  "Mode must have a router or all its children are modes");
411  } else {
412  // Mode guaranteed to be named here
413  throw multi_lang_exception{error_code::mode_requires_arguments,
414  parsing::node_token_type<mode_t>()};
415  }
416  }
417 
418  template <typename Child, typename Handler>
419  static void match_child(const Child& child, std::size_t hash, Handler handler)
420  {
421  auto found = false;
423  [&](const auto& node, const auto&...) {
424  using node_type = std::decay_t<decltype(node)>;
425 
426  // Skip modes and nodes without a parse function. In reality the runtime code will
427  // never allow that, but let's help the compiler out...
428  if constexpr (!traits::is_specialisation_of_v<node_type, mode_t> &&
429  traits::has_parse_method_v<node_type>) {
430  if (!found && (utility::type_hash<node_type>() == hash)) {
431  found = true;
432  handler(node);
433  }
434  }
435  },
436  child);
437  }
438 
439  template <typename Child>
440  [[nodiscard]] static bool verify_match(bool already_matched,
441  [[maybe_unused]] parsing::token_type token)
442  {
443  if constexpr (!Child::is_named && !policy::has_multi_stage_value_v<Child>) {
444  // Child is not named and can only appear on the command line once, so only perform the
445  // pre-parse if it hasn't been matched already
446  return !already_matched;
447  } else if constexpr (Child::is_named && !policy::has_multi_stage_value_v<Child>) {
448  // Child is named, but can only appear once on the command line, so perform the
449  // pre-parse and if there is a match check it isn't already matched
450  if (already_matched) {
451  throw multi_lang_exception{error_code::argument_has_already_been_set, token};
452  }
453 
454  return true;
455  } else {
456  // Just to prevent C4702 errors on MSVC
457  return true;
458  }
459  }
460 
461  template <std::size_t I, typename ResultsType, typename ChildType>
462  void process_result(ResultsType& results,
463  utility::unsafe_any parse_result,
464  const ChildType& child) const
465  {
466  using optional_result_type = std::tuple_element_t<I, ResultsType>;
467 
468  // The mode check is just for the compiler, the runtime code prevents it reaching here
469  if constexpr (!is_skip_tag_v<optional_result_type>) {
470  auto& result = std::get<I>(results);
471 
472  if constexpr (policy::has_multi_stage_value_v<ChildType>) {
473  using result_type = traits::arg_type_at_index<decltype(&ChildType::merge), 1>;
474  child.merge(result, std::move(parse_result.get<result_type>()));
475  } else {
476  if (result) {
477  throw multi_lang_exception{error_code::argument_has_already_been_set,
478  parsing::node_token_type<ChildType>()};
479  }
480 
481  using result_type = decltype(std::declval<ChildType>().parse(
482  std::declval<parsing::parse_target>()));
483  result = std::move(parse_result.get<result_type>());
484  }
485  }
486  }
487 
488 #ifdef _MSC_VER
489 # pragma warning(push)
490 # pragma warning(disable : 4702)
491 #endif
492  template <typename ValueType, typename ChildType, typename... Parents>
493  void process_missing_token(std::optional<ValueType>& result,
494  const ChildType& child,
495  const Parents&... parents) const
496  {
497  utility::tuple_type_iterator<typename ChildType::policies_type>([&](auto i) {
498  using policy_type = std::tuple_element_t<i, typename ChildType::policies_type>;
499  if constexpr (policy::has_missing_phase_method_v<policy_type, ValueType>) {
500  result = child.policy_type::template missing_phase<ValueType>(child, parents...);
501  }
502 
503 #ifdef _MSC_VER
504 # pragma warning(pop)
505 #endif
506  });
507 
508  // If no missing_phase methods were found that made the result valid, then it still needs to
509  // be valid - just default initialise
510  if (!result) {
511  result = ValueType{};
512  }
513 
514  // Irritatingly, we have to run the validation phase on the new value
515  utility::tuple_type_iterator<typename ChildType::policies_type>([&](auto i) {
516  using policy_type = std::tuple_element_t<i, typename ChildType::policies_type>;
517  if constexpr (policy::has_validation_phase_method_v<policy_type, ValueType>) {
518  child.policy_type::template validation_phase(*result, child, parents...);
519  }
520  });
521  }
522 
523  template <typename ResultsType, typename... Parents>
524  void multi_stage_validation(const ResultsType& results, const Parents&... parents) const
525  {
526  // For all the children that use multi-stage values, invoke any policy
527  // that supports a validation phase
529  [&](auto i, const auto& child) {
530  using child_type = std::decay_t<decltype(child)>;
531  using child_policies_type = typename child_type::policies_type;
532  using optional_result_type = std::tuple_element_t<i, ResultsType>;
533 
534  constexpr auto msv = policy::has_multi_stage_value_v<child_type> ||
535  boost::mp11::mp_any_of<typename child_type::children_type,
536  policy::has_multi_stage_value>::value;
537 
538  if constexpr (!is_skip_tag_v<optional_result_type> && msv) {
539  using result_type = typename optional_result_type::value_type;
540  const auto& result = *std::get<i>(results);
541 
542  utility::tuple_type_iterator<child_policies_type>([&](auto j) {
543  using policy_type = std::tuple_element_t<j, child_policies_type>;
544  if constexpr (policy::has_validation_phase_method_v<policy_type,
545  result_type>) {
546  child.policy_type::template validation_phase(result, child, parents...);
547  }
548  });
549  }
550  },
551  this->children());
552  }
553 };
554 
569 template <typename... Params>
570 constexpr auto mode(Params... params)
571 {
572  return std::apply(
573  [](auto... converted_params) {
574  return mode_t<std::decay_t<decltype(converted_params)>...>{
575  std::move(converted_params)...};
576  },
580  std::move(params)...));
581 }
582 } // namespace arg_router
void parse(parsing::parse_target target, const Parents &... parents) const
Definition: mode.hpp:204
std::optional< parsing::parse_target > pre_parse(parsing::pre_parse_data< Validator, HasTarget > pre_parse_data, const Parents &... parents) const
Definition: mode.hpp:184
constexpr static bool is_anonymous
Definition: mode.hpp:104
boost::mp11::mp_transform< traits::get_value_type, boost::mp11::mp_remove_if< children_type, policy::has_no_result_value > > value_type
Definition: mode.hpp:101
constexpr mode_t(Params... params, std::enable_if_t< has_none_or_error_name > *=nullptr) noexcept
Definition: mode.hpp:151
constexpr auto tuple_filter_and_construct(U &&input) noexcept
Definition: algorithm.hpp:196
void unknown_argument_exception(const Node &node, token_type unknown_token)
constexpr bool match(token_type token) noexcept
Definition: parsing.hpp:36
constexpr auto clean_node_ancestry_list(const BaseNode &base_node, const DerivedAndParents &... derived_and_parents)
Definition: parsing.hpp:105
typename add_optional< T >::type add_optional_t
Definition: traits.hpp:226
typename T::value_type get_value_type
Definition: traits.hpp:69
std::tuple_element_t< I+1, arg_extractor_t< T > > arg_type_at_index
Definition: traits.hpp:762
constexpr auto convert(Params &&... params) noexcept
constexpr void tree_recursor(Visitor visitor, const Root &root)
constexpr std::enable_if_t< traits::is_tuple_like_v< std::decay_t< Tuple > > > tuple_iterator(F &&f, Tuple &&tuple)
unsafe_any_t< sizeof(std::string_view)> unsafe_any
Definition: unsafe_any.hpp:210
std::vector< T, config::allocator< T > > vector
Definition: basic_types.hpp:39
constexpr auto mode(Params... params)
Definition: mode.hpp:570