Skip to content
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

Merged
merged 1 commit into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions include/seqan3/argument_parser/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ class argument_parser
*/
template <typename option_type, validator validator_type = detail::default_validator<option_type>>
//!\cond
requires (input_stream_over<std::istringstream, option_type> ||
input_stream_over<std::istringstream, typename option_type::value_type>) &&
requires (argument_parser_compatible_option<option_type> ||
argument_parser_compatible_option<typename option_type::value_type>) &&
std::invocable<validator_type, option_type>
//!\endcond
void add_option(option_type & value,
Expand Down Expand Up @@ -292,8 +292,8 @@ class argument_parser
*/
template <typename option_type, validator validator_type = detail::default_validator<option_type>>
//!\cond
requires (input_stream_over<std::istringstream, option_type> ||
input_stream_over<std::istringstream, typename option_type::value_type>) &&
requires (argument_parser_compatible_option<option_type> ||
argument_parser_compatible_option<typename option_type::value_type>) &&
std::invocable<validator_type, option_type>
//!\endcond
void add_positional_option(option_type & value,
Expand Down
197 changes: 197 additions & 0 deletions include/seqan3/argument_parser/auxiliary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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{});
}
};
Copy link
Member

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.


} // 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>>`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should this be const? Is that enforced or unimportant?

Copy link
Member Author

Choose a reason for hiding this comment

The 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...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think it should be const...

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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
*
Expand Down
12 changes: 8 additions & 4 deletions include/seqan3/argument_parser/detail/format_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ class format_help_base : public format_base
option_spec const & spec,
validator_type && validator)
{
parser_set_up_calls.push_back([this, &value, short_id, long_id, desc, spec, validator] ()
std::string msg = validator.get_help_page_message();

parser_set_up_calls.push_back([this, &value, short_id, long_id, desc, spec, msg] ()
{
if (!(spec & option_spec::HIDDEN) && (!(spec & option_spec::ADVANCED) || show_advanced_options))
derived_t().print_list_item(prep_id_for_help(short_id, long_id) +
Expand All @@ -251,7 +253,7 @@ class format_help_base : public format_base
((spec & option_spec::REQUIRED)
? std::string{" "}
: detail::to_string(" Default: ", value, ". ")) +
validator.get_help_page_message());
msg);
});
}

Expand Down Expand Up @@ -289,7 +291,9 @@ class format_help_base : public format_base
std::string const & desc,
validator_type & validator)
{
positional_option_calls.push_back([this, &value, desc, validator] ()
std::string msg = 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 ",
Expand All @@ -299,7 +303,7 @@ class format_help_base : public format_base
((sequence_container<option_type> && !std::same_as<option_type, std::string>)
? detail::to_string(" Default: ", value, ". ")
: std::string{" "}) +
validator.get_help_page_message());
msg);
});
}

Expand Down
31 changes: 27 additions & 4 deletions include/seqan3/argument_parser/detail/format_parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,11 @@ class format_parse : public format_base
}

/*!\brief Tries to cast an input string into a value.
*
* \tparam option_t Must satisfy the seqan3::input_stream_over.
*
* \tparam option_t Must model seqan3::input_stream_over.
* \param[out] value Stores the casted value.
* \param[in] in The input argument to be casted.
*
* \throws seqan3::parser_invalid_argument
* \throws seqan3::type_conversion_failed
*/
template <typename option_t>
//!\cond
Expand All @@ -301,8 +299,33 @@ class format_parse : public format_base
stream >> value;

if (stream.fail() || !stream.eof())
{
throw type_conversion_failed("Argument " + in + " could not be casted to type " +
get_type_name_as_string(value) + ".");
}
}

/*!\brief Sets an option value depending on the keys found in seqan3::enumeration_names<option_t>.
* \tparam option_t Must model seqan3::named_enumeration.
* \param[out] value Stores the cast value.
* \param[in] in The input argument to be cast.
*
* \throws seqan3::type_conversion_failed
*/
template <named_enumeration option_t>
void retrieve_value(option_t & value, std::string_view const in)
{
auto map = seqan3::enumeration_names<option_t>;

if (auto it = map.find(in); it == map.end())
{
throw type_conversion_failed("Argument " + std::string{in} + " could not be cast to enum type " +
get_display_name_v<option_t>.str() + ".");
}
else
{
value = it->second;
}
}

//!\cond
Expand Down
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>)});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the mechanism of using seqan3::views::get documented anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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 views::get<>.
If this is not explained anywhere, I think it would be good to add a sentence as explanation. But this can also be done later if you add a card.


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;
}
Loading