SeqAn3 3.1.0-rc.2
The Modern C++ library for sequence analysis.
validators.hpp
Go to the documentation of this file.
1// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik
4// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6// -----------------------------------------------------------------------------------------------------
7
13#pragma once
14
15#include <seqan3/std/algorithm>
16#include <seqan3/std/concepts>
17#include <seqan3/std/filesystem>
18#include <fstream>
19#include <seqan3/std/ranges>
20#include <regex>
21#include <sstream>
22
33
34namespace seqan3
35{
36
94template <typename validator_type>
95SEQAN3_CONCEPT validator = std::copyable<std::remove_cvref_t<validator_type>> &&
96 requires(validator_type validator,
98{
100
101 SEQAN3_RETURN_TYPE_CONSTRAINT(validator(value), std::same_as, void);
103};
105
119template <arithmetic option_value_t>
121{
122public:
124 using option_value_type = option_value_t;
125
131 min{min_}, max{max_}
132 {}
133
138 void operator()(option_value_type const & cmp) const
139 {
140 if (!((cmp <= max) && (cmp >= min)))
141 throw validation_error{detail::to_string("Value ", cmp, " is not in range [", min, ",", max, "].")};
142 }
143
150 template <std::ranges::forward_range range_type>
154 void operator()(range_type const & range) const
155 {
156 std::for_each(range.begin(), range.end(), [&] (auto cmp) { (*this)(cmp); });
157 }
158
161 {
162 return detail::to_string("Value must be in range [", min, ",", max, "].");
163 }
164
165private:
167 option_value_type min{};
168
170 option_value_type max{};
171};
172
190template <typename option_value_t>
192{
193public:
195 using option_value_type = option_value_t;
196
206
212 template <std::ranges::forward_range range_type>
214 requires std::constructible_from<option_value_type, std::ranges::range_rvalue_reference_t<range_type>>
216 value_list_validator(range_type rng)
217 {
218 values.clear();
219 std::ranges::move(std::move(rng), std::cpp20::back_inserter(values));
220 }
221
227 template <typename ...option_types>
229 requires ((std::constructible_from<option_value_type, option_types> && ...))
231 value_list_validator(option_types && ...opts)
232 {
233 (values.emplace_back(std::forward<option_types>(opts)), ...);
234 }
236
241 void operator()(option_value_type const & cmp) const
242 {
243 if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
244 throw validation_error{detail::to_string("Value ", cmp, " is not one of ", std::views::all(values), ".")};
245 }
246
252 template <std::ranges::forward_range range_type>
254 requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type>
256 void operator()(range_type const & range) const
257 {
258 std::for_each(std::ranges::begin(range), std::ranges::end(range), [&] (auto cmp) { (*this)(cmp); });
259 }
260
263 {
264 return detail::to_string("Value must be one of ", std::views::all(values), ".");
265 }
266
267private:
268
271};
272
278template <typename option_type, typename ...option_types>
280 requires (std::constructible_from<std::string, std::decay_t<option_types>> && ... &&
281 std::constructible_from<std::string, std::decay_t<option_type>>)
284
286template <typename range_type>
288 requires (std::ranges::forward_range<std::decay_t<range_type>> &&
289 std::constructible_from<std::string, std::ranges::range_value_t<range_type>>)
292
294template <typename option_type, typename ...option_types>
296
298template <typename range_type>
300 requires (std::ranges::forward_range<std::decay_t<range_type>>)
304
318{
319public:
320
323
332 virtual ~file_validator_base() = default;
334
342 virtual void operator()(std::filesystem::path const & path) const = 0;
343
351 template <std::ranges::forward_range range_type>
353 requires (std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
354 && !std::convertible_to<range_type, std::filesystem::path const &>)
356 void operator()(range_type const & v) const
357 {
358 std::for_each(v.begin(), v.end(), [&] (auto cmp) { this->operator()(cmp); });
359 }
360
361protected:
368 {
369 // If no valid extensions are given we can safely return here.
370 if (extensions.empty())
371 return;
372
373 // Check if extension is available.
374 if (!path.has_extension())
375 throw validation_error{detail::to_string("The given filename ", path.string(), " has no extension. Expected"
376 " one of the following valid extensions:", extensions, "!")};
377
378 std::string file_path{path.filename().string()};
379
380 // Leading dot indicates a hidden file is not part of the extension.
381 if (file_path.front() == '.')
382 file_path.erase(0, 1);
383
384 // Store a string_view containing all extensions for a better error message.
385 std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
386
387 // Compares the extensions in lower case.
388 auto case_insensitive_ends_with = [&] (std::string const & ext)
389 {
390 return case_insensitive_string_ends_with(file_path, ext);
391 };
392
393 // Check if requested extension is present.
394 if (std::ranges::find_if(extensions, case_insensitive_ends_with) == extensions.end())
395 {
396 throw validation_error{detail::to_string("Expected one of the following valid extensions: ", extensions,
397 "! Got ", all_extensions, " instead!")};
398 }
399 }
400
408 {
409 // Check if input directory is readable.
411 {
412 std::error_code ec{};
413 std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
414 if (static_cast<bool>(ec))
415 throw validation_error{detail::to_string("Cannot read the directory ", path ,"!")};
416 }
417 else
418 {
419 // Must be a regular file.
421 throw validation_error{detail::to_string("Expected a regular file ", path, "!")};
422
423 std::ifstream file{path};
424 if (!file.is_open() || !file.good())
425 throw validation_error{detail::to_string("Cannot read the file ", path, "!")};
426 }
427 }
428
436 {
437 std::ofstream file{path};
438 detail::safe_filesystem_entry file_guard{path};
439
440 bool is_open = file.is_open();
441 bool is_good = file.good();
442 file.close();
443
444 if (!is_good || !is_open)
445 throw validation_error{detail::to_string("Cannot write ", path, "!")};
446
447 file_guard.remove();
448 }
449
452 {
453 if (extensions.empty())
454 return "";
455 else
456 return detail::to_string(" Valid file extensions are: [", extensions | views::join_with(std::string{", "}), "].");
457 }
458
465 {
466 size_t const suffix_length{suffix.size()};
467 size_t const str_length{str.size()};
468 return suffix_length > str_length ?
469 false :
470 std::ranges::equal(str.substr(str_length - suffix_length), suffix, [] (char const chr1, char const chr2)
471 {
472 return std::tolower(chr1) == std::tolower(chr2);
473 });
474 }
475
478};
479
502template <typename file_t = void>
504{
505public:
506
507 static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
508 "Expected either a template type with a static member called valid_formats (a file type) or void.");
509
510 // Import from base class.
512
526 {
527 if constexpr (!std::same_as<file_t, void>)
528 file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
529 }
530
535 virtual ~input_file_validator() = default;
536
547 requires std::same_as<file_t, void>
550 {
552 }
553
554 // Import base class constructor.
557
558 // Import the base::operator()
559 using file_validator_base::operator();
560
566 virtual void operator()(std::filesystem::path const & file) const override
567 {
568 try
569 {
570 if (!std::filesystem::exists(file))
571 throw validation_error{detail::to_string("The file ", file, " does not exist!")};
572
573 // Check if file is regular and can be opened for reading.
575
576 // Check extension.
577 validate_filename(file);
578 }
579 // LCOV_EXCL_START
581 {
582 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
583 }
584 // LCOV_EXCL_STOP
585 catch (...)
586 {
588 }
589 }
590
593 {
594 return "The input file must exist and read permissions must be granted." +
596 }
597};
598
601{
606};
607
634template <typename file_t = void>
636{
637public:
638 static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
639 "Expected either a template type with a static member called valid_formats (a file type) or void.");
640
641 // Import from base class.
643
650 {}
651
656 virtual ~output_file_validator() = default;
657
666 : file_validator_base{}, mode{mode}
667 {
669 }
670
671 // Import base constructor.
674
684 {
685 if constexpr (!std::same_as<file_t, void>)
686 return detail::valid_file_extensions<typename file_t::valid_formats>();
687 return {};
688 }
689
690 // Import the base::operator()
691 using file_validator_base::operator();
692
698 virtual void operator()(std::filesystem::path const & file) const override
699 {
700 try
701 {
703 throw validation_error{detail::to_string("The file ", file, " already exists!")};
704
705 // Check if file has any write permissions.
707
708 validate_filename(file);
709 }
710 // LCOV_EXCL_START
712 {
713 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
714 }
715 // LCOV_EXCL_STOP
716 catch (...)
717 {
719 }
720 }
721
724 {
726 return "Write permissions must be granted." + valid_extensions_help_page_message();
727 else // mode == create_new
728 return "The output file must not exist already and write permissions must be granted." +
730 }
731
732private:
735};
736
752{
753public:
754 // Import from base class.
756
765 virtual ~input_directory_validator() = default;
766
767 // Import base constructor.
770
771 // Import the base::operator()
772 using file_validator_base::operator();
773
779 virtual void operator()(std::filesystem::path const & dir) const override
780 {
781 try
782 {
783 if (!std::filesystem::exists(dir))
784 throw validation_error{detail::to_string("The directory ", dir, " does not exists!")};
785
787 throw validation_error{detail::to_string("The path ", dir, " is not a directory!")};
788
789 // Check if directory has any read permissions.
791 }
792 // LCOV_EXCL_START
794 {
795 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
796 }
797 // LCOV_EXCL_STOP
798 catch (...)
799 {
801 }
802 }
803
806 {
807 return detail::to_string("An existing, readable path for the input directory.");
808 }
809};
810
826{
827public:
828 // Imported from base class.
830
839 virtual ~output_directory_validator() = default;
840
841 // Import base constructor.
844
845 // Import the base::operator().
846 using file_validator_base::operator();
847
853 virtual void operator()(std::filesystem::path const & dir) const override
854 {
855 bool dir_exists = std::filesystem::exists(dir);
856 // Make sure the created dir is deleted after we are done.
858 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
859 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
860 if (static_cast<bool>(ec))
861 throw validation_error{detail::to_string("Cannot create directory: ", dir, "!")};
862
863 try
864 {
865 if (!dir_exists)
866 {
867 detail::safe_filesystem_entry dir_guard{dir};
868 validate_writeability(dir / "dummy.txt");
869 dir_guard.remove_all();
870 }
871 else
872 {
873 validate_writeability(dir / "dummy.txt");
874 }
875 }
876 // LCOV_EXCL_START
878 {
879 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
880 }
881 // LCOV_EXCL_STOP
882 catch (...)
883 {
885 }
886 }
887
890 {
891 return detail::to_string("A valid path for the output directory.");
892 }
893};
894
913{
914public:
917
921 regex_validator(std::string const & pattern_) :
922 pattern{pattern_}
923 {}
924
929 void operator()(option_value_type const & cmp) const
930 {
931 std::regex rgx(pattern);
932 if (!std::regex_match(cmp, rgx))
933 throw validation_error{detail::to_string("Value ", cmp, " did not match the pattern ", pattern, ".")};
934 }
935
942 template <std::ranges::forward_range range_type>
944 requires std::convertible_to<std::ranges::range_reference_t<range_type>, option_value_type const &>
946 void operator()(range_type const & v) const
947 {
948 for (auto && file_name : v)
949 {
950 // note: we explicitly copy/construct any reference type other than `std::string &`
951 (*this)(static_cast<option_value_type const &>(file_name));
952 }
953 }
954
957 {
958 return detail::to_string("Value must match the pattern '", pattern, "'.");
959 }
960
961private:
963 std::string pattern;
964};
965
966namespace detail
967{
968
979template <typename option_value_t>
980struct default_validator
981{
983 using option_value_type = option_value_t;
984
986 void operator()(option_value_t const & /*cmp*/) const noexcept
987 {}
988
991 {
992 return "";
993 }
994};
995
1007template <validator validator1_type, validator validator2_type>
1009 requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
1011class validator_chain_adaptor
1012{
1013public:
1015 using option_value_type = std::common_type_t<typename validator1_type::option_value_type,
1016 typename validator2_type::option_value_type>;
1017
1021 validator_chain_adaptor() = delete;
1022 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1023 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1024 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1025 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1026
1031 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1032 vali1{std::move(vali1_)}, vali2{std::move(vali2_)}
1033 {}
1034
1036 ~validator_chain_adaptor() = default;
1038
1047 template <typename cmp_type>
1049 requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1051 void operator()(cmp_type const & cmp) const
1052 {
1053 vali1(cmp);
1054 vali2(cmp);
1055 }
1056
1059 {
1060 return detail::to_string(vali1.get_help_page_message(), " ", vali2.get_help_page_message());
1061 }
1062
1063private:
1065 validator1_type vali1;
1067 validator2_type vali2;
1068};
1069
1070} // namespace detail
1071
1099template <validator validator1_type, validator validator2_type>
1101 requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1104auto operator|(validator1_type && vali1, validator2_type && vali2)
1105{
1106 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1),
1107 std::forward<validator2_type>(vali2)};
1108}
1109
1110} // namespace seqan3
Adaptations of algorithms from the Ranges TS.
Provides various type traits on generic types.
A validator that checks whether a number is inside a given range.
Definition: validators.hpp:121
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition: validators.hpp:138
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition: validators.hpp:124
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition: validators.hpp:154
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:160
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition: validators.hpp:130
An abstract base class for the file and directory validators.
Definition: validators.hpp:318
bool case_insensitive_string_ends_with(std::string_view str, std::string_view suffix) const
Helper function that checks if a string is a suffix of another string. Case insensitive.
Definition: validators.hpp:464
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition: validators.hpp:367
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition: validators.hpp:451
virtual void operator()(std::filesystem::path const &path) const =0
Tests if the given path is a valid input, respectively output, file or directory.
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:322
file_validator_base(file_validator_base &&)=default
Defaulted.
file_validator_base & operator=(file_validator_base &&)=default
Defaulted.
void validate_readability(std::filesystem::path const &path) const
Checks if the given path is readable.
Definition: validators.hpp:407
file_validator_base()=default
Defaulted.
file_validator_base(file_validator_base const &)=default
Defaulted.
std::vector< std::string > extensions
Stores the extensions.
Definition: validators.hpp:477
virtual ~file_validator_base()=default
file_validator_base & operator=(file_validator_base const &)=default
Defaulted.
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition: validators.hpp:435
A validator that checks if a given path is a valid input directory.
Definition: validators.hpp:752
input_directory_validator(input_directory_validator &&)=default
Defaulted.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:805
input_directory_validator()=default
Defaulted.
input_directory_validator(input_directory_validator const &)=default
Defaulted.
input_directory_validator & operator=(input_directory_validator &&)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is an existing directory and is readable.
Definition: validators.hpp:779
input_directory_validator & operator=(input_directory_validator const &)=default
Defaulted.
virtual ~input_directory_validator()=default
Virtual Destructor.
A validator that checks if a given path is a valid input file.
Definition: validators.hpp:504
input_file_validator(input_file_validator const &)=default
Defaulted.
virtual ~input_file_validator()=default
Virtual destructor.
input_file_validator(std::vector< std::string > extensions)
Constructs from a given collection of valid extensions.
Definition: validators.hpp:545
input_file_validator & operator=(input_file_validator &&)=default
Defaulted.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:592
input_file_validator(input_file_validator &&)=default
Defaulted.
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is an existing regular file and is readable.
Definition: validators.hpp:566
input_file_validator()
Default constructor.
Definition: validators.hpp:525
input_file_validator & operator=(input_file_validator const &)=default
Defaulted.
A validator that checks if a given path is a valid output directory.
Definition: validators.hpp:826
output_directory_validator()=default
Defaulted.
output_directory_validator & operator=(output_directory_validator const &)=default
Defaulted.
virtual ~output_directory_validator()=default
Virtual Destructor.
output_directory_validator(output_directory_validator &&)=default
Defaulted.
output_directory_validator(output_directory_validator const &)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is writable.
Definition: validators.hpp:853
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:889
output_directory_validator & operator=(output_directory_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid output file.
Definition: validators.hpp:636
static std::vector< std::string > default_extensions()
The default extensions of file_t.
Definition: validators.hpp:683
output_file_validator(output_file_validator &&)=default
Defaulted.
output_file_validator(output_file_validator const &)=default
Defaulted.
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition: validators.hpp:698
output_file_validator()
Default constructor.
Definition: validators.hpp:649
output_file_validator & operator=(output_file_validator const &)=default
Defaulted.
output_file_validator & operator=(output_file_validator &&)=default
Defaulted.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:723
virtual ~output_file_validator()=default
Virtual Destructor.
output_file_validator(output_file_open_options const mode, std::vector< std::string > extensions=default_extensions())
Constructs from a given overwrite mode and a list of valid extensions.
Definition: validators.hpp:664
A validator that checks if a matches a regular expression pattern.
Definition: validators.hpp:913
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:956
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:929
void operator()(range_type const &v) const
Tests whether every filename in list v matches the pattern.
Definition: validators.hpp:946
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:916
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition: validators.hpp:921
Argument parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:115
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:192
value_list_validator()=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:241
value_list_validator(value_list_validator const &)=default
Defaulted.
value_list_validator & operator=(value_list_validator const &)=default
Defaulted.
option_value_t option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:195
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:262
value_list_validator(option_type, option_types ...) -> value_list_validator< option_type >
Deduction guide for a parameter pack.
void operator()(range_type const &range) const
Tests whether every element in range lies inside values.
Definition: validators.hpp:256
value_list_validator(value_list_validator &&)=default
Defaulted.
~value_list_validator()=default
Defaulted.
value_list_validator(option_types &&...opts)
Constructing from a parameter pack.
Definition: validators.hpp:231
value_list_validator(range_type &&rng) -> value_list_validator< std::string >
Deduction guide for ranges over a value type convertible to std::string.
value_list_validator(option_type, option_types...) -> value_list_validator< std::string >
Type deduction guides.
value_list_validator & operator=(value_list_validator &&)=default
Defaulted.
value_list_validator(range_type &&rng) -> value_list_validator< std::ranges::range_value_t< range_type > >
Deduction guide for ranges.
value_list_validator(range_type rng)
Constructing from a range.
Definition: validators.hpp:216
The Concepts library.
T create_directory(T... args)
T current_exception(T... args)
Provides parser related exceptions.
T exists(T... args)
Provides concepts for core language types and relations that don't have concepts in C++20 (yet).
T filename(T... args)
This header includes C++17 filesystem support and imports it into namespace std::filesystem (independ...
T find(T... args)
T for_each(T... args)
auto operator|(validator1_type &&vali1, validator2_type &&vali2)
Enables the chaining of validators.
Definition: validators.hpp:1104
constexpr ptrdiff_t find_if
Get the index of the first type in a pack that satisfies the given predicate.
Definition: traits.hpp:210
constexpr auto join_with
A join view, please use std::views::join if you don't need a separator.
Definition: join_with.hpp:29
T has_extension(T... args)
A type that satisfies std::is_arithmetic_v<t>.
The concept for option validators passed to add_option/positional_option.
void operator()(option_value_type const &cmp) const
Validates the value 'cmp' and throws a seqan3::validation_error on failure.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
using option_value_type
The type of value on which the validator is called on.
Provides various utility functions.
T is_directory(T... args)
T is_regular_file(T... args)
Provides seqan3::views::join_with.
The main SeqAn3 namespace.
Definition: cigar_operation_table.hpp:2
output_file_open_options
Mode of an output file: Determines whether an existing file can be (silently) overwritten.
Definition: validators.hpp:601
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.
SeqAn specific customisations in the standard namespace.
#define SEQAN3_RETURN_TYPE_CONSTRAINT(expression, concept_name,...)
Same as writing {expression} -> concept_name<type1[, ...]> in a concept definition.
Definition: platform.hpp:57
Provides seqan3::debug_stream and related types.
Adaptations of concepts from the Ranges TS.
T regex_match(T... args)
T rethrow_exception(T... args)
Provides seqan3::detail::safe_filesystem_entry.
T size(T... args)
T substr(T... args)
T throw_with_nested(T... args)
Auxiliary for pretty printing of exception messages.
Provides traits for seqan3::type_list.
Provides various traits for template packs.