sharg 1.0.0
THE argument parser for bio-c++ tools.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages Concepts
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/sharg-parser/blob/main/LICENSE.md
6// --------------------------------------------------------------------------------------------------------
7
14#pragma once
15
16#include <sharg/auxiliary.hpp>
17#include <sharg/config.hpp>
20#include <sharg/validators.hpp>
21
22#if __has_include(<seqan3/version.hpp>)
23# include <seqan3/version.hpp>
24#endif
25
26namespace sharg::detail
27{
28
33class format_base
34{
35protected:
40 template <typename value_type>
41 static std::string get_type_name_as_string(value_type const & )
42 {
43 using type = std::decay_t<value_type>;
44
45 if constexpr (std::is_same_v<type, int8_t>)
46 return "signed 8 bit integer";
47 else if constexpr (std::is_same_v<type, uint8_t>)
48 return "unsigned 8 bit integer";
49 else if constexpr (std::is_same_v<type, int16_t>)
50 return "signed 16 bit integer";
51 else if constexpr (std::is_same_v<type, uint16_t>)
52 return "unsigned 16 bit integer";
53 else if constexpr (std::is_same_v<type, int32_t>)
54 return "signed 32 bit integer";
55 else if constexpr (std::is_same_v<type, uint32_t>)
56 return "unsigned 32 bit integer";
57 else if constexpr (std::is_same_v<type, int64_t>)
58 return "signed 64 bit integer";
59 else if constexpr (std::is_same_v<type, uint64_t>)
60 return "unsigned 64 bit integer";
61 else if constexpr (std::is_same_v<type, double>)
62 return "double";
63 else if constexpr (std::is_same_v<type, float>)
64 return "float";
65 else if constexpr (std::is_same_v<type, bool>)
66 return "bool";
67 else if constexpr (std::is_same_v<type, char>)
68 return "char";
69 else if constexpr (std::is_same_v<type, std::string>)
70 return "std::string";
71 else if constexpr (std::is_same_v<type, std::filesystem::path>)
72 return "std::filesystem::path";
73 else
74 return sharg::detail::type_name_as_string<value_type>;
75 }
76
81 template <detail::is_container_option container_type>
82 static std::string get_type_name_as_string(container_type const & )
83 {
84 typename container_type::value_type tmp{};
85 return get_type_name_as_string(tmp);
86 }
87
93 template <typename option_value_type>
94 static std::string option_type_and_list_info(option_value_type const & value)
95 {
96 return ("(\\fI" + get_type_name_as_string(value) + "\\fP)");
97 }
98
105 template <detail::is_container_option container_type>
106 static std::string option_type_and_list_info(container_type const & container)
107 {
108 return ("(\\fIList\\fP of \\fI" + get_type_name_as_string(container) + "\\fP)");
109 }
110
118 static std::string prep_id_for_help(char const short_id, std::string const & long_id)
119 {
120 // Build list item term.
121 std::string term;
122 if (short_id != '\0')
123 term = "\\fB-" + std::string(1, short_id) + "\\fP";
124
125 if (short_id != '\0' && !long_id.empty())
126 term.append(", ");
127
128 if (!long_id.empty())
129 term.append("\\fB--" + long_id + "\\fP");
130
131 return term;
132 }
133
140 std::string escape_special_xml_chars(std::string const & original)
141 {
142 std::string escaped;
143 escaped.reserve(original.size()); // will be at least as long
144
145 for (auto c : original)
146 {
147 if (c == '"')
148 escaped.append("&quot;");
149 else if (c == '\'')
150 escaped.append("&apos;");
151 else if (c == '&')
152 escaped.append("&amp;");
153 else if (c == '<')
154 escaped.append("&lt;");
155 else if (c == '>')
156 escaped.append("&gt;");
157 else
158 escaped.push_back(c);
159 }
160
161 return escaped;
162 }
163
170 static std::string expand_multiple_flags(std::string const & flag_cluster)
171 {
172 std::string tmp;
173 auto it{flag_cluster.begin()};
174
175 if (flag_cluster[0] == '-')
176 ++it;
177
178 for (; it != flag_cluster.end() - 1; ++it)
179 tmp.append({'-', *it, ',', ' '});
180
181 tmp.erase(tmp.find_last_of(',')); // remove last ', '
182 tmp.append({'a', 'n', 'd', ' ', '-', flag_cluster[flag_cluster.size() - 1]});
183
184 return tmp;
185 }
186};
187
193template <typename derived_type>
194class format_help_base : public format_base
195{
196private:
200 format_help_base() = default;
201 format_help_base(format_help_base const & pf) = default;
202 format_help_base & operator=(format_help_base const & pf) = default;
203 format_help_base(format_help_base &&) = default;
204 format_help_base & operator=(format_help_base &&) = default;
205 ~format_help_base() = default;
206
211 format_help_base(std::vector<std::string> const & names, bool const advanced) :
212 command_names{names},
213 show_advanced_options{advanced}
214 {}
216
217public:
221 template <typename option_type, typename config_type>
222 void add_option(option_type & value, config_type const & config)
223 {
224 std::string id = prep_id_for_help(config.short_id, config.long_id) + " " + option_type_and_list_info(value);
225 std::string info{config.description};
226 if (config.default_message.empty())
227 info += ((config.required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
228 else
229 info += detail::to_string(" Default: ", config.default_message, ". ");
230
231 info += config.validator.get_help_page_message();
232
233 store_help_page_element(
234 [this, id, info]()
235 {
236 derived_t().print_list_item(id, info);
237 },
238 config);
239 }
240
244 template <typename config_type>
245 void add_flag(bool & SHARG_DOXYGEN_ONLY(value), config_type const & config)
246 {
247 store_help_page_element(
248 [this, id = prep_id_for_help(config.short_id, config.long_id), description = config.description]()
249 {
250 derived_t().print_list_item(id, description);
251 },
252 config);
253 }
254
258 template <typename option_type, typename config_type>
259 void add_positional_option(option_type & value, config_type const & config)
260 {
261 positional_option_calls.push_back(
262 [this, &value, description = config.description, validator = config.validator]()
263 {
264 ++positional_option_count;
265 derived_t().print_list_item(detail::to_string("\\fBARGUMENT-",
266 positional_option_count,
267 "\\fP ",
268 option_type_and_list_info(value)),
269 description +
270 // a list at the end may be empty and thus have a default value
271 ((detail::is_container_option<option_type>)
272 ? detail::to_string(" Default: ", value, ". ")
273 : std::string{" "})
274 + validator.get_help_page_message());
275 });
276 // clang-format on
277 }
278
282 void parse(parser_meta_data & parser_meta)
283 {
284 meta = parser_meta;
285
286 derived_t().print_header();
287
288 if (!meta.synopsis.empty())
289 {
290 derived_t().print_section("Synopsis");
291 derived_t().print_synopsis();
292 }
293
294 if (!meta.description.empty())
295 {
296 derived_t().print_section("Description");
297 for (auto desc : meta.description)
298 print_line(desc);
299 }
300
301 if (!command_names.empty())
302 {
303 derived_t().print_section("Subcommands");
304 derived_t().print_line("This program must be invoked with one of the following subcommands:", false);
305 for (std::string const & name : command_names)
306 derived_t().print_line("- \\fB" + name + "\\fP", false);
307 derived_t().print_line("See the respective help page for further details (e.g. by calling " + meta.app_name
308 + " " + command_names[0] + " -h).",
309 true);
310 derived_t().print_line("The following options below belong to the top-level parser and need to be "
311 "specified \\fBbefore\\fP the subcommand key word. Every argument after the "
312 "subcommand key word is passed on to the corresponding sub-parser.",
313 true);
314 }
315
316 // add positional options if specified
317 if (!positional_option_calls.empty())
318 derived_t().print_section("Positional Arguments");
319
320 // each call will evaluate the function derived_t().print_list_item()
321 for (auto f : positional_option_calls)
322 f();
323
324 // add options and flags if specified
325 if (!parser_set_up_calls.empty())
326 derived_t().print_section("Options");
327
328 // each call will evaluate the function derived_t().print_list_item()
329 for (auto f : parser_set_up_calls)
330 f();
331
332 if (!meta.examples.empty())
333 {
334 derived_t().print_section("Examples");
335 for (auto example : meta.examples)
336 print_line(example);
337 }
338
339 print_version();
340
341 print_legal();
342
343 derived_t().print_footer();
344
345 std::exit(EXIT_SUCCESS); // program should not continue from here
346 }
347
351 void add_section(std::string const & title, bool const advanced_only)
352 {
353 store_help_page_element(
354 [this, title]()
355 {
356 derived_t().print_section(title);
357 },
358 advanced_only,
359 false /* never hidden */);
360 }
361
365 void add_subsection(std::string const & title, bool const advanced_only)
366 {
367 store_help_page_element(
368 [this, title]()
369 {
370 derived_t().print_subsection(title);
371 },
372 advanced_only,
373 false /* never hidden */);
374 }
375
379 void add_line(std::string const & text, bool is_paragraph, bool const advanced_only)
380 {
381 store_help_page_element(
382 [this, text, is_paragraph]()
383 {
384 derived_t().print_line(text, is_paragraph);
385 },
386 advanced_only,
387 false /* never hidden */);
388 }
389
393 void add_list_item(std::string const & key, std::string const & desc, bool const advanced_only)
394 {
395 store_help_page_element(
396 [this, key, desc]()
397 {
398 derived_t().print_list_item(key, desc);
399 },
400 advanced_only,
401 false /* never hidden */);
402 }
403
418 parser_meta_data meta;
419
421 friend derived_type;
422
423protected:
425 derived_type & derived_t()
426 {
427 return static_cast<derived_type &>(*this);
428 }
429
431 void print_synopsis()
432 {
433 for (unsigned i = 0; i < meta.synopsis.size(); ++i)
434 {
435 std::string text = "\\fB";
436 text.append(meta.synopsis[i]);
437 text.insert(text.find_first_of(" \t"), "\\fP");
438
439 derived_t().print_line(text, false);
440 }
441 }
442
446 void print_line(std::string const & text)
447 {
448 derived_t().print_line(text, true);
449 }
450
452 void print_version()
453 {
454 std::string const version_str{sharg::sharg_version_cstring};
455
456 // Print version, date and url.
457 derived_t().print_section("Version");
458 derived_t().print_line(derived_t().in_bold("Last update: ") + meta.date, false);
459 derived_t().print_line(derived_t().in_bold(meta.app_name + " version: ") + meta.version, false);
460 derived_t().print_line(derived_t().in_bold("Sharg version: ") + version_str, false);
461
462#ifdef SEQAN3_VERSION_CSTRING
463 std::string const seqan3_version_str{seqan3::seqan3_version_cstring};
464 derived_t().print_line(derived_t().in_bold("SeqAn version: ") + seqan3_version_str, false);
465#endif
466
467 if (!empty(meta.url))
468 {
469 derived_t().print_section("Url");
470 derived_t().print_line(meta.url, false);
471 }
472 }
473
475 void print_legal()
476 {
477 // Print legal stuff
478 if ((!empty(meta.short_copyright)) || (!empty(meta.long_copyright)) || (!empty(meta.citation))
479 || (!empty(meta.author)) || (!empty(meta.email)))
480 {
481 derived_t().print_section("Legal");
482
483 if (!empty(meta.short_copyright))
484 {
485 derived_t().print_line(derived_t().in_bold(meta.app_name + " Copyright: ") + meta.short_copyright,
486 false);
487 }
488
489 if (!empty(meta.author))
490 {
491 derived_t().print_line(derived_t().in_bold("Author: ") + meta.author, false);
492 }
493
494 if (!empty(meta.email))
495 {
496 derived_t().print_line(derived_t().in_bold("Contact: ") + meta.email, false);
497 }
498
499 derived_t().print_line(derived_t().in_bold("SeqAn Copyright: ")
500 + "2006-2022 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.",
501 false);
502
503 if (!empty(meta.citation))
504 {
505 derived_t().print_line(derived_t().in_bold("In your academic works please cite: ") + meta.citation,
506 false);
507 }
508
509 if (!empty(meta.long_copyright))
510 {
511 derived_t().print_line("For full copyright and/or warranty information see "
512 + derived_t().in_bold("--copyright") + ".",
513 false);
514 }
515 }
516 }
517
519 std::vector<std::function<void()>> parser_set_up_calls;
521 std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
523 unsigned positional_option_count{0};
525 std::vector<std::string> command_names{};
527 bool show_advanced_options{true};
528
529private:
541 void store_help_page_element(std::function<void()> printer, bool const advanced, bool const hidden)
542 {
543 if (!(hidden) && (!(advanced) || show_advanced_options))
544 parser_set_up_calls.push_back(std::move(printer));
545 }
546
557 void store_help_page_element(std::function<void()> printer, config<auto> const & config)
558 {
559 if (!(config.hidden) && (!(config.advanced) || show_advanced_options))
560 parser_set_up_calls.push_back(std::move(printer));
561 }
562};
563
564} // namespace sharg::detail
T append(T... args)
Provides auxiliary information.
T begin(T... args)
Provides sharg::config class.
Provides the concept sharg::detail::is_container_option.
T empty(T... args)
T end(T... args)
T erase(T... args)
T exit(T... args)
T find_first_of(T... args)
T find_last_of(T... args)
T insert(T... args)
T parse(T... args)
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.
constexpr char const * sharg_version_cstring
The full version as null terminated string.
Definition: version.hpp:66