-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enum support for the argument parser #1196
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,209 @@ | |
#include <sstream> | ||
#include <vector> | ||
|
||
#include <seqan3/core/detail/customisation_point.hpp> | ||
#include <seqan3/core/detail/debug_stream_type.hpp> | ||
#include <seqan3/core/type_traits/basic.hpp> | ||
#include <seqan3/io/stream/concept.hpp> | ||
#include <seqan3/std/concepts> | ||
#include <seqan3/std/type_traits> | ||
|
||
namespace seqan3::custom | ||
{ | ||
|
||
/*!\brief A type that can be specialised to provide customisation point implementations for the seqan3::argument_parser | ||
* such that third party types may be adapted. | ||
* \tparam t The type you wish to specialise for. | ||
* \ingroup argument_parser | ||
* | ||
* \details | ||
* | ||
* ### Named Enumerations | ||
* | ||
* In order to use a third party type within the seqan3::argument_parser::add_option or | ||
* seqan3::argument_parser::add_positional_option call, you can specialise this struct in the following way: | ||
* | ||
* \include test/snippet/argument_parser/custom_argument_parsing_enumeration.cpp | ||
* | ||
* Please note that by default the `t const`, `t &` and `t const &` specialisations of this class inherit the | ||
* specialisation for `t` so you usually only need to provide a specialisation for `t`. | ||
* | ||
* \note Only use this if you cannot provide respective functions in your namespace. See the tutorial | ||
* \ref tutorial_argument_parser for an example of customising a type within your own namespace. | ||
*/ | ||
template <typename t> | ||
struct argument_parsing | ||
{}; // forward | ||
|
||
//!\cond | ||
template <typename t> | ||
struct argument_parsing<t const> : argument_parsing<t> | ||
{}; | ||
|
||
template <typename t> | ||
struct argument_parsing<t &> : argument_parsing<t> | ||
{}; | ||
|
||
template <typename t> | ||
struct argument_parsing<t const &> : argument_parsing<t> | ||
{}; | ||
//!\endcond | ||
|
||
} // seqan3::custom | ||
|
||
namespace seqan3::detail::adl_only | ||
{ | ||
|
||
//!\brief Poison-pill overload to prevent non-ADL forms of unqualified lookup. | ||
template <typename t> | ||
std::unordered_map<std::string_view, t> enumeration_names(t) = delete; | ||
|
||
/*!\brief Functor definition for seqan3::enumeration_names. | ||
* | ||
* We need a class template here because we need the original option_t next to s_option_t that may be wrapped | ||
* std::type_identity to be default constructible. We need option_t to be default constructible because the | ||
* respective CPO should be callable without a function parameter. | ||
*/ | ||
template <typename option_t> | ||
struct enumeration_names_fn | ||
{ | ||
//!\brief `option_t` with cvref removed and possibly wrapped in std::type_identity. | ||
using s_option_t = std::conditional_t<std::is_nothrow_default_constructible_v<remove_cvref_t<option_t>> && | ||
seqan3::is_constexpr_default_constructible_v<remove_cvref_t<option_t>>, | ||
remove_cvref_t<option_t>, | ||
std::type_identity<option_t>>; | ||
|
||
SEQAN3_CPO_IMPL(1, (deferred_type_t<seqan3::custom::argument_parsing<option_t>, decltype(v)>::enumeration_names)) | ||
SEQAN3_CPO_IMPL(0, (enumeration_names(v))) // ADL | ||
|
||
//!\brief Operator definition. | ||
template <typename dummy = int> // need to make this a template to enforce deferred initialisation | ||
//!\cond | ||
requires requires | ||
{ | ||
{ impl(priority_tag<1>{}, s_option_t{}, dummy{}) }; | ||
std::same_as<decltype(impl(priority_tag<1>{}, s_option_t{}, dummy{})), | ||
std::unordered_map<std::string_view, remove_cvref_t<option_t>>>; | ||
} | ||
//!\endcond | ||
auto operator()() const | ||
{ | ||
return impl(priority_tag<1>{}, s_option_t{}); | ||
} | ||
}; | ||
|
||
} // namespace seqan3::detail::adl_only | ||
|
||
namespace seqan3 | ||
{ | ||
|
||
/*!\name Customisation Points | ||
* \{ | ||
*/ | ||
|
||
/*!\brief Return a conversion map from std::string_view to option_type. | ||
* \tparam your_type Type of the value to retrieve the conversion map for. | ||
* \param value The value is not used, just its type. | ||
* \returns A std::unordered_map<std::string_view, your_type> that maps a string identifier to a value of your_type. | ||
* \ingroup argument_parser | ||
* \details | ||
* | ||
* This is a function object. Invoke it with the parameter(s) specified above. | ||
* | ||
* It acts as a wrapper and looks for two possible implementations (in this order): | ||
* | ||
* 1. A static member `enumeration_names` in `seqan3::custom::argument_parsing<your_type>` that is of type | ||
* `std::unordered_map<std::string_view, your_type>>`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can/should this be const? Is that enforced or unimportant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm I guess it could but I don't think it is important? I don't know... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I think it should be const... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should relax these constraints to just be an "associative range", but that can be done later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I cannot enforce in the concept that the impl with a static member must return a const member while the free function returns a copy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The concept doesn't know about the impls. The concept just accesses the trait / CPO. And that should always be const. |
||
* 2. A free function `enumeration_names(your_type const a)` in the namespace of your type (or as `friend`) which | ||
* returns a `std::unordered_map<std::string_view, your_type>>`. | ||
* | ||
h-2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* ### Example | ||
* | ||
* If you are working on a type in your own namespace, you should implement a free function like this: | ||
* | ||
* \include test/snippet/argument_parser/custom_enumeration.cpp | ||
* | ||
* **Only if you cannot access the namespace of your type to customize** you may specialize | ||
* the seqan3::custom::argument_parsing struct like this: | ||
* | ||
* \include test/snippet/argument_parser/custom_argument_parsing_enumeration.cpp | ||
* | ||
* ### Customisation point | ||
* | ||
* This is a customisation point (see \ref about_customisation). To specify the behaviour for your own type, | ||
* simply provide one of the two functions specified above. | ||
*/ | ||
template <typename option_type> | ||
//!\cond | ||
requires requires { { detail::adl_only::enumeration_names_fn<option_type>{}() }; } | ||
//!\endcond | ||
inline auto const enumeration_names = detail::adl_only::enumeration_names_fn<option_type>{}(); | ||
//!\} | ||
|
||
/*!\interface seqan3::named_enumeration <> | ||
* \brief Checks whether the free function seqan3::enumeration_names can be called on the type. | ||
* \ingroup argument_parser | ||
* \tparam option_type The type to check. | ||
* | ||
* ### Requirements | ||
* | ||
* * A instance of seqan3::enumeration_names<option_type> must exist and be of type | ||
* `std::unordered_map<std::string, option_type>`. | ||
*/ | ||
//!\cond | ||
template <typename option_type> | ||
SEQAN3_CONCEPT named_enumeration = requires | ||
{ | ||
{ seqan3::enumeration_names<option_type> }; | ||
}; | ||
//!\endcond | ||
|
||
/*!\interface seqan3::argument_parser_compatible_option <> | ||
* \brief Checks whether the the type can be used in an add_(positional_)option call on the argument parser. | ||
* \ingroup argument_parser | ||
* \tparam option_type The type to check. | ||
* | ||
* ### Requirements | ||
* | ||
* In order to model this concept, the type must either be streamable to std::istringstream or | ||
* model seqan3::named_enumeration<option_type>. | ||
*/ | ||
//!\cond | ||
template <typename option_type> | ||
SEQAN3_CONCEPT argument_parser_compatible_option = input_stream_over<std::istringstream, option_type> || | ||
named_enumeration<option_type>; | ||
//!\endcond | ||
|
||
/*!\name Formatted output overloads | ||
* \{ | ||
*/ | ||
/*!\brief A type (e.g. an enum) can be made debug streamable by customizing the seqan3::enumeration_names. | ||
* \tparam option_type Type of the enum to be printed. | ||
* \param s The seqan3::debug_stream. | ||
* \param op The value to print. | ||
* \relates seqan3::debug_stream_type | ||
* | ||
* \details | ||
* | ||
* This searches the seqan3::enumeration_names of the respective type for the value \p op and prints the | ||
* respective string if found or '\<UNKNOWN_VALUE\>' if the value cannot be found in the map. | ||
*/ | ||
template <typename char_t, typename option_type> | ||
//!\cond | ||
requires named_enumeration<remove_cvref_t<option_type>> | ||
//!\endcond | ||
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, option_type && op) | ||
{ | ||
for (auto & [key, value] : enumeration_names<option_type>) | ||
{ | ||
if (op == value) | ||
return s << key; | ||
} | ||
|
||
return s << "<UNKNOWN_VALUE>"; | ||
} | ||
//!\} | ||
|
||
/*!\brief Used to further specify argument_parser options/flags. | ||
* \ingroup argument_parser | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#include <system_error> | ||
|
||
#include <seqan3/argument_parser/all.hpp> | ||
#include <seqan3/range/views/get.hpp> | ||
|
||
namespace seqan3::custom | ||
{ | ||
// Specialise the seqan3::custom::argument_parsing data structure to enable parsing of std::errc. | ||
template <> | ||
struct argument_parsing<std::errc> | ||
{ | ||
// Specialise a mapping from an identifying string to the respective value of your type Foo. | ||
static inline std::unordered_map<std::string_view, std::errc> const enumeration_names | ||
{ | ||
{"no_error", std::errc{}}, | ||
{"timed_out", std::errc::timed_out}, | ||
{"invalid_argument", std::errc::invalid_argument}, | ||
{"io_error", std::errc::io_error} | ||
}; | ||
}; | ||
|
||
} // namespace seqan3::custom | ||
|
||
int main(int argc, char const * argv[]) | ||
{ | ||
std::errc value{}; | ||
|
||
seqan3::argument_parser parser{"my_program", argc, argv}; | ||
|
||
// Because of the argument_parsing struct and | ||
// the static member function enumeration_names | ||
// you can now add an option that takes a value of type std::errc: | ||
parser.add_option(value, 'e', "errc", "Give me a std::errc value.", seqan3::option_spec::DEFAULT, | ||
seqan3::value_list_validator{(seqan3::enumeration_names<std::errc> | seqan3::views::get<1>)}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the mechanism of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? This is a snippet that appears in the documentation of "how to specialise this". Is that documentation enough? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just think that most users will be surprised that you can iterate over values/keys of an associative container using |
||
|
||
try | ||
{ | ||
parser.parse(); | ||
} | ||
catch (seqan3::parser_invalid_argument const & ext) // the user did something wrong | ||
{ | ||
std::cerr << "[PARSER ERROR] " << ext.what() << "\n"; // customize your error message | ||
return -1; | ||
} | ||
|
||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff could be much simpler, but it doesn't have to block this PR. We can fix it later.