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 11, 2019
1 parent e2c5fec commit 1556454
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 16 deletions.
9 changes: 5 additions & 4 deletions include/seqan3/argument_parser/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <seqan3/argument_parser/detail/format_parse.hpp>
#include <seqan3/argument_parser/detail/version_check.hpp>
#include <seqan3/core/char_operations/predicate.hpp>
#include <seqan3/core/detail/debug_stream_string_convertible.hpp>
#include <seqan3/core/detail/terminal.hpp>
#include <seqan3/core/detail/to_string.hpp>
#include <seqan3/io/stream/concept.hpp>
Expand Down Expand Up @@ -229,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 @@ -291,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
148 changes: 148 additions & 0 deletions include/seqan3/argument_parser/auxiliary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,160 @@
#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 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_convertibility
{}; // forward

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

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

template <typename t>
struct string_convertibility<t const &> : string_convertibility<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_convertibility<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 Customisation Points
* \{
*/

/*!\brief Return a conversion map from std::string 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, 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 three possible implementations (in this order):
*
* 1. A static member function `string_conversion_map(your_type const a)` in
* `seqan3::custom::string_convertibility<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()`.
*
* ### 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_convertibility 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 type,
* simply provide one of the three functions specified above.
*/
inline constexpr auto string_conversion_map = detail::adl_only::string_conversion_map_fn{};
//!\}

/*!\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 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/views/get.hpp>
#include <seqan3/range/views/view_all.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>{(views::all(table) | views::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>{(views::all(table) | views::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
33 changes: 29 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,33 @@ 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 cast value.
* \param[in] in The input argument to be cast.
*
* \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 map = seqan3::string_conversion_map(value);
auto it = map.find(tmp);

if (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
void retrieve_value(std::string & value, std::string const & in)
{
Expand Down
1 change: 1 addition & 0 deletions include/seqan3/core/debug_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <seqan3/core/detail/debug_stream_alphabet.hpp>
#include <seqan3/core/detail/debug_stream_optional.hpp>
#include <seqan3/core/detail/debug_stream_range.hpp>
#include <seqan3/core/detail/debug_stream_string_convertible.hpp>
#include <seqan3/core/detail/debug_stream_tuple.hpp>
#include <seqan3/core/detail/debug_stream_type.hpp>
#include <seqan3/core/detail/debug_stream_variant.hpp>
Expand Down
52 changes: 52 additions & 0 deletions include/seqan3/core/detail/debug_stream_string_convertible.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

/*!\file
* \author Svenja Mehringer <svenja.mehringer AT fu-berlin.de>
* \brief Provides an overload for the seqan3::debug_stream if a seqan3::string_conversion_map of the type exists.
*/

#pragma once

#include <variant>

#include <seqan3/argument_parser/auxiliary.hpp>
#include <seqan3/core/detail/debug_stream_type.hpp>

namespace seqan3
{
/*!\name Formatted output overloads
* \{
*/
/*!\brief A type (e.g. an enum) can be made debug streamable by customizing the seqan3::string_conversion_map.
* \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::string_conversion_map 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 string_convertible<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] : string_conversion_map(op))
{
if (op == value)
return s << key;
}

return s << "<UNKNOWN_VALUE>";
}
//!\}

} // namespace seqan3
Loading

0 comments on commit 1556454

Please sign in to comment.