SeqAn3  3.1.0
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 
34 namespace seqan3
35 {
36 
96 template <typename validator_type>
97 SEQAN3_CONCEPT validator = std::copyable<std::remove_cvref_t<validator_type>> &&
98  requires(validator_type validator,
100 {
102 
103  SEQAN3_RETURN_TYPE_CONSTRAINT(validator(value), std::same_as, void);
105 };
107 
123 template <arithmetic option_value_t>
125 {
126 public:
128  using option_value_type = option_value_t;
129 
135  min{min_}, max{max_}
136  {}
137 
142  void operator()(option_value_type const & cmp) const
143  {
144  if (!((cmp <= max) && (cmp >= min)))
145  throw validation_error{detail::to_string("Value ", cmp, " is not in range [", min, ",", max, "].")};
146  }
147 
154  template <std::ranges::forward_range range_type>
158  void operator()(range_type const & range) const
159  {
160  std::for_each(range.begin(), range.end(), [&] (auto cmp) { (*this)(cmp); });
161  }
162 
165  {
166  return detail::to_string("Value must be in range [", min, ",", max, "].");
167  }
168 
169 private:
171  option_value_type min{};
172 
174  option_value_type max{};
175 };
176 
196 template <typename option_value_t>
198 {
199 public:
201  using option_value_type = option_value_t;
202 
206  value_list_validator() = default;
211  ~value_list_validator() = default;
212 
218  template <std::ranges::forward_range range_type>
220  requires std::constructible_from<option_value_type, std::ranges::range_rvalue_reference_t<range_type>>
222  value_list_validator(range_type rng)
223  {
224  values.clear();
225  std::ranges::move(std::move(rng), std::cpp20::back_inserter(values));
226  }
227 
233  template <typename ...option_types>
235  requires ((std::constructible_from<option_value_type, option_types> && ...))
237  value_list_validator(option_types && ...opts)
238  {
239  (values.emplace_back(std::forward<option_types>(opts)), ...);
240  }
242 
247  void operator()(option_value_type const & cmp) const
248  {
249  if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
250  throw validation_error{detail::to_string("Value ", cmp, " is not one of ", std::views::all(values), ".")};
251  }
252 
258  template <std::ranges::forward_range range_type>
260  requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type>
262  void operator()(range_type const & range) const
263  {
264  std::for_each(std::ranges::begin(range), std::ranges::end(range), [&] (auto cmp) { (*this)(cmp); });
265  }
266 
269  {
270  return detail::to_string("Value must be one of ", std::views::all(values), ".");
271  }
272 
273 private:
274 
277 };
278 
284 template <typename option_type, typename ...option_types>
286  requires (std::constructible_from<std::string, std::decay_t<option_types>> && ... &&
287  std::constructible_from<std::string, std::decay_t<option_type>>)
289 value_list_validator(option_type, option_types...) -> value_list_validator<std::string>;
290 
292 template <typename range_type>
294  requires (std::ranges::forward_range<std::decay_t<range_type>> &&
295  std::constructible_from<std::string, std::ranges::range_value_t<range_type>>)
297 value_list_validator(range_type && rng) -> value_list_validator<std::string>;
298 
300 template <typename option_type, typename ...option_types>
301 value_list_validator(option_type, option_types ...) -> value_list_validator<option_type>;
302 
304 template <typename range_type>
306  requires (std::ranges::forward_range<std::decay_t<range_type>>)
308 value_list_validator(range_type && rng) -> value_list_validator<std::ranges::range_value_t<range_type>>;
310 
326 {
327 public:
328 
331 
335  file_validator_base() = default;
340  virtual ~file_validator_base() = default;
342 
350  virtual void operator()(std::filesystem::path const & path) const = 0;
351 
359  template <std::ranges::forward_range range_type>
361  requires (std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
362  && !std::convertible_to<range_type, std::filesystem::path const &>)
364  void operator()(range_type const & v) const
365  {
366  std::for_each(v.begin(), v.end(), [&] (auto cmp) { this->operator()(cmp); });
367  }
368 
369 protected:
375  void validate_filename(std::filesystem::path const & path) const
376  {
377  // If no valid extensions are given we can safely return here.
378  if (extensions.empty())
379  return;
380 
381  // Check if extension is available.
382  if (!path.has_extension())
383  throw validation_error{detail::to_string("The given filename ", path.string(), " has no extension. Expected"
384  " one of the following valid extensions:", extensions, "!")};
385 
386  std::string file_path{path.filename().string()};
387 
388  // Leading dot indicates a hidden file is not part of the extension.
389  if (file_path.front() == '.')
390  file_path.erase(0, 1);
391 
392  // Store a string_view containing all extensions for a better error message.
393  std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
394 
395  // Compares the extensions in lower case.
396  auto case_insensitive_ends_with = [&] (std::string const & ext)
397  {
398  return case_insensitive_string_ends_with(file_path, ext);
399  };
400 
401  // Check if requested extension is present.
402  if (std::ranges::find_if(extensions, case_insensitive_ends_with) == extensions.end())
403  {
404  throw validation_error{detail::to_string("Expected one of the following valid extensions: ", extensions,
405  "! Got ", all_extensions, " instead!")};
406  }
407  }
408 
416  {
417  // Check if input directory is readable.
419  {
420  std::error_code ec{};
421  std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
422  if (static_cast<bool>(ec))
423  throw validation_error{detail::to_string("Cannot read the directory ", path ,"!")};
424  }
425  else
426  {
427  // Must be a regular file.
429  throw validation_error{detail::to_string("Expected a regular file ", path, "!")};
430 
431  std::ifstream file{path};
432  if (!file.is_open() || !file.good())
433  throw validation_error{detail::to_string("Cannot read the file ", path, "!")};
434  }
435  }
436 
444  {
445  std::ofstream file{path};
446  detail::safe_filesystem_entry file_guard{path};
447 
448  bool is_open = file.is_open();
449  bool is_good = file.good();
450  file.close();
451 
452  if (!is_good || !is_open)
453  throw validation_error{detail::to_string("Cannot write ", path, "!")};
454 
455  file_guard.remove();
456  }
457 
460  {
461  if (extensions.empty())
462  return "";
463  else
464  return detail::to_string(" Valid file extensions are: [", extensions | views::join_with(std::string{", "}), "].");
465  }
466 
473  {
474  size_t const suffix_length{suffix.size()};
475  size_t const str_length{str.size()};
476  return suffix_length > str_length ?
477  false :
478  std::ranges::equal(str.substr(str_length - suffix_length), suffix, [] (char const chr1, char const chr2)
479  {
480  return std::tolower(chr1) == std::tolower(chr2);
481  });
482  }
483 
486 };
487 
512 template <typename file_t = void>
514 {
515 public:
516 
517  static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
518  "Expected either a template type with a static member called valid_formats (a file type) or void.");
519 
520  // Import from base class.
522 
536  {
537  if constexpr (!std::same_as<file_t, void>)
538  file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
539  }
540 
545  virtual ~input_file_validator() = default;
546 
557  requires std::same_as<file_t, void>
560  {
562  }
563 
564  // Import base class constructor.
567 
568  // Import the base::operator()
569  using file_validator_base::operator();
570 
576  virtual void operator()(std::filesystem::path const & file) const override
577  {
578  try
579  {
580  if (!std::filesystem::exists(file))
581  throw validation_error{detail::to_string("The file ", file, " does not exist!")};
582 
583  // Check if file is regular and can be opened for reading.
584  validate_readability(file);
585 
586  // Check extension.
587  validate_filename(file);
588  }
590  {
591  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
592  }
593  catch (...)
594  {
596  }
597  }
598 
601  {
602  return "The input file must exist and read permissions must be granted." +
604  }
605 };
606 
609 {
613  create_new
614 };
615 
644 template <typename file_t = void>
646 {
647 public:
648  static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
649  "Expected either a template type with a static member called valid_formats (a file type) or void.");
650 
651  // Import from base class.
653 
660  {}
661 
666  virtual ~output_file_validator() = default;
667 
676  : file_validator_base{}, mode{mode}
677  {
679  }
680 
681  // Import base constructor.
684 
694  {
695  if constexpr (!std::same_as<file_t, void>)
696  return detail::valid_file_extensions<typename file_t::valid_formats>();
697  return {};
698  }
699 
700  // Import the base::operator()
701  using file_validator_base::operator();
702 
708  virtual void operator()(std::filesystem::path const & file) const override
709  {
710  try
711  {
713  throw validation_error{detail::to_string("The file ", file, " already exists!")};
714 
715  // Check if file has any write permissions.
716  validate_writeability(file);
717 
718  validate_filename(file);
719  }
721  {
722  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
723  }
724  catch (...)
725  {
727  }
728  }
729 
732  {
734  return "Write permissions must be granted." + valid_extensions_help_page_message();
735  else // mode == create_new
736  return "The output file must not exist already and write permissions must be granted." +
738  }
739 
740 private:
743 };
744 
762 {
763 public:
764  // Import from base class.
766 
775  virtual ~input_directory_validator() = default;
776 
777  // Import base constructor.
780 
781  // Import the base::operator()
782  using file_validator_base::operator();
783 
789  virtual void operator()(std::filesystem::path const & dir) const override
790  {
791  try
792  {
793  if (!std::filesystem::exists(dir))
794  throw validation_error{detail::to_string("The directory ", dir, " does not exists!")};
795 
797  throw validation_error{detail::to_string("The path ", dir, " is not a directory!")};
798 
799  // Check if directory has any read permissions.
801  }
803  {
804  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
805  }
806  catch (...)
807  {
809  }
810  }
811 
814  {
815  return detail::to_string("An existing, readable path for the input directory.");
816  }
817 };
818 
836 {
837 public:
838  // Imported from base class.
840 
849  virtual ~output_directory_validator() = default;
850 
851  // Import base constructor.
854 
855  // Import the base::operator().
856  using file_validator_base::operator();
857 
863  virtual void operator()(std::filesystem::path const & dir) const override
864  {
865  bool dir_exists = std::filesystem::exists(dir);
866  // Make sure the created dir is deleted after we are done.
867  std::error_code ec;
868  std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
869  // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
870  if (static_cast<bool>(ec))
871  throw validation_error{detail::to_string("Cannot create directory: ", dir, "!")};
872 
873  try
874  {
875  if (!dir_exists)
876  {
877  detail::safe_filesystem_entry dir_guard{dir};
878  validate_writeability(dir / "dummy.txt");
879  dir_guard.remove_all();
880  }
881  else
882  {
883  validate_writeability(dir / "dummy.txt");
884  }
885  }
887  {
888  std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
889  }
890  catch (...)
891  {
893  }
894  }
895 
898  {
899  return detail::to_string("A valid path for the output directory.");
900  }
901 };
902 
923 {
924 public:
927 
931  regex_validator(std::string const & pattern_) :
932  pattern{pattern_}
933  {}
934 
939  void operator()(option_value_type const & cmp) const
940  {
941  std::regex rgx(pattern);
942  if (!std::regex_match(cmp, rgx))
943  throw validation_error{detail::to_string("Value ", cmp, " did not match the pattern ", pattern, ".")};
944  }
945 
952  template <std::ranges::forward_range range_type>
954  requires std::convertible_to<std::ranges::range_reference_t<range_type>, option_value_type const &>
956  void operator()(range_type const & v) const
957  {
958  for (auto && file_name : v)
959  {
960  // note: we explicitly copy/construct any reference type other than `std::string &`
961  (*this)(static_cast<option_value_type const &>(file_name));
962  }
963  }
964 
967  {
968  return detail::to_string("Value must match the pattern '", pattern, "'.");
969  }
970 
971 private:
973  std::string pattern;
974 };
975 
976 namespace detail
977 {
978 
991 template <typename option_value_t>
992 struct default_validator
993 {
995  using option_value_type = option_value_t;
996 
998  void operator()(option_value_t const & /*cmp*/) const noexcept
999  {}
1000 
1003  {
1004  return "";
1005  }
1006 };
1007 
1021 template <validator validator1_type, validator validator2_type>
1023  requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
1025 class validator_chain_adaptor
1026 {
1027 public:
1029  using option_value_type = std::common_type_t<typename validator1_type::option_value_type,
1030  typename validator2_type::option_value_type>;
1031 
1035  validator_chain_adaptor() = delete;
1036  validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1037  validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1038  validator_chain_adaptor(validator_chain_adaptor &&) = default;
1039  validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1040 
1045  validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1046  vali1{std::move(vali1_)}, vali2{std::move(vali2_)}
1047  {}
1048 
1050  ~validator_chain_adaptor() = default;
1052 
1061  template <typename cmp_type>
1063  requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1065  void operator()(cmp_type const & cmp) const
1066  {
1067  vali1(cmp);
1068  vali2(cmp);
1069  }
1070 
1073  {
1074  return detail::to_string(vali1.get_help_page_message(), " ", vali2.get_help_page_message());
1075  }
1076 
1077 private:
1079  validator1_type vali1;
1081  validator2_type vali2;
1082 };
1083 
1084 } // namespace detail
1085 
1115 template <validator validator1_type, validator validator2_type>
1117  requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1120 auto operator|(validator1_type && vali1, validator2_type && vali2)
1121 {
1122  return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1),
1123  std::forward<validator2_type>(vali2)};
1124 }
1125 
1126 } // namespace seqan3
The <algorithm> header from C++20's standard library.
Provides various type traits on generic types.
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition: validators.hpp:125
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition: validators.hpp:142
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition: validators.hpp:128
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition: validators.hpp:158
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:164
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition: validators.hpp:134
An abstract base class for the file and directory validators.
Definition: validators.hpp:326
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:472
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition: validators.hpp:375
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:459
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:330
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:415
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:485
virtual ~file_validator_base()=default
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition: validators.hpp:443
A validator that checks if a given path is a valid input directory.
Definition: validators.hpp:762
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:813
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:789
virtual ~input_directory_validator()=default
Virtual Destructor.
A validator that checks if a given path is a valid input file.
Definition: validators.hpp:514
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:555
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:600
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:576
input_file_validator()
Default constructor.
Definition: validators.hpp:535
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:836
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:863
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:897
A validator that checks if a given path is a valid output file.
Definition: validators.hpp:646
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:708
static std::vector< std::string > default_extensions()
The default extensions of file_t.
Definition: validators.hpp:693
output_file_validator()
Default constructor.
Definition: validators.hpp:659
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:731
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:674
A validator that checks if a matches a regular expression pattern.
Definition: validators.hpp:923
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:966
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:939
void operator()(range_type const &v) const
Tests whether every filename in list v matches the pattern.
Definition: validators.hpp:956
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:926
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition: validators.hpp:931
Argument parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:124
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:198
value_list_validator()=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:247
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:201
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:268
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:262
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:222
T clear(T... args)
The <concepts> header from C++20's standard library.
Provides concepts for core language types and relations that don't have concepts in C++20 (yet).
T create_directory(T... args)
T current_exception(T... args)
T emplace_back(T... args)
T end(T... args)
Provides parser related exceptions.
T exists(T... args)
T filename(T... args)
The <filesystem> header from C++17's standard library.
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:1120
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: 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:609
@ 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.
The <ranges> header from C++20's standard library.
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.