SeqAn3
format_help.hpp
Go to the documentation of this file.
1 // ============================================================================
2 // SeqAn - The Library for Sequence Analysis
3 // ============================================================================
4 //
5 // Copyright (c) 2006-2018, Knut Reinert & Freie Universitaet Berlin
6 // Copyright (c) 2016-2018, Knut Reinert & MPI Molekulare Genetik
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are met:
11 //
12 // * Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // * Redistributions in binary form must reproduce the above copyright
15 // notice, this list of conditions and the following disclaimer in the
16 // documentation and/or other materials provided with the distribution.
17 // * Neither the name of Knut Reinert or the FU Berlin nor the names of
18 // its contributors may be used to endorse or promote products derived
19 // from this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
25 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31 // DAMAGE.
32 //
33 // ============================================================================
34 
42 #pragma once
43 
44 #include <iostream>
45 
48 #include <seqan3/version.hpp>
49 
50 namespace seqan3::detail
51 {
52 
63 class format_help : public format_base
64 {
65 public:
70  format_help() = default;
71  format_help(format_help const & pf) = default;
72  format_help & operator=(format_help const & pf) = default;
73  format_help(format_help &&) = default;
74  format_help & operator=(format_help &&) = default;
75 
80  format_help(bool advanced) :
81  show_advanced_options(advanced)
82  {}
83 
85  ~format_help() = default;
87 
100  template <typename option_type, typename validator_type>
101  void add_option(option_type & value,
102  char const short_id,
103  std::string const & long_id,
104  std::string const & desc,
105  option_spec const & spec,
106  validator_type && validator)
107  {
108  parser_set_up_calls.push_back([=, &value]()
109  {
110  if (!(spec & option_spec::HIDDEN) && (!(spec & option_spec::ADVANCED) || show_advanced_options))
111  print_list_item(prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value),
112  (desc + " " + validator.get_help_page_message()));
113  });
114  }
115 
123  void add_flag(bool & /*value*/,
124  char const short_id,
125  std::string const & long_id,
126  std::string const & desc,
127  option_spec const & spec)
128  {
129  parser_set_up_calls.push_back([=] ()
130  {
131  if (!(spec & option_spec::HIDDEN) && (!(spec & option_spec::ADVANCED) || show_advanced_options))
132  print_list_item(prep_id_for_help(short_id, long_id), desc);
133  });
134  }
135 
145  template <typename option_type, typename validator_type>
146  void add_positional_option(option_type & value,
147  std::string const & desc,
148  validator_type & validator)
149  {
150  positional_option_calls.push_back([=, &value]()
151  {
152  ++positional_option_count;
153  std::string key{"\\fBARGUMENT-" + std::to_string(positional_option_count) + "\\fP " + option_type_and_list_info(value)};
154  print_list_item(key, (desc + " " + validator.get_help_page_message()));
155  });
156  }
157 
161  void parse(argument_parser_meta_data & parser_meta)
162  {
163  meta = parser_meta;
164 
165  print_header();
166 
167  if (!meta.synopsis.empty())
168  {
169  print_section("Synopsis");
170  print_synopsis();
171  }
172 
173  if (!meta.description.empty())
174  {
175  print_section("Description");
176  for (auto desc : meta.description)
177  print_line(desc);
178  }
179 
180  // add positional options if specified
181  if (!positional_option_calls.empty())
182  print_section("Positional Arguments");
183 
184  // each call will evaluate the function print_list_item()
185  for (auto f : positional_option_calls)
186  f();
187 
188  // add options and flags if specified
189  if (!parser_set_up_calls.empty())
190  print_section("Options");
191 
192  // each call will evaluate the function print_list_item()
193  for (auto f : parser_set_up_calls)
194  f();
195 
196  if (!meta.examples.empty())
197  {
198  print_section("Examples");
199  for (auto example : meta.examples)
200  print_line(example);
201  }
202 
203  print_footer();
204 
205  throw parser_interruption(); // program should not continue from here
206  }
207 
211  void add_section(std::string const & title)
212  {
213  parser_set_up_calls.push_back([=] ()
214  {
215  print_section(title);
216  });
217  }
218 
222  void add_subsection(std::string const & title)
223  {
224  parser_set_up_calls.push_back([=] ()
225  {
226  print_subsection(title);
227  });
228  }
229 
234  void add_line(std::string const & text, bool line_is_paragraph)
235  {
236  parser_set_up_calls.push_back([=] ()
237  {
238  print_line(text, line_is_paragraph);
239  });
240  }
241 
246  void add_list_item(std::string const & key, std::string const & desc)
247  {
248  parser_set_up_calls.push_back([=] ()
249  {
250  print_list_item(key, desc);
251  });
252  }
253 
268  argument_parser_meta_data meta;
269 
270 protected:
273  struct console_layout_struct
274  {
276  unsigned screenWidth;
278  unsigned defaultScreenWidth;
280  unsigned maximalScreenWidth;
282  unsigned minimalScreenWidth;
284  unsigned leftPadding;
286  unsigned centerPadding;
288  unsigned rightPadding;
290  unsigned leftColumnWidth;
292  unsigned rightColumnWidth;
294  unsigned rightColumnTab;
295 
297  console_layout_struct() :
298  screenWidth{0}, defaultScreenWidth{80}, maximalScreenWidth{120}, minimalScreenWidth{40},
299  leftPadding{4}, centerPadding{2}, rightPadding{2}, leftColumnWidth{4}, rightColumnWidth{0}
300  {
301  // Guess terminal screen width and set into layout.
302  unsigned cols = get_terminal_width();
303  screenWidth = (cols > 0) ? cols : defaultScreenWidth;
304  screenWidth = std::max(screenWidth, minimalScreenWidth);
305  screenWidth = std::min(screenWidth, maximalScreenWidth);
306  screenWidth -= rightPadding;
307 
308  rightColumnWidth = screenWidth - leftPadding - leftColumnWidth - centerPadding - rightPadding;
309  rightColumnTab = leftPadding + leftColumnWidth + centerPadding;
310  }
311  };
312 
314  void print_header()
315  {
316  std::ostream_iterator<char> out(std::cout);
317 
318  std::cout << meta.app_name;
319  if (!empty(meta.short_description))
320  std::cout << " - " << meta.short_description;
321 
322  std::cout << "\n";
323  unsigned len = text_width(meta.app_name) + (empty(meta.short_description) ? 0 : 3) +
324  text_width(meta.short_description);
325  std::fill_n(out, len, '=');
326  std::cout << '\n';
327  }
328 
330  void print_synopsis()
331  {
332  for (unsigned i = 0; i < meta.synopsis.size(); ++i)
333  {
334  std::string text = "\\fB";
335  text.append(meta.app_name);
336  text.append("\\fP ");
337  text.append(meta.synopsis[i]);
338 
339  print_line(text, false);
340  }
341  }
342 
346  void print_section(std::string const & title)
347  {
348  std::ostream_iterator<char> out(std::cout);
349  std::cout << '\n' << to_text("\\fB");
350  std::transform(title.begin(), title.end(), out, [] (unsigned char c) { return std::toupper(c); });
351  std::cout << to_text("\\fP") << '\n';
352  prev_was_paragraph = false;
353  }
354 
358  void print_subsection(std::string const & title)
359  {
360  std::ostream_iterator<char> out(std::cout);
361  std::cout << '\n' << to_text("\\fB");
362  std::fill_n(out, layout.leftPadding / 2, ' ');
363  std::cout << title << to_text("\\fP") << '\n';
364  prev_was_paragraph = false;
365  }
366 
372  void print_line(std::string const & text, bool const line_is_paragraph)
373  {
374  if (prev_was_paragraph)
375  std::cout << '\n';
376 
377  std::ostream_iterator<char> out(std::cout);
378  std::fill_n(out, layout.leftPadding, ' ');
379  print_text(text, layout, layout.leftPadding);
380  prev_was_paragraph = line_is_paragraph;
381  }
382 
386  void print_line(std::string const & text)
387  {
388  print_line(text, true);
389  }
390 
405  void print_list_item(std::string const & term, std::string const & desc)
406  {
407  if (prev_was_paragraph)
408  std::cout << '\n';
409 
410  std::ostream_iterator<char> out(std::cout);
411 
412  // Print term.
413  std::fill_n(out, layout.leftPadding, ' ');
414  std::cout << to_text(term);
415  unsigned pos = layout.leftPadding + term.size();
416  if (pos + layout.centerPadding > layout.rightColumnTab)
417  {
418  std::cout << '\n';
419  pos = 0;
420  }
421  std::fill_n(out, layout.rightColumnTab - pos, ' ');
422  print_text(desc, layout, layout.rightColumnTab);
423 
424  prev_was_paragraph = false;
425  }
426 
428  void print_version()
429  {
430  std::ostream_iterator<char> out(std::cout);
431 
432  // Print version, date and url.
433  std::cout << "\n" << to_text("\\fB") << "VERSION" << to_text("\\fP") << "\n";
434  std::fill_n(out, layout.leftPadding, ' ');
435  std::cout << to_text("\\fB") << "Last update: " << to_text("\\fP") << meta.date << "\n";
436  std::fill_n(out, layout.leftPadding, ' ');
437  std::cout << to_text("\\fB") << meta.app_name << " version: " << to_text("\\fP") << meta.version << "\n";
438  std::fill_n(out, layout.leftPadding, ' ');
439  std::cout << to_text("\\fB") << "SeqAn version: " << to_text("\\fP") << SEQAN3_VERSION_MAJOR << '.'
441 
442  if (!empty(meta.url))
443  {
444  std::cout << "\n" << to_text("\\fB") << "URL" << to_text("\\fP") << "\n";
445  std::fill_n(out, layout.leftPadding, ' ');
446  std::cout << meta.url << "\n";
447  }
448  std::cout << "\n";
449  }
450 
452  void print_footer()
453  {
454  print_version();
455 
456  std::ostream_iterator<char> out(std::cout);
457 
458  // Print legal stuff
459  if ((!empty(meta.short_copyright)) || (!empty(meta.long_copyright)) || (!empty(meta.citation)))
460  {
461  std::cout << "\n" << to_text("\\fB") << "LEGAL" << to_text("\\fP") << "\n";
462 
463  if (!empty(meta.short_copyright))
464  {
465  std::fill_n(out, layout.leftPadding, ' ');
466  std::cout << to_text("\\fB") << meta.app_name << " Copyright: "
467  << to_text("\\fP") << meta.short_copyright << "\n";
468  }
469  std::fill_n(out, layout.leftPadding, ' ');
470  std::cout << to_text("\\fB") << "SeqAn Copyright: " << to_text("\\fP")
471  << "2006-2015 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.\n";
472  if (!empty(meta.citation))
473  {
474  std::fill_n(out, layout.leftPadding, ' ');
475  std::cout << to_text("\\fB") << "In your academic works please cite: " << to_text("\\fP")
476  << meta.citation << "\n";
477  }
478  if (!empty(meta.long_copyright))
479  {
480  std::fill_n(out, layout.leftPadding, ' ');
481  std::cout << "For full copyright and/or warranty information see " << to_text("\\fB")
482  << "--copyright" << to_text("\\fP") << ".\n";
483  }
484  }
485  }
486 
490  std::string to_text(std::string const & str)
491  {
492  std::string result;
493 
494  for (auto it = str.begin(); it != str.end(); ++it)
495  {
496  if (*it == '\\')
497  {
498  // Handle escape sequence, we interpret only "\-", "\fI", and "\fB".
499  ++it;
500  assert(it != str.end());
501  if (*it == '-')
502  {
503  result.push_back(*it);
504  }
505  else if (*it == 'f')
506  {
507  ++it;
508  assert(it != str.end());
509  if (*it == 'I')
510  {
511  if (is_terminal())
512  result.append("\033[4m");
513  }
514  else if (*it == 'B')
515  {
516  if (is_terminal())
517  result.append("\033[1m");
518  }
519  else if (*it == 'P')
520  {
521  if (is_terminal())
522  result.append("\033[0m");
523  }
524  else
525  {
526  result.append("\\f");
527  result.push_back(*it);
528  }
529  }
530  else
531  {
532  result.push_back('\\');
533  result.push_back(*it);
534  }
535  }
536  else
537  {
538  result.push_back(*it);
539  }
540  }
541 
542  return result;
543  }
544 
549  unsigned text_width(std::string const & text)
550  {
551  unsigned result = 0;
552 
553  for (unsigned i = 0; i < text.size(); ++i)
554  {
555  if (text[i] != '\\')
556  {
557  result += 1;
558  continue;
559  }
560 
561  if (i + 1 == text.size())
562  {
563  result += 1; // Will print "\\".
564  continue;
565  }
566 
567  if (text[i + 1] == '\\' || text[i + 1] == '-')
568  {
569  i += 1;
570  result += 1;
571  continue; // Will print '\\' or '-'.
572  }
573 
574  if (i + 2 == text.size())
575  {
576  i += 1;
577  result += 2; // Will print two chars.
578  continue;
579  }
580 
581  if (text[i + 1] == 'f')
582  {
583  if (text[i + 2] == 'B' || text[i + 2] == 'I' || text[i + 2] == 'P')
584  i += 2; // Skip f and {B, I, P}.
585  else
586  result += 1;
587  }
588  }
589 
590  return result;
591  }
592 
598  void print_text(std::string const & text,
599  console_layout_struct const & layout,
600  unsigned const tab)
601  {
602  unsigned pos = tab;
603  std::ostream_iterator<char> out(std::cout);
604 
605  // Tokenize the text.
606  std::istringstream iss(text.c_str());
607  std::vector<std::string> tokens;
608  std::copy(std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>(),
609  std::back_inserter<std::vector<std::string> >(tokens));
610 
611  // Print the text.
612  assert(pos <= tab);
613  std::fill_n(out, tab - pos, ' '); // go to tab
614 
615  pos = tab;
616  typedef std::vector<std::string>::const_iterator TConstIter;
617  for (TConstIter it = tokens.begin(); it != tokens.end(); ++it)
618  {
619  if (it == tokens.begin())
620  {
621  std::cout << to_text(*it);
622  pos += text_width(*it);
623  if (pos > layout.screenWidth)
624  {
625  std::cout << '\n';
626  std::fill_n(out, tab, ' ');
627  pos = tab;
628  }
629  }
630  else
631  {
632  if (pos + 1 + text_width(*it) > layout.screenWidth)
633  {
634  // Would go over screen with next, print current word on next line.
635  std::cout << '\n';
636  fill_n(out, tab, ' ');
637  std::cout << to_text(*it);
638  pos = tab + text_width(*it);
639  }
640  else
641  {
642  std::cout << ' ';
643  std::cout << to_text(*it);
644  pos += text_width(*it) + 1;
645  }
646  }
647  }
648  if (!empty(tokens))
649  std::cout << '\n';
650  }
651 
653  std::vector<std::function<void()>> parser_set_up_calls;
655  std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
657  unsigned positional_option_count{0};
659  bool prev_was_paragraph{false};
661  bool show_advanced_options{false};
663  console_layout_struct layout;
664 };
665 
675 class format_short_help : public format_help
676 {
677 public:
681  void parse(argument_parser_meta_data const & parser_meta)
682  {
683  meta = parser_meta;
684 
685  print_header();
686 
687  if (!parser_meta.synopsis.empty())
688  print_synopsis();
689 
690  print_line("Try -h or --help for more information.\n");
691 
692  throw parser_interruption();
693  }
694 };
695 
705 class format_version : public format_help
706 {
707 public:
720  template <typename option_type, typename validator_type>
721  void add_option(option_type & value,
722  char const short_id,
723  std::string const & long_id,
724  std::string const & desc,
725  option_spec const & spec,
726  validator_type && validator)
727  {
728  parser_set_up_calls.push_back([=, &value]()
729  {
730  if (!(spec & option_spec::HIDDEN) && (!(spec & option_spec::ADVANCED) || show_advanced_options))
731  print_list_item(prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value),
732  (desc + " " + validator.get_help_page_message()));
733  });
734  }
735 
745  void add_flag(bool & value,
746  char const short_id,
747  std::string const & long_id,
748  std::string const & desc,
749  option_spec const & spec)
750  {
751  parser_set_up_calls.push_back([=, &value] ()
752  {
753  if (!(spec & option_spec::HIDDEN) && (!(spec & option_spec::ADVANCED) || show_advanced_options))
754  print_list_item(prep_id_for_help(short_id, long_id), desc);
755  });
756  }
757 
767  template <typename option_type, typename validator_type>
768  void add_positional_option(option_type & value,
769  std::string const & desc,
770  validator_type && validator)
771  {
772  positional_option_calls.push_back([=, &value]()
773  {
774  ++positional_option_count;
775  std::string key{"\\fBARGUMENT " + std::to_string(positional_option_count) + "\\fP " + option_type_and_list_info(value)};
776  print_list_item(key, (desc + " " + validator.get_help_page_message()));
777  });
778  }
779 
783  void parse(argument_parser_meta_data & parser_meta)
784  {
785  meta = parser_meta;
786 
787  print_header();
788  print_version();
789 
790  throw parser_interruption(); // program should not continue from here
791  }
792 };
793 
794 } // namespace seqan3::detail
#define SEQAN3_VERSION_MINOR
The minor version as MACRO.
Definition: version.hpp:49
Contains the format_base struct containing all helper functions that are needed in all formats...
constexpr auto transform
A range adaptor that takes a invocable and returns a view of the elements with the invocable applied...
Definition: transform.hpp:95
Definition: auxiliary.hpp:72
::ranges::copy copy
Alias for ranges::copy. Copies a range of elements to a new location.
Definition: ranges:200
Checks if program is run interactively and retrieves dimensions of terminal (Transferred from seqan2)...
Definition: aligned_sequence_concept.hpp:288
Definition: auxiliary.hpp:68
::ranges::empty empty
Alias for ranges::empty. Checks whether a range is empty.
Definition: ranges:205
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:60
constexpr auto back_inserter(container_t &container)
Create a std::back_insert_iterator for the argument.
Definition: iterator:79
#define SEQAN3_VERSION_PATCH
The patch version as MACRO.
Definition: version.hpp:51
#define SEQAN3_VERSION_MAJOR
The major version as MACRO.
Definition: version.hpp:47
Contains SeqAn version macros and global variables.