SeqAn3  3.2.0
The Modern C++ library for sequence analysis.
format_base.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2022, Knut Reinert & MPI für molekulare Genetik
4 // This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5 // shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6 // -----------------------------------------------------------------------------------------------------
7 
14 #pragma once
15 
16 #include <filesystem>
17 #include <iostream>
18 #include <limits>
19 #include <sstream>
20 #include <string>
21 
27 #include <seqan3/version.hpp>
28 
29 namespace seqan3::detail
30 {
31 
36 class format_base
37 {
38 protected:
43  template <typename value_type>
44  static std::string get_type_name_as_string(value_type const & )
45  {
46  using type = std::decay_t<value_type>;
47  using types = type_list<int8_t,
48  uint8_t,
49  int16_t,
50  uint16_t,
51  int32_t,
52  uint32_t,
53  int64_t,
54  uint64_t,
55  double,
56  float,
57  bool,
58  char,
61  std::vector<std::string> names{"signed 8 bit integer",
62  "unsigned 8 bit integer",
63  "signed 16 bit integer",
64  "unsigned 16 bit integer",
65  "signed 32 bit integer",
66  "unsigned 32 bit integer",
67  "signed 64 bit integer",
68  "unsigned 64 bit integer",
69  "double",
70  "float",
71  "bool",
72  "char",
73  "std::string",
74  "std::filesystem::path"};
75 
76  if constexpr (list_traits::contains<type, types>)
77  return names[list_traits::find<type, types>];
78  else
79  return detail::type_name_as_string<value_type>;
80  }
81 
86  template <detail::is_container_option container_type>
87  static std::string get_type_name_as_string(container_type const & )
88  {
89  typename container_type::value_type tmp{};
90  return get_type_name_as_string(tmp);
91  }
92 
98  template <typename option_value_type>
99  static std::string option_type_and_list_info(option_value_type const & value)
100  {
101  return ("(\\fI" + get_type_name_as_string(value) + "\\fP)");
102  }
103 
110  template <detail::is_container_option container_type>
111  static std::string option_type_and_list_info(container_type const & container)
112  {
113  return ("(\\fIList\\fP of \\fI" + get_type_name_as_string(container) + "\\fP)");
114  }
115 
123  static std::string prep_id_for_help(char const short_id, std::string const & long_id)
124  {
125  // Build list item term.
126  std::string term;
127  if (short_id != '\0')
128  term = "\\fB-" + std::string(1, short_id) + "\\fP";
129 
130  if (short_id != '\0' && !long_id.empty())
131  term.append(", ");
132 
133  if (!long_id.empty())
134  term.append("\\fB--" + long_id + "\\fP");
135 
136  return term;
137  }
138 
145  std::string escape_special_xml_chars(std::string const & original)
146  {
147  std::string escaped;
148  escaped.reserve(original.size()); // will be at least as long
149 
150  for (auto c : original)
151  {
152  if (c == '"')
153  escaped.append("&quot;");
154  else if (c == '\'')
155  escaped.append("&apos;");
156  else if (c == '&')
157  escaped.append("&amp;");
158  else if (c == '<')
159  escaped.append("&lt;");
160  else if (c == '>')
161  escaped.append("&gt;");
162  else
163  escaped.push_back(c);
164  }
165 
166  return escaped;
167  }
168 
175  static std::string expand_multiple_flags(std::string const & flag_cluster)
176  {
177  std::string tmp;
178  auto it{flag_cluster.begin()};
179 
180  if (flag_cluster[0] == '-')
181  ++it;
182 
183  for (; it != flag_cluster.end() - 1; ++it)
184  tmp.append({'-', *it, ',', ' '});
185 
186  tmp.erase(tmp.find_last_of(',')); // remove last ', '
187  tmp.append({'a', 'n', 'd', ' ', '-', flag_cluster[flag_cluster.size() - 1]});
188 
189  return tmp;
190  }
191 };
192 
198 template <typename derived_type>
199 class format_help_base : public format_base
200 {
201 private:
205  format_help_base() = default;
206  format_help_base(format_help_base const & pf) = default;
207  format_help_base & operator=(format_help_base const & pf) = default;
208  format_help_base(format_help_base &&) = default;
209  format_help_base & operator=(format_help_base &&) = default;
210  ~format_help_base() = default;
211 
216  format_help_base(std::vector<std::string> const & names, bool const advanced) :
217  command_names{names},
218  show_advanced_options{advanced}
219  {}
221 
222 public:
226  template <typename option_type, typename validator_type>
227  void add_option(option_type & value,
228  char const short_id,
229  std::string const & long_id,
230  std::string const & desc,
231  option_spec const spec,
232  validator_type && option_validator)
233  {
234  std::string id = prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value);
235  std::string info{desc};
236  info += ((spec & option_spec::required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
237  info += option_validator.get_help_page_message();
238  store_help_page_element(
239  [this, id, info]()
240  {
241  derived_t().print_list_item(id, info);
242  },
243  spec);
244  }
245 
249  void add_flag(bool & SEQAN3_DOXYGEN_ONLY(value),
250  char const short_id,
251  std::string const & long_id,
252  std::string const & desc,
253  option_spec const spec)
254  {
255  std::string id = prep_id_for_help(short_id, long_id);
256  store_help_page_element(
257  [this, id, desc]()
258  {
259  derived_t().print_list_item(id, desc);
260  },
261  spec);
262  }
263 
267  template <typename option_type, typename validator_type>
268  void add_positional_option(option_type & value, std::string const & desc, validator_type & option_validator)
269  {
270  std::string msg = option_validator.get_help_page_message();
271 
272  positional_option_calls.push_back(
273  [this, &value, desc, msg]()
274  {
275  ++positional_option_count;
276  derived_t().print_list_item(detail::to_string("\\fBARGUMENT-",
277  positional_option_count,
278  "\\fP ",
279  option_type_and_list_info(value)),
280  desc +
281  // a list at the end may be empty and thus have a default value
282  ((detail::is_container_option<option_type>)
283  ? detail::to_string(" Default: ", value, ". ")
284  : std::string{" "})
285  + msg);
286  });
287  }
288 
292  void parse(argument_parser_meta_data & parser_meta)
293  {
294  meta = parser_meta;
295 
296  derived_t().print_header();
297 
298  if (!meta.synopsis.empty())
299  {
300  derived_t().print_section("Synopsis");
301  derived_t().print_synopsis();
302  }
303 
304  if (!meta.description.empty())
305  {
306  derived_t().print_section("Description");
307  for (auto desc : meta.description)
308  print_line(desc);
309  }
310 
311  if (!command_names.empty())
312  {
313  derived_t().print_section("Subcommands");
314  derived_t().print_line("This program must be invoked with one of the following subcommands:", false);
315  for (std::string const & name : command_names)
316  derived_t().print_line("- \\fB" + name + "\\fP", false);
317  derived_t().print_line("See the respective help page for further details (e.g. by calling " + meta.app_name
318  + " " + command_names[0] + " -h).",
319  true);
320  derived_t().print_line("The following options below belong to the top-level parser and need to be "
321  "specified \\fBbefore\\fP the subcommand key word. Every argument after the "
322  "subcommand key word is passed on to the corresponding sub-parser.",
323  true);
324  }
325 
326  // add positional options if specified
327  if (!positional_option_calls.empty())
328  derived_t().print_section("Positional Arguments");
329 
330  // each call will evaluate the function derived_t().print_list_item()
331  for (auto f : positional_option_calls)
332  f();
333 
334  // add options and flags if specified
335  if (!parser_set_up_calls.empty())
336  derived_t().print_section("Options");
337 
338  // each call will evaluate the function derived_t().print_list_item()
339  for (auto f : parser_set_up_calls)
340  f();
341 
342  if (!meta.examples.empty())
343  {
344  derived_t().print_section("Examples");
345  for (auto example : meta.examples)
346  print_line(example);
347  }
348 
349  print_version();
350 
351  print_legal();
352 
353  derived_t().print_footer();
354 
355  std::exit(EXIT_SUCCESS); // program should not continue from here
356  }
357 
361  void add_section(std::string const & title, option_spec const spec)
362  {
363  store_help_page_element(
364  [this, title]()
365  {
366  derived_t().print_section(title);
367  },
368  spec);
369  }
370 
374  void add_subsection(std::string const & title, option_spec const spec)
375  {
376  store_help_page_element(
377  [this, title]()
378  {
379  derived_t().print_subsection(title);
380  },
381  spec);
382  }
383 
387  void add_line(std::string const & text, bool is_paragraph, option_spec const spec)
388  {
389  store_help_page_element(
390  [this, text, is_paragraph]()
391  {
392  derived_t().print_line(text, is_paragraph);
393  },
394  spec);
395  }
396 
400  void add_list_item(std::string const & key, std::string const & desc, option_spec const spec)
401  {
402  store_help_page_element(
403  [this, key, desc]()
404  {
405  derived_t().print_list_item(key, desc);
406  },
407  spec);
408  }
409 
424  argument_parser_meta_data meta;
425 
427  friend derived_type;
428 
429 protected:
431  derived_type & derived_t()
432  {
433  return static_cast<derived_type &>(*this);
434  }
435 
437  void print_synopsis()
438  {
439  for (unsigned i = 0; i < meta.synopsis.size(); ++i)
440  {
441  std::string text = "\\fB";
442  text.append(meta.synopsis[i]);
443  text.insert(text.find_first_of(" \t"), "\\fP");
444 
445  derived_t().print_line(text, false);
446  }
447  }
448 
452  void print_line(std::string const & text)
453  {
454  derived_t().print_line(text, true);
455  }
456 
458  void print_version()
459  {
460  std::string const version_str{seqan3_version_cstring};
461 
462  // Print version, date and url.
463  derived_t().print_section("Version");
464  derived_t().print_line(derived_t().in_bold("Last update: ") + meta.date, false);
465  derived_t().print_line(derived_t().in_bold(meta.app_name + " version: ") + meta.version, false);
466  derived_t().print_line(derived_t().in_bold("SeqAn version: ") + version_str, false);
467 
468  if (!empty(meta.url))
469  {
470  derived_t().print_section("Url");
471  derived_t().print_line(meta.url, false);
472  }
473  }
474 
476  void print_legal()
477  {
478  // Print legal stuff
479  if ((!empty(meta.short_copyright)) || (!empty(meta.long_copyright)) || (!empty(meta.citation))
480  || (!empty(meta.author)) || (!empty(meta.email)))
481  {
482  derived_t().print_section("Legal");
483 
484  if (!empty(meta.short_copyright))
485  {
486  derived_t().print_line(derived_t().in_bold(meta.app_name + " Copyright: ") + meta.short_copyright,
487  false);
488  }
489 
490  if (!empty(meta.author))
491  {
492  derived_t().print_line(derived_t().in_bold("Author: ") + meta.author, false);
493  }
494 
495  if (!empty(meta.email))
496  {
497  derived_t().print_line(derived_t().in_bold("Contact: ") + meta.email, false);
498  }
499 
500  derived_t().print_line(derived_t().in_bold("SeqAn Copyright: ")
501  + "2006-2022 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.",
502  false);
503 
504  if (!empty(meta.citation))
505  {
506  derived_t().print_line(derived_t().in_bold("In your academic works please cite: ") + meta.citation,
507  false);
508  }
509 
510  if (!empty(meta.long_copyright))
511  {
512  derived_t().print_line("For full copyright and/or warranty information see "
513  + derived_t().in_bold("--copyright") + ".",
514  false);
515  }
516  }
517  }
518 
520  std::vector<std::function<void()>> parser_set_up_calls;
522  std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
524  unsigned positional_option_count{0};
526  std::vector<std::string> command_names{};
528  bool show_advanced_options{true};
529 
530 private:
541  void store_help_page_element(std::function<void()> printer, option_spec const spec)
542  {
543  if (!(spec & option_spec::hidden) && (!(spec & option_spec::advanced) || show_advanced_options))
544  parser_set_up_calls.push_back(std::move(printer));
545  }
546 };
547 
548 } // namespace seqan3::detail
T append(T... args)
Provides the concept seqan3::detail::is_container_option.
Provides auxiliary information.
T begin(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
Provides parser related exceptions.
T exit(T... args)
T find_first_of(T... args)
T find_last_of(T... args)
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:248
@ advanced
Definition: auxiliary.hpp:255
@ hidden
Definition: auxiliary.hpp:259
@ required
Definition: auxiliary.hpp:250
T insert(T... args)
The (most general) container concept as defined by the standard library.
constexpr char const * seqan3_version_cstring
The full version as null terminated string.
Definition: version.hpp:67
T push_back(T... args)
T reserve(T... args)
T size(T... args)
Provides traits to inspect some information of a type, for example its name.
Provides some standard validators for (positional) options.
Provides SeqAn version macros and global variables.