diff --git a/CHANGELOG.md b/CHANGELOG.md index f3535d22..cd4d8810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,13 @@ If possible, provide tooling that performs the changes, e.g. a shell-script. ## API changes -#### I/O +#### General + +* Custom option types must not only model `sharg::istreamable` (`stream >> option`) + but must also model `sharg::ostreamable` in order to be used in `parser.add_option()` calls. + All standard types as well as types that overload `sharg::named_enumeration` are not affected. + +#### Validators * In order to avoid using the seqan3 I/O module, you now have to give a list of file extensions explicitly to `sharg::input_file_validator` and `sharg::output_file_validator`: diff --git a/include/sharg/auxiliary.hpp b/include/sharg/auxiliary.hpp index 4ea3f9ce..47bca531 100644 --- a/include/sharg/auxiliary.hpp +++ b/include/sharg/auxiliary.hpp @@ -14,8 +14,8 @@ #include #include +#include -#include #include #include @@ -291,11 +291,11 @@ struct argument_parser_meta_data // holds all meta information } // namespace sharg //!\cond -namespace seqan3 +namespace std { -template +template requires sharg::named_enumeration> -inline debug_stream_type & operator<<(debug_stream_type & s, option_type && op) +inline ostream & operator<<(ostream & s, option_type && op) { for (auto & [key, value] : sharg::enumeration_names) { @@ -305,5 +305,5 @@ inline debug_stream_type & operator<<(debug_stream_type & s, opt return s << ""; } -} +} // namespace std //!\endcond diff --git a/include/sharg/concept.hpp b/include/sharg/concept.hpp index e90501c0..fbf137af 100644 --- a/include/sharg/concept.hpp +++ b/include/sharg/concept.hpp @@ -34,6 +34,26 @@ concept istreamable = requires (std::istream & is, value_type & val) SHARG_RETURN_TYPE_CONSTRAINT(is >> val, std::same_as, std::istream&); }; +/*!\concept sharg::ostreamable + * \ingroup argument_parser + * \brief Concept for types that can be parsed into a std::ostream via the stream operator. + * \tparam type The type to check whether it's stremable via std::ostream or it's a container over streamable values. + * + * ### Requirements + * + * `std::ostream` must support the (un)formatted output function (`operator<<`) for an l-value of a given `type` or + * for an l-value of `type::reference`. + */ +template +concept ostreamable = requires (std::ostream & os, type & val) +{ + SHARG_RETURN_TYPE_CONSTRAINT(os << val, std::same_as, std::ostream&); +} || +requires (std::ostream & os, type & con) +{ + SHARG_RETURN_TYPE_CONSTRAINT(os << con[0], std::same_as, std::ostream&); +}; + /*!\concept sharg::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 @@ -47,6 +67,7 @@ concept istreamable = requires (std::istream & is, value_type & val) * \remark For a complete overview, take a look at \ref argument_parser */ template -concept argument_parser_compatible_option = sharg::istreamable || named_enumeration; +concept argument_parser_compatible_option = (sharg::istreamable && sharg::ostreamable) || + named_enumeration; } // namespace sharg diff --git a/include/sharg/detail/format_base.hpp b/include/sharg/detail/format_base.hpp index a0967497..f9497f24 100644 --- a/include/sharg/detail/format_base.hpp +++ b/include/sharg/detail/format_base.hpp @@ -222,7 +222,7 @@ class format_help_base : public format_base { std::string id = prep_id_for_help(short_id, long_id) + " " + option_type_and_list_info(value); std::string info{desc}; - info += ((spec & option_spec::required) ? std::string{" "} : seqan3::detail::to_string(" Default: ", value, ". ")); + info += ((spec & option_spec::required) ? std::string{" "} : detail::to_string(" Default: ", value, ". ")); info += option_validator.get_help_page_message(); store_help_page_element([this, id, info] () { derived_t().print_list_item(id, info); }, spec); } @@ -253,12 +253,12 @@ class format_help_base : public format_base positional_option_calls.push_back([this, &value, desc, msg] () { ++positional_option_count; - derived_t().print_list_item(seqan3::detail::to_string("\\fBARGUMENT-", positional_option_count, "\\fP ", - option_type_and_list_info(value)), + derived_t().print_list_item(detail::to_string("\\fBARGUMENT-", positional_option_count, "\\fP ", + option_type_and_list_info(value)), desc + // a list at the end may be empty and thus have a default value ((detail::is_container_option) - ? seqan3::detail::to_string(" Default: ", value, ". ") + ? detail::to_string(" Default: ", value, ". ") : std::string{" "}) + msg); }); diff --git a/include/sharg/detail/to_string.hpp b/include/sharg/detail/to_string.hpp new file mode 100644 index 00000000..2cb3138a --- /dev/null +++ b/include/sharg/detail/to_string.hpp @@ -0,0 +1,67 @@ +// ----------------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2021, 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/sharg-parser/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------------- + +/*!\file + * \author Svenja Mehringer + * \brief Provides sharg::detail::to_string. + */ + +#pragma once + +#include + +#include +#include + +namespace sharg::detail +{ + +/*!\brief Streams all parameters via std::ostringstream and returns a concatenated string. + * \tparam value_types Must be sharg::ostreamable (stream << value). + * \param[in] values Variable number of parameters of any type that implement the stream operator. + * \returns A concatenated string of all values (no separator in between is added). + */ +template + requires (ostreamable && ...) +std::string to_string(value_types && ...values) +{ + std::stringstream stream; + + auto print = [&stream] (auto val) + { + if constexpr (is_container_option) + { + if (val.empty()) + { + stream << "[]"; + } + else + { + stream << '['; + stream << val[0]; + for (size_t i = 1; i < val.size(); ++i) + stream << ", " << val[i]; + stream << ']'; + } + } + else if constexpr (std::is_same_v, int8_t> || + std::is_same_v, uint8_t>) + { + stream << static_cast(val); + } + else + { + stream << val; + } + }; + + (print(std::forward(values)),...); + + return stream.str(); +} + +} // namespace sharg::detail diff --git a/include/sharg/validators.hpp b/include/sharg/validators.hpp index 1dfa5c3f..72209cf4 100644 --- a/include/sharg/validators.hpp +++ b/include/sharg/validators.hpp @@ -15,10 +15,11 @@ #include #include -#include -#include +#include +#include #include +#include #include namespace sharg @@ -131,7 +132,7 @@ class arithmetic_range_validator /*!\brief A validator that checks whether a value is inside a list of valid values. * \ingroup argument_parser * \implements sharg::validator - * \tparam option_value_t \copybrief sharg::value_list_validator::option_value_type + * \tparam option_value_t The type the validator is called on. Must model sharg::argument_parser_compatible_option. * * \details * @@ -147,7 +148,7 @@ class arithmetic_range_validator * * \remark For a complete overview, take a look at \ref argument_parser */ -template +template class value_list_validator { public: @@ -201,7 +202,7 @@ class value_list_validator void operator()(option_value_type const & cmp) const { if (!(std::find(values.begin(), values.end(), cmp) != values.end())) - throw validation_error{seqan3::detail::to_string("Value ", cmp, " is not one of ", std::views::all(values), ".")}; + throw validation_error{detail::to_string("Value ", cmp, " is not one of ", values,".")}; } /*!\brief Tests whether every element in \p range lies inside values. @@ -221,11 +222,10 @@ class value_list_validator //!\brief Returns a message that can be appended to the (positional) options help page info. std::string get_help_page_message() const { - return seqan3::detail::to_string("Value must be one of ", std::views::all(values), "."); + return detail::to_string("Value must be one of ", values, "."); } private: - //!\brief Minimum of the range to test. std::vector values{}; }; @@ -437,19 +437,6 @@ class file_validator_base return true; } - //!\brief Creates a std::string from the extensions list, e.g. "[ext, ext2]". - std::string const create_extensions_str() const - { - if (extensions.empty()) - return "[]"; - - std::string result{'['}; - for (std::string const & ext : extensions) - result += ext + ", "; - result.replace(result.size() - 2, 2, "]"); // replace last ", " by "]" - return result; - } - //!\brief Stores the extensions. std::vector extensions{}; @@ -501,8 +488,8 @@ class input_file_validator : public file_validator_base */ explicit input_file_validator(std::vector extensions) : file_validator_base{} { + file_validator_base::extensions_str = detail::to_string(extensions); file_validator_base::extensions = std::move(extensions); - file_validator_base::extensions_str = create_extensions_str(); } // Import base class constructor. @@ -613,8 +600,8 @@ class output_file_validator : public file_validator_base explicit output_file_validator(output_file_open_options const mode, std::vector extensions = {}) : file_validator_base{}, mode{mode} { + file_validator_base::extensions_str = detail::to_string(extensions); file_validator_base::extensions = std::move(extensions); - file_validator_base::extensions_str = create_extensions_str(); } // Import base constructor. diff --git a/test/unit/detail/format_help_test.cpp b/test/unit/detail/format_help_test.cpp index b522233a..c7f01e6c 100644 --- a/test/unit/detail/format_help_test.cpp +++ b/test/unit/detail/format_help_test.cpp @@ -7,6 +7,8 @@ #include +#include + #include // reused global variables @@ -449,8 +451,8 @@ TEST(help_page_printing, full_information) " -i, --int (signed 32 bit integer)\n" " this is a int option. Default: 5.\n" " -e, --enum (foo)\n" - " this is an enum option. Default: one. Value must be one of\n" - " [three,two,one].\n" + " this is an enum option. Default: one. Value must be one of [three,\n" + " two, one].\n" " -r, --required-int (signed 8 bit integer)\n" " this is another int option.\n" "\n" diff --git a/test/unit/format_parse_test.cpp b/test/unit/format_parse_test.cpp index 8959ccf2..a5fe3c2a 100644 --- a/test/unit/format_parse_test.cpp +++ b/test/unit/format_parse_test.cpp @@ -7,6 +7,8 @@ #include +#include + #include TEST(parse_type_test, add_option_short_id) diff --git a/test/unit/format_parse_validators_test.cpp b/test/unit/format_parse_validators_test.cpp index c2c8ab93..2218cb2a 100644 --- a/test/unit/format_parse_validators_test.cpp +++ b/test/unit/format_parse_validators_test.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -830,6 +832,11 @@ enum class foo three }; +auto enumeration_names(foo) +{ + return std::unordered_map{{"one", foo::one}, {"two", foo::two}, {"three", foo::three}}; +} + TEST(validator_test, value_list_validator_success) { // type deduction @@ -951,7 +958,7 @@ TEST(validator_test, value_list_validator_success) "\n" + basic_options_str + " -i, --int-option (List of signed 32 bit integer)\n" - " desc Default: []. Value must be one of [-10,48,50].\n\n" + + " desc Default: []. Value must be one of [-10, 48, 50].\n\n" + basic_version_str); EXPECT_EQ(my_stdout, expected); }