arg_router  1.4.0
C++ command line argument parsing and routing
root.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/exception_translator.hpp"
9 #include "arg_router/policy/flatten_help.hpp"
10 #include "arg_router/policy/no_result_value.hpp"
11 #include "arg_router/tree_node.hpp"
12 
13 #include <utility>
14 #include <variant>
15 
16 namespace arg_router
17 {
18 namespace policy::validation
19 {
20 template <typename... Rules>
21 class validator;
22 } // namespace policy::validation
23 
24 namespace detail
25 {
26 template <typename... Params>
27 class add_missing_exception_translator
28 {
29  using params_tuple = std::tuple<std::decay_t<Params>...>;
30  using policies_tuple = boost::mp11::mp_copy_if<params_tuple, policy::is_policy>;
31 
32 public:
33  constexpr static auto exception_translator =
34  policy::exception_translator<default_error_code_translations, void>;
35 
36  constexpr static auto has_exception_translator =
37  boost::mp11::mp_find_if<policies_tuple, traits::has_translate_exception_method>::value !=
38  std::tuple_size_v<policies_tuple>;
39 
40  using type = std::conditional_t<
41  has_exception_translator,
42  boost::mp11::mp_rename<params_tuple, tree_node>,
43  boost::mp11::mp_rename<
44  boost::mp11::mp_push_back<params_tuple, std::decay_t<decltype(exception_translator)>>,
45  tree_node>>;
46 };
47 } // namespace detail
48 
53 template <typename... Params>
54 class root_t : public detail::add_missing_exception_translator<Params...>::type
55 {
56  using parent_type = typename detail::add_missing_exception_translator<Params...>::type;
57 
58 public:
59  using typename parent_type::policies_type;
60  using typename parent_type::children_type;
61 
62 private:
63  static_assert(
64  !parent_type::template any_phases_v<bool, // Type doesn't matter, as long as it isn't void
70  "Root does not support policies with any parsing phases");
71 
72  static_assert(!traits::has_long_name_method_v<parent_type> &&
73  !traits::has_short_name_method_v<parent_type> &&
74  !traits::has_display_name_method_v<parent_type> &&
75  !traits::has_none_name_method_v<parent_type> &&
76  !traits::has_error_name_method_v<parent_type> &&
77  !traits::has_description_method_v<parent_type>,
78  "Root cannot have name or description policies");
79 
80  constexpr static auto validator_index =
81  algorithm::find_specialisation_v<policy::validation::validator, policies_type>;
82  static_assert(validator_index != std::tuple_size_v<policies_type>,
83  "Root must have a validator policy, use "
84  "policy::validation::default_validator unless you have "
85  "created a custom one");
86 
87  static_assert(std::tuple_size_v<children_type> >= 1, "Root must have at least one child");
88 
89  template <typename Child>
90  struct router_checker {
91  constexpr static bool has_router = !std::is_void_v<
92  typename Child::template phase_finder_t<policy::has_routing_phase_method>>;
93 
94  constexpr static bool value = policy::has_no_result_value_v<Child> || has_router;
95  };
96  static_assert(boost::mp11::mp_all_of<children_type, router_checker>::value,
97  "All root children must have routers, unless they have no value");
98 
99 public:
101  // Initially I wanted the default_validator to be used if one isn't user specified, but you get
102  // into a circular dependency as the validator needs the root first
103  using validator_type = std::tuple_element_t<validator_index, policies_type>;
104 
106  template <bool Flatten>
108  {
109  public:
110  using label = AR_STRING("");
111  using description = AR_STRING("");
112  using children =
113  typename parent_type::template default_leaf_help_data_type<Flatten>::all_children_help;
114  };
115 
120  template <auto has_exception_translator =
121  detail::add_missing_exception_translator<Params...>::has_exception_translator>
122  constexpr explicit root_t(Params... params,
123  // NOLINTNEXTLINE(*-named-parameter)
124  std::enable_if_t<has_exception_translator>* = nullptr) noexcept :
125  parent_type{std::move(params)...}
126  {
127  validator_type::template validate<std::decay_t<decltype(*this)>>();
128  }
129 
130  template <auto has_exception_translator =
131  detail::add_missing_exception_translator<Params...>::has_exception_translator>
132  constexpr explicit root_t(Params... params,
133  // NOLINTNEXTLINE(*-named-parameter)
134  std::enable_if_t<!has_exception_translator>* = nullptr) noexcept :
135  parent_type{std::move(params)...,
136  detail::add_missing_exception_translator<Params...>::exception_translator}
137  {
138  validator_type::template validate<std::decay_t<decltype(*this)>>();
139  }
140 
148  {
149  try {
150  // Take a copy of the front token for the error messages
151  const auto front_token = args.empty() ? //
153  args.front();
154 
155  // Find a matching child
156  auto match = std::optional<parsing::parse_target>{};
158  [&](auto /*i*/, const auto& child) {
159  // Skip any remaining children if one has been found
160  if (!match && (match = child.pre_parse(parsing::pre_parse_data{args}, *this))) {
161  if (!args.empty()) {
162  throw multi_lang_exception{error_code::unhandled_arguments, args};
163  }
164  (*match)();
165  }
166  },
167  this->children());
168  if (!match) {
169  if (front_token.name.empty()) {
171  }
172  parsing::unknown_argument_exception(*this, front_token);
173  }
174  } catch (multi_lang_exception& e) {
175  // Convert the error code exception to a parse_exception. This method will always be
176  // present because even if an exception_translator-like policy is not specified by the
177  // user, a default en_GB one is added
178  this->translate_exception(e);
179  }
180  }
181 
190  template <typename Iter, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Iter>, int>>>
191  void parse(Iter begin, Iter end) const
192  {
193  auto args = vector<parsing::token_type>{};
194  for (; begin != end; ++begin) {
195  args.emplace_back(parsing::prefix_type::none, *begin);
196  }
197  args.shrink_to_fit();
198 
199  parse(std::move(args));
200  }
201 
210  template <typename Container,
211  typename = std::enable_if_t<
212  !std::is_same_v<std::decay_t<Container>, vector<parsing::token_type>>>>
213  void parse(const Container& c) const
214  {
215  using std::begin;
216  using std::end;
217 
218  parse(std::begin(c), std::end(c));
219  }
220 
228  void parse(int argc, char** argv) const
229  {
230  // Skip the program name
231  parse(&argv[1], &argv[argc]);
232  }
233 
239  void help([[maybe_unused]] std::ostream& stream) const
240  {
241  // Have to lambda wrap here due to MSVC where the if constexpr branch is evaluated even when
242  // false
243  [&](auto* type_ptr) {
244  using root_type = std::decay_t<std::remove_pointer_t<decltype(type_ptr)>>;
245  using root_children = typename root_type::children_type;
246 
247  constexpr static auto help_index =
248  boost::mp11::mp_find_if<root_children, traits::has_generate_help_method>::value;
249 
250  if constexpr (help_index < std::tuple_size_v<root_children>) {
251  using help_type = std::tuple_element_t<help_index, root_children>;
252  constexpr auto flatten =
254  typename help_type::policies_type>;
255 
256  const auto& help_node = std::get<help_index>(this->children());
257  try {
258  if constexpr (traits::has_generate_runtime_help_data_method_v<help_type> &&
259  traits::has_runtime_generate_help_method_v<help_type>) {
260  const auto rhd =
261  help_node.template generate_runtime_help_data<flatten>(*this);
262  help_node.template generate_help<root_type, help_type, flatten>(stream,
263  rhd);
264  } else {
265  help_node.template generate_help<root_type, help_type, flatten>(stream);
266  }
267  } catch (multi_lang_exception& e) {
268  this->translate_exception(e);
269  }
270  }
271  }(this);
272  }
273 
278  [[nodiscard]] string help() const
279  {
280  auto stream = ostringstream{};
281  help(stream);
282 
283  return stream.str();
284  }
285 };
286 
294 template <typename... Params>
295 [[nodiscard]] constexpr auto root(Params... params) noexcept
296 {
297  return root_t<std::decay_t<Params>...>{std::move(params)...};
298 }
299 } // namespace arg_router
void help([[maybe_unused]] std::ostream &stream) const
Definition: root.hpp:239
void parse(const Container &c) const
Definition: root.hpp:213
void parse(Iter begin, Iter end) const
Definition: root.hpp:191
string help() const
Definition: root.hpp:278
void parse(vector< parsing::token_type > args) const
Definition: root.hpp:147
void parse(int argc, char **argv) const
Definition: root.hpp:228
std::tuple_element_t< validator_index, policies_type > validator_type
Definition: root.hpp:103
constexpr root_t(Params... params, std::enable_if_t< has_exception_translator > *=nullptr) noexcept
Definition: root.hpp:122
constexpr auto has_specialisation_v
Definition: algorithm.hpp:124
void unknown_argument_exception(const Node &node, token_type unknown_token)
constexpr auto exception_translator
constexpr std::enable_if_t< traits::is_tuple_like_v< std::decay_t< Tuple > > > tuple_iterator(F &&f, Tuple &&tuple)
constexpr auto help(Policies... policies) noexcept
Definition: help.hpp:294
std::vector< T, config::allocator< T > > vector
Definition: basic_types.hpp:39
std::basic_ostringstream< char, std::char_traits< char >, config::allocator< char > > ostringstream
Definition: basic_types.hpp:32