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 Sep 6, 2019
1 parent 0768d4b commit dbca685
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 17 deletions.
10 changes: 5 additions & 5 deletions include/seqan3/argument_parser/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,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 @@ -291,9 +291,9 @@ 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>) &&
std::invocable<validator_type, option_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,
std::string const & desc,
Expand Down
179 changes: 179 additions & 0 deletions include/seqan3/argument_parser/auxiliary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,191 @@
#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>

namespace seqan3::custom
{

/*!\brief A type that can be specialised to provide customisation point implementations so that third party types
* model the seqan3::string_convertible concept.
* \tparam t The type you wish to specialise for.
* \ingroup argument_parser
*
* \details
*
* You can specialise a this class like this:
*
* \include test/snippet/argument_parser/string_convertible.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.
*/
template <typename t>
struct string_convertiblity
{}; // forward

//!\cond
template <typename t>
struct string_convertiblity<t const> : string_convertiblity<t>
{};

template <typename t>
struct string_convertiblity<t &> : string_convertiblity<t>
{};

template <typename t>
struct string_convertiblity<t const &> : string_convertiblity<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, t> string_conversion_map(t v) = delete;

//!\brief Functor definition for seqan3::string_conversion_map.
struct string_conversion_map_fn
{
private:
SEQAN3_CPO_IMPL(2, seqan3::custom::string_convertiblity<decltype(v)>::string_conversion_map(v)) // explicit custom.
SEQAN3_CPO_IMPL(1, string_conversion_map(v)) // ADL
SEQAN3_CPO_IMPL(0, v.string_conversion_map()) // member

public:
//!\brief Operator definition.
template <typename option_type>
//!\cond
requires requires (option_type const a)
{
{ impl(priority_tag<2>{}, a) };
}
//!\endcond
constexpr auto operator()(option_type const a) const noexcept
{
return impl(priority_tag<2>{}, a);
}
};

} // namespace seqan3::detail::adl::only

namespace seqan3
{

/*!\name Function objects
* \{
*/

/*!\brief Return the rank representation of a (semi-)alphabet object.
* \tparam your_type Type of the argument.
* \param alph The (semi-)alphabet object.
* \returns The rank representation; an integral type.
* \ingroup alphabet
* \details
*
* This is a function object. Invoke it with the parameter(s) specified above.
*
* It acts as a wrapper and looks for three possible implementations (in this order):
*
* 1. A static member function `string_conversion_map(your_type const a)` in
* `seqan3::custom::string_convertiblity<your_type>`.
* 2. A free function `string_conversion_map(your_type const a)` in the namespace of your type (or as `friend`).
* 3. A member function called `string_conversion_map()`.
*
* Functions are only considered for one of the above cases if they are marked `noexcept` (`constexpr` is not required,
* but recommended) and if the returned type models std::Integral.
*
* Every (semi-)alphabet type must provide one of the above.
*
* ### 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/string_conversion_map.cpp
*
* **Only if you cannot access the namespace of your type to customize** you may specialize
* the seqan3::custom::string_convertiblity struct like this:
*
* \include test/snippet/argument_parser/string_convertible.cpp
*
* ### Customisation point
*
* This is a customisation point (see \ref about_customisation). To specify the behaviour for your own alphabet type,
* simply provide one of the three functions specified above.
*/
inline constexpr auto string_conversion_map = detail::adl_only::string_conversion_map_fn{};
//!\}
} // namespace seqan3


namespace seqan3
{

/*!\interface seqan3::string_convertible <>
* \brief Checks whether the free function seqan3::string_conversion_map can be called on the type.
* \ingroup argument_parser
* \tparam option_type The type to check.
*
* ### Requirements
*
* * The free function seqan3::string_conversion_map must be defined on a value of the type and return
* a `std::unordered_map<std::string, option_type>`.
*/
//!\cond
template <typename option_type>
SEQAN3_CONCEPT string_convertible = requires
{
{ seqan3::string_conversion_map(option_type{}) } -> std::unordered_map<std::string, 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::string stream or
* a free function seqan3::string_conversion_map must be defined on a value of the type and return
* a `std::unordered_map<std::string, option_type>`.
*/
//!\cond
template <typename option_type>
SEQAN3_CONCEPT argument_parser_compatible_option = input_stream_over<std::istringstream, option_type> ||
string_convertible<option_type>;
//!\endcond

/*!\brief foo
* \tparam option_type Type of the enum to be printed.
* \param s The seqan3::debug_stream.
* \param em The enum.
* \relates seqan3::debug_stream_type
*
* \details
*
* This prints out an alignment matrix which can be a score matrix or a trace matrix.
*/
template <typename char_t, typename option_type>
//!\cond
requires string_convertible<remove_cvref_t<option_type>>
//!\endcond
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, option_type && em)
{
for (auto & [key, value] : string_conversion_map(em))
if (em == value)
return s << key;
return s << "<UNKOWN_ENUM>";
}

/*!\brief Used to further specify argument_parser options/flags.
* \ingroup argument_parser
*
Expand Down
34 changes: 26 additions & 8 deletions include/seqan3/argument_parser/detail/format_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <seqan3/argument_parser/exceptions.hpp>
#include <seqan3/argument_parser/validators.hpp>
#include <seqan3/core/detail/reflection.hpp>
#include <seqan3/range/view/view_all.hpp>
#include <seqan3/range/view/get.hpp>
#include <seqan3/std/filesystem>

namespace seqan3::detail
Expand Down Expand Up @@ -233,17 +235,25 @@ class format_help_base : public format_base
* \param[in] long_id The long identifier for the option (e.g. "integer").
* \param[in] desc The description of the option.
* \param[in] spec Advanced option specification, see seqan3::option_spec.
* \param[in] validator The validator applied to the value after parsing (callable).
* \param[in] val The validator applied to the value after parsing (callable).
*/
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 && validator)
validator_type && val)
{
parser_set_up_calls.push_back([this, &value, short_id, long_id, desc, spec, validator] ()
std::string msg = val.get_help_page_message();

if constexpr (string_convertible<option_type>)
{
auto table = seqan3::string_conversion_map(value);
msg = (val | value_list_validator<option_type>{(view::all(table) | view::get<1>)}).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 +262,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 @@ -283,14 +293,22 @@ class format_help_base : public format_base
*
* \param[out] value The variable in which to store the given command line argument.
* \param[in] desc The description of the positional option.
* \param[in] validator The validator applied to the value after parsing (callable).
* \param[in] val The validator applied to the value after parsing (callable).
*/
template <typename option_type, typename validator_type>
void add_positional_option(option_type & value,
std::string const & desc,
validator_type & validator)
validator_type & val)
{
positional_option_calls.push_back([this, &value, desc, validator] ()
std::string msg = val.get_help_page_message();

if constexpr (string_convertible<option_type>)
{
auto table = seqan3::string_conversion_map(value);
msg = (val | value_list_validator<option_type>{(view::all(table) | view::get<1>)}).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 +318,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 @@ -305,6 +303,31 @@ class format_parse : public format_base
get_type_name_as_string(value) + ".");
}

/*!\brief Tries to cast an input string into a value.
* \tparam option_t Must model seqan3::string_convertible.
* \param[out] value Stores the casted value.
* \param[in] in The input argument to be casted.
*
* \throws seqan3::type_conversion_failed
*/
template <typename option_t>
//!\cond
requires string_convertible<option_t>
//!\endcond
void retrieve_value(option_t & value, std::string const & in)
{
std::string tmp;
std::istringstream is{in};
is >> tmp;
auto it = seqan3::string_conversion_map(value).find(tmp);

if (it == seqan3::string_conversion_map(value).end())
throw type_conversion_failed("Argument " + in + " could not be casted to enum type " +
get_display_name_v<option_t>.str() + ".");
else
value = it->second;
}

//!\cond
void retrieve_value(std::string & value, std::string const & in)
{
Expand Down
41 changes: 41 additions & 0 deletions test/snippet/argument_parser/string_conversion_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <seqan3/argument_parser/all.hpp>

namespace your_namespace
{

enum class Foo
{
one,
two,
three
};

// specialise a mapping from your values of type Foo to the respective string
auto string_conversion_map(Foo)
{
return std::unordered_map<std::string, Foo>{{{"one", Foo::one}, {"two", Foo::two}, {"three", Foo::three}}};
}

} // namespace your_namespace


int main(int argc, char const * argv[])
{
your_namespace::Foo value{};

seqan3::argument_parser parser{"my_program", argc, argv};

// Because of the string_conversion_map function
// you can now add an option that takes a value of type Foo:
parser.add_option(value, 'f', "foo", "Give me a value foo value.");

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

0 comments on commit dbca685

Please sign in to comment.