SeqAn3  3.0.3
The Modern C++ library for sequence analysis.
format_base.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2021, 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 <seqan3/std/filesystem>
17 #include <iostream>
18 #include <limits>
19 #include <sstream>
20 #include <string>
21 
28 #include <seqan3/version.hpp>
29 
30 namespace seqan3::detail
31 {
32 
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 <sequence_container container_type>
88  requires (!std::is_same_v<container_type, std::string>)
90  static std::string get_type_name_as_string(container_type const & )
91  {
92  typename container_type::value_type tmp{};
93  return get_type_name_as_string(tmp);
94  }
95 
101  template <typename option_value_type>
102  static std::string option_type_and_list_info(option_value_type const & value)
103  {
104  return ("(\\fI" + get_type_name_as_string(value) + "\\fP)");
105  }
106 
113  template <typename container_type>
115  requires sequence_container<container_type> && (!std::is_same_v<container_type, std::string>)
117  static std::string option_type_and_list_info(container_type const & container)
118  {
119  return ("(\\fIList\\fP of \\fI" + get_type_name_as_string(container) + "\\fP)");
120  }
121 
129  static std::string prep_id_for_help(char const short_id, std::string const & long_id)
130  {
131  // Build list item term.
132  std::string term;
133  if (short_id != '\0')
134  term = "\\fB-" + std::string(1, short_id) + "\\fP";
135 
136  if (short_id != '\0' && !long_id.empty())
137  term.append(", ");
138 
139  if (!long_id.empty())
140  term.append("\\fB--" + long_id + "\\fP");
141 
142  return term;
143  }
144 
151  std::string escape_special_xml_chars(std::string const & original)
152  {
153  std::string escaped;
154  escaped.reserve(original.size()); // will be at least as long
155 
156  for (auto c : original)
157  {
158  if (c == '"')
159  escaped.append("&quot;");
160  else if (c == '\'')
161  escaped.append("&apos;");
162  else if (c == '&')
163  escaped.append("&amp;");
164  else if (c == '<')
165  escaped.append("&lt;");
166  else if (c == '>')
167  escaped.append("&gt;");
168  else
169  escaped.push_back(c);
170  }
171 
172  return escaped;
173  }
174 
181  static std::string expand_multiple_flags(std::string const & flag_cluster)
182  {
183  std::string tmp;
184  auto it{flag_cluster.begin()};
185 
186  if (flag_cluster[0] == '-')
187  ++it;
188 
189  for (; it != flag_cluster.end() - 1; ++it)
190  tmp.append("-" + std::string(1, *it) + ", ");
191 
192  tmp.erase(tmp.find_last_of(',')); // remove last ', '
193  tmp.append(" and -" + std::string(1, flag_cluster[flag_cluster.size() - 1]));
194 
195  return tmp;
196  }
197 };
198 
203 template <typename derived_type>
204 class format_help_base : public format_base
205 {
206 private:
210  format_help_base() = default;
211  format_help_base(format_help_base const & pf) = default;
212  format_help_base & operator=(format_help_base const & pf) = default;
213  format_help_base(format_help_base &&) = default;
214  format_help_base & operator=(format_help_base &&) = default;
215  ~format_help_base() = default;
216 
221  format_help_base(std::vector<std::string> const & names, bool const advanced) :
222  command_names{names}, show_advanced_options{advanced}
223  {}
225 
226 public:
230  template <typename option_type, typename validator_type>
231  void add_option(option_type & value,
232  char const short_id,
233  std::string const & long_id,
234  std::string const & desc,
235  option_spec const spec,
236  validator_type && option_validator)
237  {
238  std::string id = prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value);
239  std::string info{desc};
240  info += ((spec & option_spec::required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
241  info += option_validator.get_help_page_message();
242  store_help_page_element([this, id, info] () { derived_t().print_list_item(id, info); }, spec);
243  }
244 
248  void add_flag(bool & SEQAN3_DOXYGEN_ONLY(value),
249  char const short_id,
250  std::string const & long_id,
251  std::string const & desc,
252  option_spec const spec)
253  {
254  std::string id = prep_id_for_help(short_id, long_id);
255  store_help_page_element([this, id, desc] () { derived_t().print_list_item(id, desc); }, spec);
256  }
257 
261  template <typename option_type, typename validator_type>
262  void add_positional_option(option_type & value,
263  std::string const & desc,
264  validator_type & option_validator)
265  {
266  std::string msg = option_validator.get_help_page_message();
267 
268  positional_option_calls.push_back([this, &value, desc, msg] ()
269  {
270  ++positional_option_count;
271  derived_t().print_list_item(detail::to_string("\\fBARGUMENT-", positional_option_count, "\\fP ",
272  option_type_and_list_info(value)),
273  desc +
274  // a list at the end may be empty and thus have a default value
275  ((sequence_container<option_type> && !std::same_as<option_type, std::string>)
276  ? detail::to_string(" Default: ", value, ". ")
277  : std::string{" "}) +
278  msg);
279  });
280  }
281 
285  void parse(argument_parser_meta_data & parser_meta)
286  {
287  meta = parser_meta;
288 
289  derived_t().print_header();
290 
291  if (!meta.synopsis.empty())
292  {
293  derived_t().print_section("Synopsis");
294  derived_t().print_synopsis();
295  }
296 
297  if (!meta.description.empty())
298  {
299  derived_t().print_section("Description");
300  for (auto desc : meta.description)
301  print_line(desc);
302  }
303 
304  if (!command_names.empty())
305  {
306  derived_t().print_section("Subcommands");
307  derived_t().print_line("This program must be invoked with one of the following subcommands:", false);
308  for (std::string const & name : command_names)
309  derived_t().print_line("- \\fB" + name + "\\fP", false);
310  derived_t().print_line("See the respective help page for further details (e.g. by calling " +
311  meta.app_name + " " + command_names[0] + " -h).", true);
312  derived_t().print_line("The following options below belong to the top-level parser and need to be "
313  "specified \\fBbefore\\fP the subcommand key word. Every argument after the "
314  "subcommand key word is passed on to the corresponding sub-parser.", true);
315  }
316 
317  // add positional options if specified
318  if (!positional_option_calls.empty())
319  derived_t().print_section("Positional Arguments");
320 
321  // each call will evaluate the function derived_t().print_list_item()
322  for (auto f : positional_option_calls)
323  f();
324 
325  // add options and flags if specified
326  if (!parser_set_up_calls.empty())
327  derived_t().print_section("Options");
328 
329  // each call will evaluate the function derived_t().print_list_item()
330  for (auto f : parser_set_up_calls)
331  f();
332 
333  if (!meta.examples.empty())
334  {
335  derived_t().print_section("Examples");
336  for (auto example : meta.examples)
337  print_line(example);
338  }
339 
340  print_version();
341 
342  print_legal();
343 
344  derived_t().print_footer();
345 
346  std::exit(EXIT_SUCCESS); // program should not continue from here
347  }
348 
352  void add_section(std::string const & title, option_spec const spec)
353  {
354  store_help_page_element([this, title] () { derived_t().print_section(title); }, spec);
355  }
356 
360  void add_subsection(std::string const & title, option_spec const spec)
361  {
362  store_help_page_element([this, title] () { derived_t().print_subsection(title); }, spec);
363  }
364 
368  void add_line(std::string const & text, bool is_paragraph, option_spec const spec)
369  {
370  store_help_page_element([this, text, is_paragraph] () { derived_t().print_line(text, is_paragraph); }, spec);
371  }
372 
376  void add_list_item(std::string const & key, std::string const & desc, option_spec const spec)
377  {
378  store_help_page_element([this, key, desc] () { derived_t().print_list_item(key, desc); }, spec);
379  }
380 
395  argument_parser_meta_data meta;
396 
398  friend derived_type;
399 
400 protected:
402  derived_type & derived_t()
403  {
404  return static_cast<derived_type &>(*this);
405  }
406 
408  void print_synopsis()
409  {
410  for (unsigned i = 0; i < meta.synopsis.size(); ++i)
411  {
412  std::string text = "\\fB";
413  text.append(meta.synopsis[i]);
414  text.insert(text.find_first_of(" \t"), "\\fP");
415 
416  derived_t().print_line(text, false);
417  }
418  }
419 
423  void print_line(std::string const & text)
424  {
425  derived_t().print_line(text, true);
426  }
427 
429  void print_version()
430  {
431  std::string const version_str{seqan3_version_cstring};
432 
433  // Print version, date and url.
434  derived_t().print_section("Version");
435  derived_t().print_line(derived_t().in_bold("Last update: ") + meta.date, false);
436  derived_t().print_line(derived_t().in_bold(meta.app_name + " version: ") + meta.version, false);
437  derived_t().print_line(derived_t().in_bold("SeqAn version: ") + version_str, false);
438 
439  if (!empty(meta.url))
440  {
441  derived_t().print_section("Url");
442  derived_t().print_line(meta.url, false);
443  }
444  }
445 
447  void print_legal()
448  {
449  // Print legal stuff
450  if ((!empty(meta.short_copyright)) ||
451  (!empty(meta.long_copyright)) ||
452  (!empty(meta.citation)) ||
453  (!empty(meta.author)) ||
454  (!empty(meta.email)))
455  {
456  derived_t().print_section("Legal");
457 
458  if (!empty(meta.short_copyright))
459  {
460  derived_t().print_line(derived_t().in_bold(meta.app_name + " Copyright: ") + meta.short_copyright,
461  false);
462  }
463 
464  if (!empty(meta.author))
465  {
466  derived_t().print_line(derived_t().in_bold("Author: ") + meta.author, false);
467  }
468 
469  if (!empty(meta.email))
470  {
471  derived_t().print_line(derived_t().in_bold("Contact: ") + meta.email, false);
472  }
473 
474  derived_t().print_line(derived_t().in_bold("SeqAn Copyright: ") +
475  "2006-2021 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.", false);
476 
477  if (!empty(meta.citation))
478  {
479  derived_t().print_line(derived_t().in_bold("In your academic works please cite: ") + meta.citation,
480  false);
481  }
482 
483  if (!empty(meta.long_copyright))
484  {
485  derived_t().print_line("For full copyright and/or warranty information see " +
486  derived_t().in_bold("--copyright") + ".",
487  false);
488  }
489  }
490  }
491 
493  std::vector<std::function<void()>> parser_set_up_calls;
495  std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
497  unsigned positional_option_count{0};
499  std::vector<std::string> command_names{};
501  bool show_advanced_options{true};
502 
503 private:
514  void store_help_page_element(std::function<void()> printer, option_spec const spec)
515  {
516  if (!(spec & option_spec::hidden) && (!(spec & option_spec::advanced) || show_advanced_options))
517  parser_set_up_calls.push_back(std::move(printer));
518  }
519 };
520 
521 } // namespace seqan3::detail
T append(T... args)
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)
This header includes C++17 filesystem support and imports it into namespace std::filesystem (independ...
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:238
@ advanced
Definition: auxiliary.hpp:245
@ hidden
Definition: auxiliary.hpp:249
@ required
Definition: auxiliary.hpp:240
auto const move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:74
T insert(T... args)
The (most general) container concept as defined by the standard library.
A more refined container concept than seqan3::container.
constexpr char const * seqan3_version_cstring
The full version as null terminated string.
Definition: version.hpp:73
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.
Adaptations of concepts from the standard library.
Provides traits for seqan3::type_list.
Provides some standard validators for (positional) options.
Provides SeqAn version macros and global variables.