SeqAn3  3.1.0
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 
37 class format_base
38 {
39 protected:
44  template <typename value_type>
45  static std::string get_type_name_as_string(value_type const & )
46  {
47  using type = std::decay_t<value_type>;
48  using types = type_list<int8_t,
49  uint8_t,
50  int16_t,
51  uint16_t,
52  int32_t,
53  uint32_t,
54  int64_t,
55  uint64_t,
56  double,
57  float,
58  bool,
59  char,
62  std::vector<std::string> names{"signed 8 bit integer",
63  "unsigned 8 bit integer",
64  "signed 16 bit integer",
65  "unsigned 16 bit integer",
66  "signed 32 bit integer",
67  "unsigned 32 bit integer",
68  "signed 64 bit integer",
69  "unsigned 64 bit integer",
70  "double",
71  "float",
72  "bool",
73  "char",
74  "std::string",
75  "std::filesystem::path"};
76 
77  if constexpr (list_traits::contains<type, types>)
78  return names[list_traits::find<type, types>];
79  else
80  return detail::type_name_as_string<value_type>;
81  }
82 
87  template <sequence_container container_type>
89  requires (!std::is_same_v<container_type, std::string>)
91  static std::string get_type_name_as_string(container_type const & )
92  {
93  typename container_type::value_type tmp{};
94  return get_type_name_as_string(tmp);
95  }
96 
102  template <typename option_value_type>
103  static std::string option_type_and_list_info(option_value_type const & value)
104  {
105  return ("(\\fI" + get_type_name_as_string(value) + "\\fP)");
106  }
107 
114  template <typename container_type>
116  requires sequence_container<container_type> && (!std::is_same_v<container_type, std::string>)
118  static std::string option_type_and_list_info(container_type const & container)
119  {
120  return ("(\\fIList\\fP of \\fI" + get_type_name_as_string(container) + "\\fP)");
121  }
122 
130  static std::string prep_id_for_help(char const short_id, std::string const & long_id)
131  {
132  // Build list item term.
133  std::string term;
134  if (short_id != '\0')
135  term = "\\fB-" + std::string(1, short_id) + "\\fP";
136 
137  if (short_id != '\0' && !long_id.empty())
138  term.append(", ");
139 
140  if (!long_id.empty())
141  term.append("\\fB--" + long_id + "\\fP");
142 
143  return term;
144  }
145 
152  std::string escape_special_xml_chars(std::string const & original)
153  {
154  std::string escaped;
155  escaped.reserve(original.size()); // will be at least as long
156 
157  for (auto c : original)
158  {
159  if (c == '"')
160  escaped.append("&quot;");
161  else if (c == '\'')
162  escaped.append("&apos;");
163  else if (c == '&')
164  escaped.append("&amp;");
165  else if (c == '<')
166  escaped.append("&lt;");
167  else if (c == '>')
168  escaped.append("&gt;");
169  else
170  escaped.push_back(c);
171  }
172 
173  return escaped;
174  }
175 
182  static std::string expand_multiple_flags(std::string const & flag_cluster)
183  {
184  std::string tmp;
185  auto it{flag_cluster.begin()};
186 
187  if (flag_cluster[0] == '-')
188  ++it;
189 
190  for (; it != flag_cluster.end() - 1; ++it)
191  tmp.append("-" + std::string(1, *it) + ", ");
192 
193  tmp.erase(tmp.find_last_of(',')); // remove last ', '
194  tmp.append(" and -" + std::string(1, flag_cluster[flag_cluster.size() - 1]));
195 
196  return tmp;
197  }
198 };
199 
205 template <typename derived_type>
206 class format_help_base : public format_base
207 {
208 private:
212  format_help_base() = default;
213  format_help_base(format_help_base const & pf) = default;
214  format_help_base & operator=(format_help_base const & pf) = default;
215  format_help_base(format_help_base &&) = default;
216  format_help_base & operator=(format_help_base &&) = default;
217  ~format_help_base() = default;
218 
223  format_help_base(std::vector<std::string> const & names, bool const advanced) :
224  command_names{names}, show_advanced_options{advanced}
225  {}
227 
228 public:
232  template <typename option_type, typename validator_type>
233  void add_option(option_type & value,
234  char const short_id,
235  std::string const & long_id,
236  std::string const & desc,
237  option_spec const spec,
238  validator_type && option_validator)
239  {
240  std::string id = prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value);
241  std::string info{desc};
242  info += ((spec & option_spec::required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
243  info += option_validator.get_help_page_message();
244  store_help_page_element([this, id, info] () { derived_t().print_list_item(id, info); }, spec);
245  }
246 
250  void add_flag(bool & SEQAN3_DOXYGEN_ONLY(value),
251  char const short_id,
252  std::string const & long_id,
253  std::string const & desc,
254  option_spec const spec)
255  {
256  std::string id = prep_id_for_help(short_id, long_id);
257  store_help_page_element([this, id, desc] () { derived_t().print_list_item(id, desc); }, spec);
258  }
259 
263  template <typename option_type, typename validator_type>
264  void add_positional_option(option_type & value,
265  std::string const & desc,
266  validator_type & option_validator)
267  {
268  std::string msg = option_validator.get_help_page_message();
269 
270  positional_option_calls.push_back([this, &value, desc, msg] ()
271  {
272  ++positional_option_count;
273  derived_t().print_list_item(detail::to_string("\\fBARGUMENT-", positional_option_count, "\\fP ",
274  option_type_and_list_info(value)),
275  desc +
276  // a list at the end may be empty and thus have a default value
277  ((sequence_container<option_type> && !std::same_as<option_type, std::string>)
278  ? detail::to_string(" Default: ", value, ". ")
279  : std::string{" "}) +
280  msg);
281  });
282  }
283 
287  void parse(argument_parser_meta_data & parser_meta)
288  {
289  meta = parser_meta;
290 
291  derived_t().print_header();
292 
293  if (!meta.synopsis.empty())
294  {
295  derived_t().print_section("Synopsis");
296  derived_t().print_synopsis();
297  }
298 
299  if (!meta.description.empty())
300  {
301  derived_t().print_section("Description");
302  for (auto desc : meta.description)
303  print_line(desc);
304  }
305 
306  if (!command_names.empty())
307  {
308  derived_t().print_section("Subcommands");
309  derived_t().print_line("This program must be invoked with one of the following subcommands:", false);
310  for (std::string const & name : command_names)
311  derived_t().print_line("- \\fB" + name + "\\fP", false);
312  derived_t().print_line("See the respective help page for further details (e.g. by calling " +
313  meta.app_name + " " + command_names[0] + " -h).", true);
314  derived_t().print_line("The following options below belong to the top-level parser and need to be "
315  "specified \\fBbefore\\fP the subcommand key word. Every argument after the "
316  "subcommand key word is passed on to the corresponding sub-parser.", true);
317  }
318 
319  // add positional options if specified
320  if (!positional_option_calls.empty())
321  derived_t().print_section("Positional Arguments");
322 
323  // each call will evaluate the function derived_t().print_list_item()
324  for (auto f : positional_option_calls)
325  f();
326 
327  // add options and flags if specified
328  if (!parser_set_up_calls.empty())
329  derived_t().print_section("Options");
330 
331  // each call will evaluate the function derived_t().print_list_item()
332  for (auto f : parser_set_up_calls)
333  f();
334 
335  if (!meta.examples.empty())
336  {
337  derived_t().print_section("Examples");
338  for (auto example : meta.examples)
339  print_line(example);
340  }
341 
342  print_version();
343 
344  print_legal();
345 
346  derived_t().print_footer();
347 
348  std::exit(EXIT_SUCCESS); // program should not continue from here
349  }
350 
354  void add_section(std::string const & title, option_spec const spec)
355  {
356  store_help_page_element([this, title] () { derived_t().print_section(title); }, spec);
357  }
358 
362  void add_subsection(std::string const & title, option_spec const spec)
363  {
364  store_help_page_element([this, title] () { derived_t().print_subsection(title); }, spec);
365  }
366 
370  void add_line(std::string const & text, bool is_paragraph, option_spec const spec)
371  {
372  store_help_page_element([this, text, is_paragraph] () { derived_t().print_line(text, is_paragraph); }, spec);
373  }
374 
378  void add_list_item(std::string const & key, std::string const & desc, option_spec const spec)
379  {
380  store_help_page_element([this, key, desc] () { derived_t().print_list_item(key, desc); }, spec);
381  }
382 
397  argument_parser_meta_data meta;
398 
400  friend derived_type;
401 
402 protected:
404  derived_type & derived_t()
405  {
406  return static_cast<derived_type &>(*this);
407  }
408 
410  void print_synopsis()
411  {
412  for (unsigned i = 0; i < meta.synopsis.size(); ++i)
413  {
414  std::string text = "\\fB";
415  text.append(meta.synopsis[i]);
416  text.insert(text.find_first_of(" \t"), "\\fP");
417 
418  derived_t().print_line(text, false);
419  }
420  }
421 
425  void print_line(std::string const & text)
426  {
427  derived_t().print_line(text, true);
428  }
429 
431  void print_version()
432  {
433  std::string const version_str{seqan3_version_cstring};
434 
435  // Print version, date and url.
436  derived_t().print_section("Version");
437  derived_t().print_line(derived_t().in_bold("Last update: ") + meta.date, false);
438  derived_t().print_line(derived_t().in_bold(meta.app_name + " version: ") + meta.version, false);
439  derived_t().print_line(derived_t().in_bold("SeqAn version: ") + version_str, false);
440 
441  if (!empty(meta.url))
442  {
443  derived_t().print_section("Url");
444  derived_t().print_line(meta.url, false);
445  }
446  }
447 
449  void print_legal()
450  {
451  // Print legal stuff
452  if ((!empty(meta.short_copyright)) ||
453  (!empty(meta.long_copyright)) ||
454  (!empty(meta.citation)) ||
455  (!empty(meta.author)) ||
456  (!empty(meta.email)))
457  {
458  derived_t().print_section("Legal");
459 
460  if (!empty(meta.short_copyright))
461  {
462  derived_t().print_line(derived_t().in_bold(meta.app_name + " Copyright: ") + meta.short_copyright,
463  false);
464  }
465 
466  if (!empty(meta.author))
467  {
468  derived_t().print_line(derived_t().in_bold("Author: ") + meta.author, false);
469  }
470 
471  if (!empty(meta.email))
472  {
473  derived_t().print_line(derived_t().in_bold("Contact: ") + meta.email, false);
474  }
475 
476  derived_t().print_line(derived_t().in_bold("SeqAn Copyright: ") +
477  "2006-2021 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.", false);
478 
479  if (!empty(meta.citation))
480  {
481  derived_t().print_line(derived_t().in_bold("In your academic works please cite: ") + meta.citation,
482  false);
483  }
484 
485  if (!empty(meta.long_copyright))
486  {
487  derived_t().print_line("For full copyright and/or warranty information see " +
488  derived_t().in_bold("--copyright") + ".",
489  false);
490  }
491  }
492  }
493 
495  std::vector<std::function<void()>> parser_set_up_calls;
497  std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
499  unsigned positional_option_count{0};
501  std::vector<std::string> command_names{};
503  bool show_advanced_options{true};
504 
505 private:
516  void store_help_page_element(std::function<void()> printer, option_spec const spec)
517  {
518  if (!(spec & option_spec::hidden) && (!(spec & option_spec::advanced) || show_advanced_options))
519  parser_set_up_calls.push_back(std::move(printer));
520  }
521 };
522 
523 } // 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)
The <filesystem> header from C++17's standard library.
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:249
@ advanced
Definition: auxiliary.hpp:256
@ hidden
Definition: auxiliary.hpp:260
@ required
Definition: auxiliary.hpp:251
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 for seqan3::type_list.
Provides traits to inspect some information of a type, for example its name.
Adaptations of concepts from the standard library.
Provides some standard validators for (positional) options.
Provides SeqAn version macros and global variables.