Skip to content

Commit

Permalink
[FEATURE] Enum support for the argument parser.
Browse files Browse the repository at this point in the history
  • Loading branch information
smehringer committed Nov 11, 2019
1 parent 78389d5 commit d450b8a
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 12 deletions.
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
198 changes: 198 additions & 0 deletions include/seqan3/argument_parser/auxiliary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,210 @@
#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.
*
* This struct is templetised 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
{
private:
//!\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

public:
//!\brief Operator definition.
template <typename dummy = int> // need to make this a template to enforce deferred initialisation
//!\cond
requires requires
{
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>>`.
* 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>>`.
*
* ### 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 define the `operator>>` over std::stringstream 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
15 changes: 11 additions & 4 deletions include/seqan3/argument_parser/detail/format_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include <seqan3/argument_parser/exceptions.hpp>
#include <seqan3/argument_parser/validators.hpp>
#include <seqan3/core/detail/reflection.hpp>
#include <seqan3/range/views/get.hpp>
#include <seqan3/range/views/to.hpp>
#include <seqan3/range/views/view_all.hpp>
#include <seqan3/std/filesystem>

namespace seqan3::detail
Expand Down Expand Up @@ -243,7 +246,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 @@ -252,7 +257,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 @@ -290,7 +295,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 @@ -300,7 +307,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 Tries to cast an input string into a value.
* \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 const & in)
{
auto map = seqan3::enumeration_names<option_t>;

if (auto it = map.find(in); it == map.end())
{
throw type_conversion_failed("Argument " + 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,46 @@
#include <system_error>

#include <seqan3/argument_parser/all.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 const std::unordered_map<std::string_view, std::errc> 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>)});

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

0 comments on commit d450b8a

Please sign in to comment.