Loading...
Searching...
No Matches
format_base.hpp
Go to the documentation of this file.
// SPDX-FileCopyrightText: 2006-2025 Knut Reinert & Freie Universität Berlin
// SPDX-FileCopyrightText: 2016-2025 Knut Reinert & MPI für molekulare Genetik
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <filesystem>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <seqan3/argument_parser/auxiliary.hpp>
#include <seqan3/argument_parser/detail/concept.hpp>
#include <seqan3/argument_parser/exceptions.hpp>
#include <seqan3/argument_parser/validators.hpp>
#include <seqan3/utility/detail/type_name_as_string.hpp>
#include <seqan3/version.hpp>
namespace seqan3::detail
{
class format_base
{
protected:
template <typename value_type>
static std::string get_type_name_as_string(value_type const & /**/)
{
using type = std::decay_t<value_type>;
using types = type_list<int8_t,
uint8_t,
int16_t,
uint16_t,
int32_t,
uint32_t,
int64_t,
uint64_t,
double,
float,
bool,
char,
std::string,
std::filesystem::path>;
std::vector<std::string> names{"signed 8 bit integer",
"unsigned 8 bit integer",
"signed 16 bit integer",
"unsigned 16 bit integer",
"signed 32 bit integer",
"unsigned 32 bit integer",
"signed 64 bit integer",
"unsigned 64 bit integer",
"double",
"float",
"bool",
"char",
"std::string",
"std::filesystem::path"};
if constexpr (list_traits::contains<type, types>)
return names[list_traits::find<type, types>];
else
return detail::type_name_as_string<value_type>;
}
template <detail::is_container_option container_type>
static std::string get_type_name_as_string(container_type const & /**/)
{
typename container_type::value_type tmp{};
return get_type_name_as_string(tmp);
}
template <typename option_value_type>
static std::string option_type_and_list_info(option_value_type const & value)
{
return ("(\\fI" + get_type_name_as_string(value) + "\\fP)");
}
template <detail::is_container_option container_type>
static std::string option_type_and_list_info(container_type const & container)
{
return ("(\\fIList\\fP of \\fI" + get_type_name_as_string(container) + "\\fP)");
}
static std::string prep_id_for_help(char const short_id, std::string const & long_id)
{
// Build list item term.
std::string term;
if (short_id != '\0')
term = "\\fB-" + std::string(1, short_id) + "\\fP";
if (short_id != '\0' && !long_id.empty())
term.append(", ");
if (!long_id.empty())
term.append("\\fB--" + long_id + "\\fP");
return term;
}
std::string escape_special_xml_chars(std::string const & original)
{
std::string escaped;
escaped.reserve(original.size()); // will be at least as long
for (auto c : original)
{
if (c == '"')
escaped.append("&quot;");
else if (c == '\'')
escaped.append("&apos;");
else if (c == '&')
escaped.append("&amp;");
else if (c == '<')
escaped.append("&lt;");
else if (c == '>')
escaped.append("&gt;");
else
escaped.push_back(c);
}
return escaped;
}
static std::string expand_multiple_flags(std::string const & flag_cluster)
{
std::string tmp;
auto it{flag_cluster.begin()};
if (flag_cluster[0] == '-')
++it;
for (; it != flag_cluster.end() - 1; ++it)
tmp.append({'-', *it, ',', ' '});
tmp.erase(tmp.find_last_of(',')); // remove last ', '
tmp.append({'a', 'n', 'd', ' ', '-', flag_cluster[flag_cluster.size() - 1]});
return tmp;
}
};
template <typename derived_type>
class format_help_base : public format_base
{
private:
format_help_base() = default;
format_help_base(format_help_base const & pf) = default;
format_help_base & operator=(format_help_base const & pf) = default;
format_help_base(format_help_base &&) = default;
format_help_base & operator=(format_help_base &&) = default;
~format_help_base() = default;
format_help_base(std::vector<std::string> const & names, bool const advanced) :
command_names{names},
show_advanced_options{advanced}
{}
public:
template <typename option_type, typename validator_type>
void add_option(option_type & value,
char const short_id,
std::string const & long_id,
std::string const & desc,
option_spec const spec,
validator_type && option_validator)
{
std::string id = prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value);
std::string info{desc};
info += ((spec & option_spec::required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
info += option_validator.get_help_page_message();
store_help_page_element(
[this, id, info]()
{
derived_t().print_list_item(id, info);
},
spec);
}
void add_flag(bool & SEQAN3_DOXYGEN_ONLY(value),
char const short_id,
std::string const & long_id,
std::string const & desc,
option_spec const spec)
{
std::string id = prep_id_for_help(short_id, long_id);
store_help_page_element(
[this, id, desc]()
{
derived_t().print_list_item(id, desc);
},
spec);
}
template <typename option_type, typename validator_type>
void add_positional_option(option_type & value, std::string const & desc, validator_type & option_validator)
{
std::string msg = option_validator.get_help_page_message();
positional_option_calls.push_back(
[this, &value, desc, msg]()
{
++positional_option_count;
derived_t().print_list_item(detail::to_string("\\fBARGUMENT-",
positional_option_count,
"\\fP ",
option_type_and_list_info(value)),
desc +
// a list at the end may be empty and thus have a default value
((detail::is_container_option<option_type>)
? detail::to_string(" Default: ", value, ". ")
: std::string{" "})
+ msg);
});
}
void parse(argument_parser_meta_data & parser_meta)
{
meta = parser_meta;
derived_t().print_header();
if (!meta.synopsis.empty())
{
derived_t().print_section("Synopsis");
derived_t().print_synopsis();
}
if (!meta.description.empty())
{
derived_t().print_section("Description");
for (auto desc : meta.description)
print_line(desc);
}
if (!command_names.empty())
{
derived_t().print_section("Subcommands");
derived_t().print_line("This program must be invoked with one of the following subcommands:", false);
for (std::string const & name : command_names)
derived_t().print_line("- \\fB" + name + "\\fP", false);
derived_t().print_line("See the respective help page for further details (e.g. by calling " + meta.app_name
+ " " + command_names[0] + " -h).",
true);
derived_t().print_line("The following options below belong to the top-level parser and need to be "
"specified \\fBbefore\\fP the subcommand key word. Every argument after the "
"subcommand key word is passed on to the corresponding sub-parser.",
true);
}
// add positional options if specified
if (!positional_option_calls.empty())
derived_t().print_section("Positional Arguments");
// each call will evaluate the function derived_t().print_list_item()
for (auto f : positional_option_calls)
f();
// add options and flags if specified
if (!parser_set_up_calls.empty())
derived_t().print_section("Options");
// each call will evaluate the function derived_t().print_list_item()
for (auto f : parser_set_up_calls)
f();
if (!meta.examples.empty())
{
derived_t().print_section("Examples");
for (auto example : meta.examples)
print_line(example);
}
print_version();
print_legal();
derived_t().print_footer();
std::exit(EXIT_SUCCESS); // program should not continue from here
}
void add_section(std::string const & title, option_spec const spec)
{
store_help_page_element(
[this, title]()
{
derived_t().print_section(title);
},
spec);
}
void add_subsection(std::string const & title, option_spec const spec)
{
store_help_page_element(
[this, title]()
{
derived_t().print_subsection(title);
},
spec);
}
void add_line(std::string const & text, bool is_paragraph, option_spec const spec)
{
store_help_page_element(
[this, text, is_paragraph]()
{
derived_t().print_line(text, is_paragraph);
},
spec);
}
void add_list_item(std::string const & key, std::string const & desc, option_spec const spec)
{
store_help_page_element(
[this, key, desc]()
{
derived_t().print_list_item(key, desc);
},
spec);
}
argument_parser_meta_data meta;
friend derived_type;
protected:
derived_type & derived_t()
{
return static_cast<derived_type &>(*this);
}
void print_synopsis()
{
for (unsigned i = 0; i < meta.synopsis.size(); ++i)
{
std::string text = "\\fB";
text.append(meta.synopsis[i]);
text.insert(text.find_first_of(" \t"), "\\fP");
derived_t().print_line(text, false);
}
}
void print_line(std::string const & text)
{
derived_t().print_line(text, true);
}
void print_version()
{
std::string const version_str{seqan3_version_cstring};
// Print version, date and url.
derived_t().print_section("Version");
derived_t().print_line(derived_t().in_bold("Last update: ") + meta.date, false);
derived_t().print_line(derived_t().in_bold(meta.app_name + " version: ") + meta.version, false);
derived_t().print_line(derived_t().in_bold("SeqAn version: ") + version_str, false);
if (!empty(meta.url))
{
derived_t().print_section("Url");
derived_t().print_line(meta.url, false);
}
}
void print_legal()
{
// Print legal stuff
if ((!empty(meta.short_copyright)) || (!empty(meta.long_copyright)) || (!empty(meta.citation))
|| (!empty(meta.author)) || (!empty(meta.email)))
{
derived_t().print_section("Legal");
if (!empty(meta.short_copyright))
{
derived_t().print_line(derived_t().in_bold(meta.app_name + " Copyright: ") + meta.short_copyright,
false);
}
if (!empty(meta.author))
{
derived_t().print_line(derived_t().in_bold("Author: ") + meta.author, false);
}
if (!empty(meta.email))
{
derived_t().print_line(derived_t().in_bold("Contact: ") + meta.email, false);
}
derived_t().print_line(derived_t().in_bold("SeqAn Copyright: ")
+ "2006-2025 Knut Reinert, FU-Berlin; released under the 3-clause BSDL.",
false);
if (!empty(meta.citation))
{
derived_t().print_line(derived_t().in_bold("In your academic works please cite: ") + meta.citation,
false);
}
if (!empty(meta.long_copyright))
{
derived_t().print_line("For full copyright and/or warranty information see "
+ derived_t().in_bold("--copyright") + ".",
false);
}
}
}
std::vector<std::function<void()>> parser_set_up_calls;
std::vector<std::function<void()>> positional_option_calls; // singled out to be printed on top
unsigned positional_option_count{0};
std::vector<std::string> command_names{};
bool show_advanced_options{true};
private:
void store_help_page_element(std::function<void()> printer, option_spec const spec)
{
if (!(spec & option_spec::hidden) && (!(spec & option_spec::advanced) || show_advanced_options))
parser_set_up_calls.push_back(std::move(printer));
}
};
} // namespace seqan3::detail
std::string::append
T append(T... args)
concept.hpp
Provides the concept seqan3::detail::is_container_option.
auxiliary.hpp
Provides auxiliary information.
std::string::begin
T begin(T... args)
std::string::empty
T empty(T... args)
std::string::end
T end(T... args)
std::string::erase
T erase(T... args)
exceptions.hpp
Provides parser related exceptions.
std::exit
T exit(T... args)
filesystem
std::string::find_first_of
T find_first_of(T... args)
std::string::find_last_of
T find_last_of(T... args)
seqan3::option_spec
option_spec
Used to further specify argument_parser options/flags.
Definition auxiliary.hpp:249
seqan3::advanced
@ advanced
Definition auxiliary.hpp:256
seqan3::hidden
@ hidden
Definition auxiliary.hpp:260
seqan3::required
@ required
Definition auxiliary.hpp:251
seqan3::list_traits::contains
constexpr bool contains
Whether a type occurs in a type list or not.
Definition type_list/traits.hpp:249
std::string::insert
T insert(T... args)
iostream
limits
seqan3::seqan3_version_cstring
constexpr char const * seqan3_version_cstring
The full version as null terminated string.
Definition version.hpp:64
std::string::push_back
T push_back(T... args)
std::string::reserve
T reserve(T... args)
std::string::size
T size(T... args)
sstream
string
type_name_as_string.hpp
Provides traits to inspect some information of a type, for example its name.
validators.hpp
Provides some standard validators for (positional) options.
version.hpp
Provides SeqAn version macros and global variables.
Hide me