arg_router  1.4.0
C++ command line argument parsing and routing
default_help_formatter.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/algorithm.hpp"
8 #include "arg_router/policy/program_addendum.hpp"
9 #include "arg_router/policy/program_intro.hpp"
10 #include "arg_router/policy/program_name.hpp"
11 #include "arg_router/policy/program_version.hpp"
12 #include "arg_router/tree_node_fwd.hpp"
14 #include "arg_router/utility/terminal.hpp"
15 #include "arg_router/utility/tuple_iterator.hpp"
16 
17 #include <iomanip>
18 #include <limits>
19 
20 namespace arg_router::policy
21 {
28 namespace help_formatter_component
29 {
35 template <std::size_t Indent>
37 {
38  static_assert(Indent > 0, "Indent must be greater than zero");
39 
40 public:
50  template <std::size_t DescStart, std::size_t Depth, typename HelpData>
51  void format(std::ostream& stream, std::size_t columns)
52  {
53  if constexpr (!HelpData::label::empty()) {
54  constexpr auto indent = indent_size<Depth>();
55  stream << utility::create_sequence_cts_t<indent, ' '>::get() << HelpData::label::get();
56 
57  if constexpr (!HelpData::description::empty()) {
58  static_assert(HelpData::description::get().find('\t') == std::string_view::npos,
59  "Help descriptions cannot contain tabs");
60 
61  constexpr auto gap =
62  DescStart - indent - utility::utf8::terminal_width(HelpData::label::get());
63 
64  // Spacing between the end of the args label and start of description
65  stream << utility::create_sequence_cts_t<gap, ' '>::get();
66 
67  // Print the description, breaking if a word will exceed the terminal width
68  for (auto it = utility::utf8::line_iterator{HelpData::description::get(),
69  columns - DescStart};
71  stream << *it;
72 
73  // If there's more data to follow, then add the offset
74  if (++it != utility::utf8::line_iterator{}) {
75  stream << '\n' << utility::create_sequence_cts_t<DescStart, ' '>::get();
76  }
77  }
78  }
79 
80  stream << '\n';
81  }
82  }
83 
93  void format(std::ostream& stream,
94  std::size_t desc_start,
95  std::size_t depth,
96  std::size_t columns,
97  const runtime_help_data& help_data)
98  {
99  if (!help_data.label.empty()) {
100  const auto indent = depth * Indent;
101  set_gap(stream, indent);
102  stream << help_data.label;
103 
104  if (!help_data.description.empty()) {
105  const auto gap =
106  desc_start - indent - utility::utf8::terminal_width(help_data.label);
107 
108  // Spacing between the end of the args label and start of description
109  set_gap(stream, gap);
110 
111  // Print the description, breaking if a word will exceed the terminal width
112  for (auto it =
113  utility::utf8::line_iterator{help_data.description, columns - desc_start};
114  it != utility::utf8::line_iterator{};) {
115  stream << *it;
116 
117  // If there's more data to follow, then add the offset
118  if (++it != utility::utf8::line_iterator{}) {
119  stream << '\n';
120  set_gap(stream, desc_start);
121  }
122  }
123  }
124 
125  stream << '\n';
126  }
127  }
128 
129 private:
130  template <std::size_t Depth>
131  [[nodiscard]] constexpr static std::size_t indent_size() noexcept
132  {
133  return Depth * Indent;
134  }
135 
136  static void set_gap(std::ostream& stream, std::size_t num_chars)
137  {
138  if (num_chars > 0) {
139  stream << std::setw(num_chars) << ' ';
140  }
141  }
142 };
143 
146 {
147 public:
154  template <typename HelpNode>
155  void format(std::ostream& stream)
156  {
157  [[maybe_unused]] constexpr auto name_index =
159  typename HelpNode::policies_type>;
160  [[maybe_unused]] constexpr auto version_index =
162  typename HelpNode::policies_type>;
163  [[maybe_unused]] constexpr auto intro_index =
165  typename HelpNode::policies_type>;
166 
167  // Generate the preamble
168  if constexpr (name_index != std::tuple_size_v<typename HelpNode::policies_type>) {
169  stream << std::tuple_element_t<name_index,
170  typename HelpNode::policies_type>::program_name();
171  if constexpr (version_index != std::tuple_size_v<typename HelpNode::policies_type>) {
172  stream << " "
173  << std::tuple_element_t<version_index,
174  typename HelpNode::policies_type>::program_version();
175  }
176  stream << "\n"
177  << "\n";
178  }
179  if constexpr (intro_index != std::tuple_size_v<typename HelpNode::policies_type>) {
180  stream << std::tuple_element_t<intro_index,
181  typename HelpNode::policies_type>::program_intro()
182  << "\n"
183  << "\n";
184  }
185  }
186 };
187 
190 {
191 public:
198  template <typename HelpNode>
199  void format(std::ostream& stream)
200  {
201  [[maybe_unused]] constexpr auto addendum_index =
203  typename HelpNode::policies_type>;
204 
205  if constexpr (addendum_index != std::tuple_size_v<typename HelpNode::policies_type>) {
206  stream << "\n"
207  << std::tuple_element_t<addendum_index,
208  typename HelpNode::policies_type>::program_addendum()
209  << "\n";
210  }
211  }
212 };
213 } // namespace help_formatter_component
214 
227 template <typename Indent = traits::integral_constant<std::size_t{4}>,
228  typename DescColumnOffset = traits::integral_constant<Indent{} * 2>,
229  typename LineFormatter = help_formatter_component::default_line_formatter<Indent{}>,
230  typename PreambleFormatter = help_formatter_component::default_preamble_formatter,
231  typename AddendumFormatter = help_formatter_component::default_addendum_formatter>
233 {
234  static_assert(traits::has_value_type_v<Indent>, "Indent must have a value_type");
235  static_assert(Indent{} > 0, "Indent value_type must be greater than zero");
236  static_assert(traits::has_value_type_v<DescColumnOffset>,
237  "DescColumnOffset must have a value_type");
238  static_assert(DescColumnOffset{} > 0, "DescColumnOffset value_type must be greater than zero");
239 
240 public:
251  template <typename Node, typename HelpNode, bool Flatten>
252  static void generate_help(std::ostream& stream)
253  {
254  generate_help_impl<Node, HelpNode, Flatten>(stream);
255  }
256 
265  template <typename Node, typename HelpNode, bool Flatten>
266  static void generate_help(std::ostream& stream, const runtime_help_data& help_data)
267  {
268  generate_help_impl<Node, HelpNode, Flatten>(stream, &help_data);
269  }
270 
271 private:
272  template <typename Node, typename HelpNode, bool Flatten>
273  static void generate_help_impl(std::ostream& stream,
274  const runtime_help_data* help_data = nullptr)
275  {
276  static_assert(traits::has_help_data_type_v<Node>,
277  "Node must have a help_data_type to generate help from");
278 
279  using help_data_type = typename Node::template help_data_type<Flatten>;
280 
281  // Write out the preamble
282  auto preamble_formatter = PreambleFormatter{};
283  preamble_formatter.template format<HelpNode>(stream);
284 
285  // Calculate description offset
286  constexpr auto desc_column = description_column_start<0, help_data_type>(0);
287 
288  // Get the current number of console columns, so we can wrap the description nicely. If
289  // the call fails (it returns zero), or the description field start column plus a fixed
290  // offset exceeds the column count, then don't attempt to wrap
291  const auto columns = utility::terminal::columns();
292 
293  // Generate the args output
294  auto line_formatter = LineFormatter{};
295  if (help_data) {
296  line_formatter_dispatch(stream,
297  desc_column,
298  0,
299  columns >= (desc_column + DescColumnOffset{}) ?
300  columns :
301  std::numeric_limits<std::size_t>::max(),
302  line_formatter,
303  *help_data);
304  } else {
305  line_formatter_dispatch<desc_column, 0, help_data_type>(
306  stream,
307  columns >= (desc_column + DescColumnOffset{}) ?
308  columns :
309  std::numeric_limits<std::size_t>::max(),
310  line_formatter);
311  }
312 
313  // Write out the addendum
314  auto addendum_formatter = AddendumFormatter{};
315  addendum_formatter.template format<HelpNode>(stream);
316  }
317 
318  template <std::size_t Depth, typename HelpData>
319  [[nodiscard]] constexpr static std::size_t description_column_start(
320  std::size_t current_max) noexcept
321  {
322  constexpr auto this_row_start =
323  (Depth * Indent{}) + utility::utf8::terminal_width(HelpData::label::get()) + Indent{};
324  current_max = std::max(current_max, this_row_start);
325 
326  utility::tuple_type_iterator<typename HelpData::children>([&](auto i) {
327  using child_type = std::tuple_element_t<i, typename HelpData::children>;
328 
329  current_max = description_column_start<Depth + 1, child_type>(current_max);
330  });
331 
332  return current_max;
333  }
334 
335  template <std::size_t DescStart, std::size_t Depth, typename HelpData>
336  static void line_formatter_dispatch(std::ostream& stream,
337  std::size_t columns,
338  LineFormatter& line_formatter)
339  {
340  line_formatter.template format<DescStart, Depth, HelpData>(stream, columns);
341 
342  utility::tuple_type_iterator<typename HelpData::children>([&](auto i) {
343  using child_type = std::tuple_element_t<i, typename HelpData::children>;
344 
345  line_formatter_dispatch<DescStart, Depth + 1, child_type>(stream,
346  columns,
347  line_formatter);
348  });
349  }
350 
351  static void line_formatter_dispatch(std::ostream& stream,
352  std::size_t desc_start,
353  std::size_t depth,
354  std::size_t columns,
355  LineFormatter& line_formatter,
356  const runtime_help_data& help_data)
357  {
358  line_formatter.format(stream, desc_start, depth, columns, help_data);
359 
360  for (const auto& child_help_data : help_data.children) {
361  line_formatter_dispatch(stream,
362  desc_start,
363  depth + 1,
364  columns,
365  line_formatter,
366  child_help_data);
367  }
368  }
369 };
370 
373 
374 template <typename Indent,
375  typename DescColumnOffset,
376  typename LineFormatter,
377  typename PreambleFormatter>
378 struct is_policy<
379  default_help_formatter_t<Indent, DescColumnOffset, LineFormatter, PreambleFormatter>> :
380  std::true_type {
381 };
382 } // namespace arg_router::policy
static void generate_help(std::ostream &stream, const runtime_help_data &help_data)
void format(std::ostream &stream, std::size_t desc_start, std::size_t depth, std::size_t columns, const runtime_help_data &help_data)
constexpr auto find_specialisation_v
Definition: algorithm.hpp:46
constexpr auto program_version
constexpr auto program_addendum
constexpr auto default_help_formatter
constexpr auto program_name
constexpr auto program_intro
std::integral_constant< decltype(Value), Value > integral_constant
Definition: traits.hpp:210
constexpr std::size_t terminal_width(std::string_view str) noexcept
Definition: utf8.hpp:321
utility::dynamic_string_view label
Node name.
utility::dynamic_string_view description
Node description.