arg_router  1.4.0
C++ command line argument parsing and routing
help.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/policy/default_help_formatter.hpp"
8 #include "arg_router/policy/description.hpp"
9 #include "arg_router/policy/flatten_help.hpp"
10 #include "arg_router/policy/long_name.hpp"
11 #include "arg_router/policy/min_max_count.hpp"
12 #include "arg_router/policy/no_result_value.hpp"
13 #include "arg_router/policy/short_name.hpp"
14 #include "arg_router/tree_node.hpp"
15 #include "arg_router/utility/string_to_policy.hpp"
16 
17 #include <iostream>
18 #include <sstream>
19 
20 namespace arg_router
21 {
22 namespace detail
23 {
24 template <typename... Policies>
25 class add_missing_formatter_policy
26 {
27  using policies_tuple = std::tuple<std::decay_t<Policies>...>;
28  using default_formatter_type = policy::default_help_formatter_t<>;
29 
30 public:
31  constexpr static auto has_formatter =
32  boost::mp11::mp_find_if<policies_tuple, traits::has_generate_help_method>::value !=
33  std::tuple_size_v<policies_tuple>;
34 
35  using type = std::conditional_t<
36  has_formatter,
37  boost::mp11::mp_rename<policies_tuple, tree_node>,
38  boost::mp11::mp_rename<boost::mp11::mp_push_front<policies_tuple, default_formatter_type>,
39  tree_node>>;
40 };
41 } // namespace detail
42 
48 template <typename... Policies>
49 class help_t :
50  public detail::add_missing_formatter_policy<
51  policy::no_result_value<>,
52  policy::min_max_count_t<traits::integral_constant<std::size_t{0}>,
53  traits::integral_constant<std::numeric_limits<std::size_t>::max()>>,
54  std::decay_t<Policies>...>::type
55 {
56 private:
57  static_assert(policy::is_all_policies_v<std::tuple<std::decay_t<Policies>...>>,
58  "Help must only contain policies (not other nodes)");
59 
60  static_assert(traits::has_long_name_method_v<help_t> || traits::has_short_name_method_v<help_t>,
61  "Help must have a long and/or short name policy");
62  static_assert(!traits::has_display_name_method_v<help_t>,
63  "Help must not have a display name policy");
64  static_assert(!traits::has_none_name_method_v<help_t>, "Help must not have a none name policy");
65 
66  using parent_type = typename detail::add_missing_formatter_policy<
70  std::decay_t<Policies>...>::type;
71 
72  constexpr static auto formatter_index =
73  boost::mp11::mp_find_if<typename parent_type::policies_type,
75  static_assert(formatter_index != std::tuple_size_v<typename parent_type::policies_type>,
76  "Help node must have a formatter policy");
77  using formatter_type =
78  std::tuple_element_t<formatter_index, typename parent_type::policies_type>;
79 
80 public:
81  using typename parent_type::policies_type;
82  using parent_type::generate_help;
83 
85  template <bool Flatten>
86  using help_data_type = typename parent_type::template default_leaf_help_data_type<Flatten>;
87 
92  template <auto has_formatter = detail::add_missing_formatter_policy<Policies...>::has_formatter>
93  constexpr explicit help_t(Policies... policies,
94  // NOLINTNEXTLINE(*-named-parameter)
95  std::enable_if_t<has_formatter>* = nullptr) noexcept :
96  parent_type{policy::no_result_value<>{},
98  traits::integral_constant<std::size_t{0}>,
100  std::move(policies)...}
101  {
102  }
103 
104  template <auto has_formatter = detail::add_missing_formatter_policy<Policies...>::has_formatter>
105  constexpr explicit help_t(Policies... policies,
106  // NOLINTNEXTLINE(*-named-parameter)
107  std::enable_if_t<!has_formatter>* = nullptr) noexcept :
108  parent_type{policy::default_help_formatter,
109  policy::no_result_value<>{},
110  policy::min_max_count_t<
111  traits::integral_constant<std::size_t{0}>,
112  traits::integral_constant<std::numeric_limits<std::size_t>::max()>>{},
113  std::move(policies)...}
114  {
115  }
116 
117  template <typename Validator, bool HasTarget, typename... Parents>
118  [[nodiscard]] std::optional<parsing::parse_target> pre_parse(
119  parsing::pre_parse_data<Validator, HasTarget> pre_parse_data,
120  const Parents&... parents) const
121 
122  {
123  static_assert((sizeof...(Parents) >= 1), "At least one parent needed for help");
124 
125  return parent_type::pre_parse(pre_parse_data, *this, parents...);
126  }
127 
135  template <bool Flatten, typename Node>
137  const Node& start_node) const noexcept
138  {
139  using node_hdt = typename Node::template help_data_type<Flatten>;
140 
141  const auto filter = [](const auto& child) {
142  using child_type = std::decay_t<decltype(child)>;
143  if constexpr (traits::has_help_data_type_v<child_type>) {
144  if constexpr (traits::has_runtime_enabled_method_v<child_type>) {
145  return child.runtime_enabled();
146  }
147  return true;
148  }
149 
150  return false;
151  };
152 
153  // Create a target runtime
154  auto rhd = runtime_help_data{node_hdt::label::get(), node_hdt::description::get(), {}};
155  if constexpr (traits::has_runtime_children_method_v<node_hdt>) {
156  rhd.children = node_hdt::runtime_children(start_node, filter);
157  } else {
158  rhd.children =
159  parent_type::template default_leaf_help_data_type<Flatten>::runtime_children(
160  start_node,
161  filter);
162  }
163 
164  return rhd;
165  }
166 
178  template <typename... Parents>
179  void parse(parsing::parse_target&& target, const Parents&... parents) const
180  {
181  const auto& root =
182  std::get<sizeof...(Parents) - 1>(std::tuple{std::cref(parents)...}).get();
183 
184  find_help_target(target.tokens(), root, [&](const auto& target_node) {
185  using root_type = std::decay_t<decltype(root)>;
186  using node_type = std::decay_t<decltype(target_node)>;
187 
188  // If the user has specified a help target then enable flattening otherwise the output
189  // is a bit useless...
190  constexpr auto flatten =
191  algorithm::has_specialisation_v<policy::flatten_help_t,
192  typename parent_type::policies_type> ||
193  !std::is_same_v<root_type, node_type>;
194 
195  // Create the runtime filter
196  if constexpr (traits::has_runtime_generate_help_method_v<formatter_type>) {
197  const auto rhd = generate_runtime_help_data<flatten>(target_node);
198 
199  // If there is a routing policy then delegate to it, otherwise print the help output
200  // to std::cout and exit
201  using routing_policy =
202  typename parent_type::template phase_finder_t<policy::has_routing_phase_method>;
203  if constexpr (std::is_void_v<routing_policy>) {
204  formatter_type::template generate_help<node_type, help_t, flatten>(std::cout,
205  rhd);
206  std::exit(EXIT_SUCCESS);
207  } else {
208  auto stream = ostringstream{};
209  formatter_type::template generate_help<node_type, help_t, flatten>(stream, rhd);
210  this->routing_policy::routing_phase(std::move(stream));
211  }
212  } else {
213  using routing_policy =
214  typename parent_type::template phase_finder_t<policy::has_routing_phase_method>;
215  if constexpr (std::is_void_v<routing_policy>) {
216  formatter_type::template generate_help<node_type, help_t, flatten>(std::cout);
217  std::exit(EXIT_SUCCESS);
218  } else {
219  auto stream = ostringstream{};
220  formatter_type::template generate_help<node_type, help_t, flatten>(stream);
221  this->routing_policy::routing_phase(std::move(stream));
222  }
223  }
224  });
225  }
226 
227 private:
228  static_assert(
229  !parent_type::template any_phases_v<bool, // Type doesn't matter, as long as it isn't void
230  policy::has_parse_phase_method,
231  policy::has_validation_phase_method,
232  policy::has_missing_phase_method>,
233  "Help only supports policies with pre-parse and routing phases");
234 
235  template <typename Node, typename TargetFn>
236  static void find_help_target(vector<parsing::token_type>& tokens,
237  const Node& node,
238  const TargetFn& fn)
239  {
240  if constexpr (traits::has_runtime_enabled_method_v<Node>) {
241  if (!node.runtime_enabled()) {
242  throw multi_lang_exception{error_code::unknown_argument, tokens.front()};
243  }
244  }
245 
246  if (tokens.empty()) {
247  fn(node);
248  return;
249  }
250 
251  auto result = false;
252  utility::tuple_iterator(
253  [&](auto /*i*/, const auto& child) {
254  using child_type = std::decay_t<decltype(child)>;
255 
256  if (tokens.empty()) {
257  return;
258  }
259 
260  // Help tokens aren't pre-parsed by the target nodes (as they would fail if missing
261  // any required value tokens), so we have just use the prefix to generate a
262  // token_type from them, as they all of a prefix_type of none
263  const auto token = parsing::get_token_type(child, tokens.front().name);
264  if (!result && parsing::match<child_type>(token)) {
265  result = true;
266  tokens.erase(tokens.begin());
267  find_help_target(tokens, child, fn);
268  }
269  },
270  node.children());
271 
272  if (!result) {
273  throw multi_lang_exception{error_code::unknown_argument, tokens.front()};
274  }
275  }
276 };
277 
293 template <typename... Policies>
294 [[nodiscard]] constexpr auto help(Policies... policies) noexcept
295 {
296  return std::apply(
297  [](auto... converted_policies) {
298  return help_t<std::decay_t<decltype(converted_policies)>...>{
299  std::move(converted_policies)...};
300  },
301  utility::string_to_policy::convert<
305  std::move(policies)...));
306 }
307 } // namespace arg_router
constexpr help_t(Policies... policies, std::enable_if_t< has_formatter > *=nullptr) noexcept
Definition: help.hpp:93
typename parent_type::template default_leaf_help_data_type< Flatten > help_data_type
Definition: help.hpp:86
runtime_help_data generate_runtime_help_data(const Node &start_node) const noexcept
Definition: help.hpp:136
void parse(parsing::parse_target &&target, const Parents &... parents) const
Definition: help.hpp:179
constexpr auto default_help_formatter
constexpr auto is_all_policies_v
Definition: policy.hpp:53
std::integral_constant< decltype(Value), Value > integral_constant
Definition: traits.hpp:210
constexpr auto help(Policies... policies) noexcept
Definition: help.hpp:294
constexpr auto root(Params... params) noexcept
Definition: root.hpp:295
vector< runtime_help_data > children
Child node help data.