Loading...
Searching...
No Matches
format_parse.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 <concepts>
#include <seqan3/std/charconv>
#include <sstream>
#include <string>
#include <vector>
#include <seqan3/argument_parser/detail/format_base.hpp>
#include <seqan3/utility/char_operations/predicate.hpp>
namespace seqan3::detail
{
class format_parse : public format_base
{
public:
format_parse() = delete;
format_parse(format_parse const & pf) = default;
format_parse & operator=(format_parse const & pf) = default;
format_parse(format_parse &&) = default;
format_parse & operator=(format_parse &&) = default;
~format_parse() = default;
format_parse(int const, std::vector<std::string> argv_) : argv{std::move(argv_)}
{}
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 & SEQAN3_DOXYGEN_ONLY(desc),
option_spec const spec,
validator_type && option_validator)
{
option_calls.push_back(
[this, &value, short_id, long_id, spec, option_validator]()
{
get_option(value, short_id, long_id, spec, option_validator);
});
}
void add_flag(bool & value,
char const short_id,
std::string const & long_id,
std::string const & SEQAN3_DOXYGEN_ONLY(desc),
option_spec const & SEQAN3_DOXYGEN_ONLY(spec))
{
flag_calls.push_back(
[this, &value, short_id, long_id]()
{
get_flag(value, short_id, long_id);
});
}
template <typename option_type, typename validator_type>
void add_positional_option(option_type & value,
std::string const & SEQAN3_DOXYGEN_ONLY(desc),
validator_type && option_validator)
{
positional_option_calls.push_back(
[this, &value, option_validator]()
{
get_positional_option(value, option_validator);
});
}
void parse(argument_parser_meta_data const & /*meta*/)
{
end_of_options_it = std::find(argv.begin(), argv.end(), "--");
// parse options first, because we need to rule out -keyValue pairs
// (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
for (auto && f : option_calls)
f();
for (auto && f : flag_calls)
f();
check_for_unknown_ids();
if (end_of_options_it != argv.end())
*end_of_options_it = ""; // remove -- before parsing positional arguments
for (auto && f : positional_option_calls)
f();
check_for_left_over_args();
}
// functions are not needed for command line parsing but are part of the format interface.
void add_section(std::string const &, option_spec const)
{}
void add_subsection(std::string const &, option_spec const)
{}
void add_line(std::string const &, bool, option_spec const)
{}
void add_list_item(std::string const &, std::string const &, option_spec const)
{}
template <typename id_type>
static bool is_empty_id(id_type const & id)
{
if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
return id.empty();
else // char
return is_char<'\0'>(id);
}
template <typename iterator_type, typename id_type>
static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
{
if (is_empty_id(id))
return end_it;
return (std::find_if(begin_it,
end_it,
[&](std::string const & current_arg)
{
std::string full_id = prepend_dash(id);
if constexpr (std::same_as<id_type, char>) // short id
{
// check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
// "-ovalue", "-o=value", and "-o value".
return current_arg.substr(0, full_id.size()) == full_id;
}
else
{
// only "--opt Value" or "--opt=Value" are valid
return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
(current_arg.size() == full_id.size()
|| current_arg[full_id.size()] == '='); // space or `=`
}
}));
}
private:
enum class option_parse_result
{
success,
error,
overflow_error
};
static std::string prepend_dash(std::string const & long_id)
{
return {"--" + long_id};
}
static std::string prepend_dash(char const short_id)
{
return {'-', short_id};
}
std::string combine_option_names(char const short_id, std::string const & long_id)
{
if (short_id == '\0')
return prepend_dash(long_id);
else if (long_id.empty())
return prepend_dash(short_id);
else // both are set (note: both cannot be empty, this is caught before)
return prepend_dash(short_id) + "/" + prepend_dash(long_id);
}
bool flag_is_set(std::string const & long_id)
{
auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
if (it != end_of_options_it)
*it = ""; // remove seen flag
return (it != end_of_options_it);
}
bool flag_is_set(char const short_id)
{
// short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
for (std::string & arg : argv)
{
if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
{
auto pos = arg.find(short_id);
if (pos != std::string::npos)
{
arg.erase(pos, 1); // remove seen bool
if (arg == "-") // if flag is empty now
arg = "";
return true;
}
}
}
return false;
}
template <typename option_t>
requires input_stream_over<std::istringstream, option_t>
option_parse_result parse_option_value(option_t & value, std::string const & in)
{
std::istringstream stream{in};
stream >> value;
if (stream.fail() || !stream.eof())
return option_parse_result::error;
return option_parse_result::success;
}
template <named_enumeration option_t>
option_parse_result parse_option_value(option_t & value, std::string const & in)
{
auto map = seqan3::enumeration_names<option_t>;
if (auto it = map.find(in); it == map.end())
{
std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
std::ranges::sort(key_value_pairs,
[](auto pair1, auto pair2)
{
if constexpr (std::totally_ordered<option_t>)
{
if (pair1.second != pair2.second)
return pair1.second < pair2.second;
}
return pair1.first < pair2.first;
});
throw user_input_error{detail::to_string("You have chosen an invalid input value: ",
in,
". Please use one of: ",
key_value_pairs | std::views::keys)};
}
else
{
value = it->second;
}
return option_parse_result::success;
}
option_parse_result parse_option_value(std::string & value, std::string const & in)
{
value = in;
return option_parse_result::success;
}
template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
requires requires (format_parse_t fp,
typename container_option_t::value_type & container_value,
std::string const & in) {
{ fp.parse_option_value(container_value, in) } -> std::same_as<option_parse_result>;
}
option_parse_result parse_option_value(container_option_t & value, std::string const & in)
{
typename container_option_t::value_type tmp{};
auto res = parse_option_value(tmp, in);
if (res == option_parse_result::success)
value.push_back(tmp);
return res;
}
template <arithmetic option_t>
requires input_stream_over<std::istringstream, option_t>
option_parse_result parse_option_value(option_t & value, std::string const & in)
{
auto res = std::from_chars(&in[0], &in[in.size()], value);
if (res.ec == std::errc::result_out_of_range)
return option_parse_result::overflow_error;
else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
return option_parse_result::error;
return option_parse_result::success;
}
option_parse_result parse_option_value(bool & value, std::string const & in)
{
if (in == "0")
value = false;
else if (in == "1")
value = true;
else if (in == "true")
value = true;
else if (in == "false")
value = false;
else
return option_parse_result::error;
return option_parse_result::success;
}
template <typename option_type>
void throw_on_input_error(option_parse_result const res,
std::string const & option_name,
std::string const & input_value)
{
std::string msg{"Value parse failed for " + option_name + ": "};
if (res == option_parse_result::error)
{
throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
+ get_type_name_as_string(option_type{}) + "."};
}
if constexpr (arithmetic<option_type>)
{
if (res == option_parse_result::overflow_error)
{
throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
+ std::to_string(std::numeric_limits<option_type>::min()) + ","
+ std::to_string(std::numeric_limits<option_type>::max()) + "]."};
}
}
assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
}
template <typename option_type, typename id_type>
bool identify_and_retrieve_option_value(option_type & value,
std::vector<std::string>::iterator & option_it,
id_type const & id)
{
if (option_it != end_of_options_it)
{
std::string input_value;
size_t id_size = (prepend_dash(id)).size();
if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
{
if ((*option_it)[id_size] == '=') // -key=value
{
if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
throw too_few_arguments("Missing value for option " + prepend_dash(id));
input_value = (*option_it).substr(id_size + 1);
}
else // -kevValue
{
input_value = (*option_it).substr(id_size);
}
*option_it = ""; // remove used identifier-value pair
}
else // -key value
{
*option_it = ""; // remove used identifier
++option_it;
if (option_it == end_of_options_it) // should not happen
throw too_few_arguments("Missing value for option " + prepend_dash(id));
input_value = *option_it;
*option_it = ""; // remove value
}
auto res = parse_option_value(value, input_value);
throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
return true;
}
return false;
}
template <typename option_type, typename id_type>
bool get_option_by_id(option_type & value, id_type const & id)
{
auto it = find_option_id(argv.begin(), end_of_options_it, id);
if (it != end_of_options_it)
identify_and_retrieve_option_value(value, it, id);
if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
throw option_declared_multiple_times("Option " + prepend_dash(id)
+ " is no list/container but declared multiple times.");
return (it != end_of_options_it); // first search was successful or not
}
template <detail::is_container_option option_type, typename id_type>
bool get_option_by_id(option_type & value, id_type const & id)
{
auto it = find_option_id(argv.begin(), end_of_options_it, id);
bool seen_at_least_once{it != end_of_options_it};
if (seen_at_least_once)
value.clear();
while (it != end_of_options_it)
{
identify_and_retrieve_option_value(value, it, id);
it = find_option_id(it, end_of_options_it, id);
}
return seen_at_least_once;
}
void check_for_unknown_ids()
{
for (auto it = argv.begin(); it != end_of_options_it; ++it)
{
std::string arg{*it};
if (!arg.empty() && arg[0] == '-') // may be an identifier
{
if (arg == "-")
{
continue; // positional option
}
else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
{
throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
+ ". In case this is meant to be a non-option/argument/parameter, "
+ "please specify the start of arguments with '--'. "
+ "See -h/--help for program information.");
}
else // unknown short or long option
{
throw unknown_option("Unknown option " + arg
+ ". In case this is meant to be a non-option/argument/parameter, "
+ "please specify the start of non-options with '--'. "
+ "See -h/--help for program information.");
}
}
}
}
void check_for_left_over_args()
{
if (std::find_if(argv.begin(),
argv.end(),
[](std::string const & s)
{
return (s != "");
})
!= argv.end())
throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
}
template <typename option_type, typename validator_type>
void get_option(option_type & value,
char const short_id,
std::string const & long_id,
option_spec const spec,
validator_type && validator)
{
bool short_id_is_set{get_option_by_id(value, short_id)};
bool long_id_is_set{get_option_by_id(value, long_id)};
// if value is no container we need to check for multiple declarations
if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id)
+ " is no list/container but specified multiple times");
if (short_id_is_set || long_id_is_set)
{
try
{
validator(value);
}
catch (std::exception & ex)
{
throw validation_error(std::string("Validation failed for option ")
+ combine_option_names(short_id, long_id) + ": " + ex.what());
}
}
else // option is not set
{
// check if option is required
if (spec & option_spec::required)
throw required_option_missing("Option " + combine_option_names(short_id, long_id)
+ " is required but not set.");
}
}
void get_flag(bool & value, char const short_id, std::string const & long_id)
{
value = flag_is_set(short_id) || flag_is_set(long_id);
}
template <typename option_type, typename validator_type>
void get_positional_option(option_type & value, validator_type && validator)
{
++positional_option_count;
auto it = std::find_if(argv.begin(),
argv.end(),
[](std::string const & s)
{
return (s != "");
});
if (it == argv.end())
throw too_few_arguments("Not enough positional arguments provided (Need at least "
+ std::to_string(positional_option_calls.size())
+ "). See -h/--help for more information.");
if constexpr (detail::is_container_option<
option_type>) // vector/list will be filled with all remaining arguments
{
assert(positional_option_count == positional_option_calls.size()); // checked on set up.
value.clear();
while (it != argv.end())
{
auto res = parse_option_value(value, *it);
std::string id = "positional option" + std::to_string(positional_option_count);
throw_on_input_error<option_type>(res, id, *it);
*it = ""; // remove arg from argv
it = std::find_if(it,
argv.end(),
[](std::string const & s)
{
return (s != "");
});
++positional_option_count;
}
}
else
{
auto res = parse_option_value(value, *it);
std::string id = "positional option" + std::to_string(positional_option_count);
throw_on_input_error<option_type>(res, id, *it);
*it = ""; // remove arg from argv
}
try
{
validator(value);
}
catch (std::exception & ex)
{
throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
+ ": " + ex.what());
}
}
std::vector<std::function<void()>> option_calls;
std::vector<std::function<void()>> flag_calls;
std::vector<std::function<void()>> positional_option_calls;
unsigned positional_option_count{0};
std::vector<std::string> argv;
std::vector<std::string>::iterator end_of_options_it;
};
} // namespace seqan3::detail
charconv
The <charconv> header from C++17's standard library.
std::empty
T empty(T... args)
std::istringstream::eof
T eof(T... args)
std::istringstream::fail
T fail(T... args)
std::find
T find(T... args)
format_base.hpp
Provides the format_base struct containing all helper functions that are needed in all formats.
seqan3::enumeration_names
auto const enumeration_names
Return a conversion map from std::string_view to option_type.
Definition auxiliary.hpp:162
seqan3::option_spec
option_spec
Used to further specify argument_parser options/flags.
Definition auxiliary.hpp:249
seqan3::required
@ required
Definition auxiliary.hpp:251
seqan3::is_char
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition predicate.hpp:60
seqan3::pack_traits::size
constexpr size_t size
The size of a type pack.
Definition type_pack/traits.hpp:143
std::numeric_limits::max
T max(T... args)
std::numeric_limits::min
T min(T... args)
std::from_chars
from_chars_result from_chars(char const *first, char const *last, floating_point_type &value, chars_format fmt=chars_format::general) noexcept
Parse a char sequence into an floating point value.
Definition charconv:252
predicate.hpp
Provides character predicates for tokenisation.
std::string::size
T size(T... args)
sstream
string
std::string::substr
T substr(T... args)
std::to_string
T to_string(T... args)
vector
std::exception::what
T what(T... args)
Hide me