SeqAn3  3.0.3
The Modern C++ library for sequence analysis.
validators.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2020, 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 
34 
35 namespace seqan3
36 {
37 
95 template <typename validator_type>
96 SEQAN3_CONCEPT validator = std::copyable<std::remove_cvref_t<validator_type>> &&
97  requires(validator_type validator,
99 {
101 
102  SEQAN3_RETURN_TYPE_CONSTRAINT(validator(value), std::same_as, void);
104 };
106 
120 {
121 public:
123  using option_value_type = double;
124 
130  min{min_}, max{max_}
131  {}
132 
137  void operator()(option_value_type const & cmp) const
138  {
139  if (!((cmp <= max) && (cmp >= min)))
140  throw validation_error{detail::to_string("Value ", cmp, " is not in range [", min, ",", max, "].")};
141  }
142 
149  template <std::ranges::forward_range range_type>
153  void operator()(range_type const & range) const
154  {
155  std::for_each(range.begin(), range.end(), [&] (auto cmp) { (*this)(cmp); });
156  }
157 
160  {
161  return detail::to_string("Value must be in range [", min, ",", max, "].");
162  }
163 
164 private:
166  option_value_type min{};
167 
169  option_value_type max{};
170 };
171 
189 template <typename option_value_t>
191 {
192 public:
194  using option_value_type = option_value_t;
195 
199  value_list_validator() = default;
204  ~value_list_validator() = default;
205 
211  template <std::ranges::forward_range range_type>
213  requires std::constructible_from<option_value_type, std::ranges::range_rvalue_reference_t<range_type>>
215  value_list_validator(range_type rng)
216  {
217  values.clear();
218  std::ranges::move(std::move(rng), std::cpp20::back_inserter(values));
219  }
220 
226  template <typename ...option_types>
228  requires ((std::constructible_from<option_value_type, option_types> && ...))
230  value_list_validator(option_types && ...opts)
231  {
232  (values.emplace_back(std::forward<option_types>(opts)), ...);
233  }
235 
240  void operator()(option_value_type const & cmp) const
241  {
242  if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
243  throw validation_error{detail::to_string("Value ", cmp, " is not one of ", std::views::all(values), ".")};
244  }
245 
251  template <std::ranges::forward_range range_type>
253  requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type>
255  void operator()(range_type const & range) const
256  {
257  std::for_each(std::ranges::begin(range), std::ranges::end(range), [&] (auto cmp) { (*this)(cmp); });
258  }
259 
262  {
263  return detail::to_string("Value must be one of ", std::views::all(values), ".");
264  }
265 
266 private:
267 
270 };
271 
277 template <typename option_type, typename ...option_types>
279  requires (std::constructible_from<std::string, std::decay_t<option_types>> && ... &&
280  std::constructible_from<std::string, std::decay_t<option_type>>)
282 value_list_validator(option_type, option_types...) -> value_list_validator<std::string>;
283 
285 template <typename range_type>
287  requires (std::ranges::forward_range<std::decay_t<range_type>> &&
288  std::constructible_from<std::string, std::ranges::range_value_t<range_type>>)
290 value_list_validator(range_type && rng) -> value_list_validator<std::string>;
291 
293 template <typename option_type, typename ...option_types>
294 value_list_validator(option_type, option_types ...) -> value_list_validator<option_type>;
295 
297 template <typename range_type>
299  requires (std::ranges::forward_range<std::decay_t<range_type>>)
301 value_list_validator(range_type && rng) -> value_list_validator<std::ranges::range_value_t<range_type>>;
303 
317 {
318 public:
319 
322 
326  file_validator_base() = default;
331  virtual ~file_validator_base() = default;
333 
341  virtual void operator()(std::filesystem::path const & path) const = 0;
342 
350  template <std::ranges::forward_range range_type>
352  requires std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
354  void operator()(range_type const & v) const
355  {
356  std::for_each(v.begin(), v.end(), [&] (auto cmp) { this->operator()(cmp); });
357  }
358 
359 protected:
365  void validate_filename(std::filesystem::path const & path) const
366  {
367  // If no valid extensions are given we can safely return here.
368  if (extensions.empty())
369  return;
370 
371  // Check if extension is available.
372  if (!path.has_extension())
373  throw validation_error{detail::to_string("The given filename ", path.string(), " has no extension. Expected"
374  " one of the following valid extensions:", extensions, "!")};
375 
376  std::string file_path{path.filename().string()};
377 
378  // Leading dot indicates a hidden file is not part of the extension.
379  if (file_path.front() == '.')
380  file_path.erase(0, 1);
381 
382  // Store a string_view containing all extensions for a better error message.
383  std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
384 
385  // Compares the extensions in lower case.
386  auto case_insensitive_ends_with = [&] (std::string const & ext)
387  {
388  return case_insensitive_string_ends_with(file_path, ext);
389  };
390 
391  // Check if requested extension is present.
392  if (std::ranges::find_if(extensions, case_insensitive_ends_with) == extensions.end())
393  {
394  throw validation_error{detail::to_string("Expected one of the following valid extensions: ", extensions,
395  "! Got ", all_extensions, " instead!")};
396  }
397  }
398 
406  {
407  // Check if input directory is readable.
409  {
410  std::error_code ec{};
411  std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
412  if (static_cast<bool>(ec))
413  throw validation_error{detail::to_string("Cannot read the directory ", path ,"!")};
414  }
415  else
416  {
417  // Must be a regular file.
419  throw validation_error{detail::to_string("Expected a regular file ", path, "!")};
420 
421  std::ifstream file{path};
422  if (!file.is_open() || !file.good())
423  throw validation_error{detail::to_string("Cannot read the file ", path, "!")};
424  }
425  }
426 
434  {
435  std::ofstream file{path};
436  detail::safe_filesystem_entry file_guard{path};
437 
438  bool is_open = file.is_open();
439  bool is_good = file.good();
440  file.close();
441 
442  if (!is_good || !is_open)
443  throw validation_error{detail::to_string("Cannot write ", path, "!")};
444 
445  file_guard.remove();
446  }
447 
450  {
451  if (extensions.empty())
452  return "";
453  else
454  return detail::to_string(" Valid file extensions are: [", extensions | views::join(std::string{", "}), "].");
455  }
456 
463  {
464  size_t const suffix_length{suffix.size()};
465  size_t const str_length{str.size()};
466  return suffix_length > str_length ?
467  false :
468  std::ranges::equal(str.substr(str_length - suffix_length), suffix, [] (char const chr1, char const chr2)
469  {
470  return std::tolower(chr1) == std::tolower(chr2);
471  });
472  }
473 
476 };
477 
500 template <typename file_t = void>
502 {
503 public:
504 
505  static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
506  "Expected either a template type with a static member called valid_formats (a file type) or void.");
507 
508  // Import from base class.
510 
524  {
525  if constexpr (!std::same_as<file_t, void>)
526  file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
527  }
528 
533  virtual ~input_file_validator() = default;
534 
545  requires std::same_as<file_t, void>
548  {
550  }
551 
552  // Import base class constructor.
555 
556  // Import the base::operator()
557  using file_validator_base::operator();
558 
564  virtual void operator()(std::filesystem::path const & file) const override
565  {
566  try
567  {
568  if (!std::filesystem::exists(file))
569  throw validation_error{detail::to_string("The file ", file, " does not exist!")};
570 
571  // Check if file is regular and can be opened for reading.
572  validate_readability(file);
573 
574  // Check extension.
575  validate_filename(file);
576  }
578  {
579  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
580  }
581  catch (...)
582  {
584  }
585  }
586 
589  {
590  return "The input file must exist and read permissions must be granted." +
592  }
593 };
594 
597 {
601  create_new
602 };
603 
630 template <typename file_t = void>
632 {
633 public:
634  static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
635  "Expected either a template type with a static member called valid_formats (a file type) or void.");
636 
637  // Import from base class.
639 
646  {}
647 
652  virtual ~output_file_validator() = default;
653 
662  : file_validator_base{}, mode{mode}
663  {
665  }
666 
667  // Import base constructor.
670 
680  {
681  if constexpr (!std::same_as<file_t, void>)
682  return detail::valid_file_extensions<typename file_t::valid_formats>();
683  return {};
684  }
685 
686  // Import the base::operator()
687  using file_validator_base::operator();
688 
694  virtual void operator()(std::filesystem::path const & file) const override
695  {
696  try
697  {
699  throw validation_error{detail::to_string("The file ", file, " already exists!")};
700 
701  // Check if file has any write permissions.
702  validate_writeability(file);
703 
704  validate_filename(file);
705  }
707  {
708  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
709  }
710  catch (...)
711  {
713  }
714  }
715 
718  {
720  return "Write permissions must be granted." + valid_extensions_help_page_message();
721  else // mode == create_new
722  return "The output file must not exist already and write permissions must be granted." +
724  }
725 
726 private:
729 };
730 
746 {
747 public:
748  // Import from base class.
750 
759  virtual ~input_directory_validator() = default;
760 
761  // Import base constructor.
764 
765  // Import the base::operator()
766  using file_validator_base::operator();
767 
773  virtual void operator()(std::filesystem::path const & dir) const override
774  {
775  try
776  {
777  if (!std::filesystem::exists(dir))
778  throw validation_error{detail::to_string("The directory ", dir, " does not exists!")};
779 
781  throw validation_error{detail::to_string("The path ", dir, " is not a directory!")};
782 
783  // Check if directory has any read permissions.
785  }
787  {
788  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
789  }
790  catch (...)
791  {
793  }
794  }
795 
798  {
799  return detail::to_string("An existing, readable path for the input directory.");
800  }
801 };
802 
818 {
819 public:
820  // Imported from base class.
822 
831  virtual ~output_directory_validator() = default;
832 
833  // Import base constructor.
836 
837  // Import the base::operator().
838  using file_validator_base::operator();
839 
845  virtual void operator()(std::filesystem::path const & dir) const override
846  {
847  bool dir_exists = std::filesystem::exists(dir);
848  // Make sure the created dir is deleted after we are done.
849  std::error_code ec;
850  std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
851  // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
852  if (static_cast<bool>(ec))
853  throw validation_error{detail::to_string("Cannot create directory: ", dir, "!")};
854 
855  try
856  {
857  if (!dir_exists)
858  {
859  detail::safe_filesystem_entry dir_guard{dir};
860  validate_writeability(dir / "dummy.txt");
861  dir_guard.remove_all();
862  }
863  else
864  {
865  validate_writeability(dir / "dummy.txt");
866  }
867  }
869  {
870  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
871  }
872  catch (...)
873  {
875  }
876  }
877 
880  {
881  return detail::to_string("A valid path for the output directory.");
882  }
883 };
884 
903 {
904 public:
907 
911  regex_validator(std::string const & pattern_) :
912  pattern{pattern_}
913  {}
914 
919  void operator()(option_value_type const & cmp) const
920  {
921  std::regex rgx(pattern);
922  if (!std::regex_match(cmp, rgx))
923  throw validation_error{detail::to_string("Value ", cmp, " did not match the pattern ", pattern, ".")};
924  }
925 
932  template <std::ranges::forward_range range_type>
934  requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type const &>
936  void operator()(range_type const & v) const
937  {
938  for (option_value_type const & file_name : v)
939  (*this)(file_name);
940  }
941 
944  {
945  return detail::to_string("Value must match the pattern '", pattern, "'.");
946  }
947 
948 private:
950  std::string pattern;
951 };
952 
953 namespace detail
954 {
955 
966 template <typename option_value_t>
967 struct default_validator
968 {
970  using option_value_type = option_value_t;
971 
973  void operator()(option_value_t const & /*cmp*/) const noexcept
974  {}
975 
978  {
979  return "";
980  }
981 };
982 
994 template <validator validator1_type, validator validator2_type>
996  requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
998 class validator_chain_adaptor
999 {
1000 public:
1002  using option_value_type = std::common_type_t<typename validator1_type::option_value_type,
1003  typename validator2_type::option_value_type>;
1004 
1008  validator_chain_adaptor() = delete;
1009  validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1010  validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1011  validator_chain_adaptor(validator_chain_adaptor &&) = default;
1012  validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1013 
1018  validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1019  vali1{std::move(vali1_)}, vali2{std::move(vali2_)}
1020  {}
1021 
1023  ~validator_chain_adaptor() = default;
1025 
1034  template <typename cmp_type>
1036  requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1038  void operator()(cmp_type const & cmp) const
1039  {
1040  vali1(cmp);
1041  vali2(cmp);
1042  }
1043 
1046  {
1047  return detail::to_string(vali1.get_help_page_message(), " ", vali2.get_help_page_message());
1048  }
1049 
1050 private:
1052  validator1_type vali1;
1054  validator2_type vali2;
1055 };
1056 
1057 } // namespace detail
1058 
1086 template <validator validator1_type, validator validator2_type>
1088  requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1091 auto operator|(validator1_type && vali1, validator2_type && vali2)
1092 {
1093  return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1),
1094  std::forward<validator2_type>(vali2)};
1095 }
1096 
1097 } // namespace seqan3
Adaptations of algorithms from the Ranges TS.
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition: validators.hpp:120
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:159
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition: validators.hpp:129
double option_value_type
The type of value that this validator invoked upon.
Definition: validators.hpp:123
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition: validators.hpp:137
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition: validators.hpp:153
An abstract base class for the file and directory validators.
Definition: validators.hpp:317
file_validator_base & operator=(file_validator_base &&)=default
Defaulted.
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:462
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition: validators.hpp:365
file_validator_base & operator=(file_validator_base const &)=default
Defaulted.
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition: validators.hpp:449
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:321
void operator()(range_type const &v) const
Tests whether every path in list v passes validation. See operator()(option_value_type const & value)...
Definition: validators.hpp:354
file_validator_base(file_validator_base &&)=default
Defaulted.
void validate_readability(std::filesystem::path const &path) const
Checks if the given path is readable.
Definition: validators.hpp:405
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:475
virtual ~file_validator_base()=default
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition: validators.hpp:433
A validator that checks if a given path is a valid input directory.
Definition: validators.hpp:746
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:797
input_directory_validator & operator=(input_directory_validator &&)=default
Defaulted.
input_directory_validator()=default
Defaulted.
input_directory_validator & operator=(input_directory_validator const &)=default
Defaulted.
input_directory_validator(input_directory_validator const &)=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:773
virtual ~input_directory_validator()=default
Virtual Destructor.
A validator that checks if a given path is a valid input file.
Definition: validators.hpp:502
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:543
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:588
input_file_validator(input_file_validator &&)=default
Defaulted.
input_file_validator & operator=(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:564
input_file_validator()
Default constructor.
Definition: validators.hpp:523
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:818
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:845
output_directory_validator & operator=(output_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:879
A validator that checks if a given path is a valid output file.
Definition: validators.hpp:632
output_file_validator(output_file_validator &&)=default
Defaulted.
output_file_validator(output_file_validator const &)=default
Defaulted.
output_file_validator & operator=(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:694
static std::vector< std::string > default_extensions()
The default extensions of file_t.
Definition: validators.hpp:679
output_file_validator()
Default constructor.
Definition: validators.hpp:645
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:717
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:660
A validator that checks if a matches a regular expression pattern.
Definition: validators.hpp:903
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:943
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:919
void operator()(range_type const &v) const
Tests whether every filename in list v matches the pattern.
Definition: validators.hpp:936
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:906
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition: validators.hpp:911
Argument parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:141
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:191
value_list_validator()=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:240
value_list_validator(value_list_validator const &)=default
Defaulted.
option_value_t option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:194
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:261
value_list_validator & operator=(value_list_validator &&)=default
Defaulted.
void operator()(range_type const &range) const
Tests whether every element in range lies inside values.
Definition: validators.hpp:255
value_list_validator(value_list_validator &&)=default
Defaulted.
~value_list_validator()=default
Defaulted.
value_list_validator & operator=(value_list_validator const &)=default
Defaulted.
value_list_validator(range_type rng)
Constructing from a range.
Definition: validators.hpp:215
T clear(T... args)
The Concepts library.
T create_directory(T... args)
T current_exception(T... args)
Auxiliary for pretty printing of exception messages.
Provides seqan3::debug_stream and related types.
T emplace_back(T... args)
T end(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:1091
constexpr ptrdiff_t find_if
Get the index of the first type in a pack that satisfies the given predicate.
Definition: traits.hpp:209
auto const move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:70
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.
The main SeqAn3 namespace.
Definition: aligned_sequence_concept.hpp:29
output_file_open_options
Mode of an output file: Determines whether an existing file can be (silently) overwritten.
Definition: validators.hpp:597
@ 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
Adaptations of concepts from the standard library.
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)
Provides traits for seqan3::type_list.
Provides various traits for template packs.
Provides various type traits on generic types.