Sharg 1.1.2-rc.1
The argument parser for bio-c++ tools.
Loading...
Searching...
No Matches
validators.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2024, Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2024, Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <algorithm>
13#include <any>
14#include <concepts>
15#include <exception>
16#include <fstream>
17#include <ranges>
18#include <regex>
19
22#include <sharg/exceptions.hpp>
23
24namespace sharg
25{
26
47// clang-format off
48template <typename validator_type>
51 requires(validator_type validator,
53 {
54 {validator(value)} -> std::same_as<void>;
55 {validator.get_help_page_message()} -> std::same_as<std::string>;
56 };
57// clang-format on
58
76template <typename option_value_t>
79{
80public:
82 using option_value_type = option_value_t;
83
92 min{min_},
93 max{max_},
94 valid_range_str{"[" + std::to_string(min_) + "," + std::to_string(max_) + "]"}
95 {}
96
104 void operator()(option_value_type const & cmp) const
105 {
106 if (!((cmp <= max) && (cmp >= min)))
107 throw validation_error{"Value " + std::to_string(cmp) + " is not in range " + valid_range_str + "."};
108 }
109
119 template <std::ranges::forward_range range_type>
121 void operator()(range_type const & range) const
122 {
123 std::for_each(range.begin(),
124 range.end(),
125 [&](auto cmp)
126 {
127 (*this)(cmp);
128 });
129 }
130
137 {
138 return std::string{"Value must be in range "} + valid_range_str + ".";
139 }
140
141private:
143 option_value_type min{};
144
146 option_value_type max{};
147
149 std::string valid_range_str{};
150};
151
173template <parsable option_value_t>
175{
176public:
178 using option_value_type = option_value_t;
179
189
198 template <std::ranges::forward_range range_type>
200 value_list_validator(range_type rng) // No &&, because rng will be moved.
201 {
202 std::move(rng.begin(), rng.end(), std::back_inserter(values));
203 }
204
213 template <typename... option_types>
215 value_list_validator(option_types &&... opts)
216 {
217 (values.emplace_back(std::forward<option_types>(opts)), ...);
218 }
220
228 void operator()(option_value_type const & cmp) const
229 {
230 if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
231 throw validation_error{detail::to_string("Value ", cmp, " is not one of ", values, ".")};
232 }
233
242 template <std::ranges::forward_range range_type>
245 void operator()(range_type const & range) const
246 {
248 std::ranges::end(range),
249 [&](auto cmp)
250 {
251 (*this)(cmp);
252 });
253 }
254
261 {
262 return detail::to_string("Value must be one of ", values, ".");
263 }
264
265private:
268};
269
275template <typename option_type, typename... option_types>
279
281template <typename range_type>
282 requires (std::ranges::forward_range<std::decay_t<range_type>>
285
287template <typename option_type, typename... option_types>
289
291template <typename range_type>
292 requires (std::ranges::forward_range<std::decay_t<range_type>>)
295
312{
313public:
316
325 virtual ~file_validator_base() = default;
327
337 virtual void operator()(std::filesystem::path const & path) const = 0;
338
349 template <std::ranges::forward_range range_type>
352 void operator()(range_type const & v) const
353 {
354 std::for_each(v.begin(),
355 v.end(),
356 [&](auto cmp)
357 {
358 this->operator()(cmp);
359 });
360 }
361
362protected:
369 {
370 // If no valid extensions are given we can safely return here.
371 if (extensions.empty())
372 return;
373
374 // Check if extension is available.
375 if (!path.has_extension())
376 {
377 throw validation_error{"The given filename " + path.string()
378 + " has no extension. Expected one of the "
379 "following valid extensions:"
380 + extensions_str + "!"};
381 }
382
383 std::string file_path{path.filename().string()};
384
385 // Leading dot indicates a hidden file is not part of the extension.
386 if (file_path.front() == '.')
387 file_path.erase(0, 1);
388
389 // Store a string_view containing all extensions for a better error message.
390 std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
391
392 // Compares the extensions in lower case.
393 auto case_insensitive_ends_with = [&](std::string const & ext)
394 {
395 return case_insensitive_string_ends_with(file_path, ext);
396 };
397
398 // Check if requested extension is present.
399 if (std::find_if(extensions.begin(), extensions.end(), case_insensitive_ends_with) == extensions.end())
400 {
401 throw validation_error{"Expected one of the following valid extensions: " + extensions_str + "! Got "
402 + all_extensions + " instead!"};
403 }
404 }
405
412 {
413 // Check if input directory is readable.
415 {
416 std::error_code ec{};
417 std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
418 if (static_cast<bool>(ec))
419 throw validation_error{"Cannot read the directory \"" + path.string() + "\"!"};
420 }
421 else
422 {
423 // Must be a regular file.
425 throw validation_error{"Expected a regular file \"" + path.string() + "\"!"};
426
427 std::ifstream file{path};
428 if (!file.is_open() || !file.good())
429 throw validation_error{"Cannot read the file \"" + path.string() + "\"!"};
430 }
431 }
432
440 {
441 // Contingency check. This case should already be handled by the output_file_validator.
442 // Opening a file handle on a directory would delete its contents.
443 // LCOV_EXCL_START
445 throw validation_error{"\"" + path.string() + "\" is a directory. Cannot validate writeability."};
446 // LCOV_EXCL_STOP
447
448 std::ofstream file{path};
449 sharg::detail::safe_filesystem_entry file_guard{path};
450
451 bool is_open = file.is_open();
452 bool is_good = file.good();
453 file.close();
454
455 if (!is_good || !is_open)
456 throw validation_error{"Cannot write \"" + path.string() + "\"!"};
457
458 file_guard.remove();
459 }
460
463 {
464 if (extensions.empty())
465 return "";
466 else
467 return "Valid file extensions are: " + extensions_str + ".";
468 }
469
476 {
477 size_t const suffix_length{suffix.size()};
478 size_t const str_length{str.size()};
479
480 if (suffix_length > str_length)
481 return false;
482
483 for (size_t j = 0, s_start = str_length - suffix_length; j < suffix_length; ++j)
484 if (std::tolower(str[s_start + j]) != std::tolower(suffix[j]))
485 return false;
486
487 return true;
488 }
489
492
495};
496
521{
522public:
523 // Import from base class.
525
534 virtual ~input_file_validator() = default;
535
547
548 // Import base class constructor.
551
552 // Import the base::operator()
553 using file_validator_base::operator();
554
563 virtual void operator()(std::filesystem::path const & file) const override
564 {
565 try
566 {
567 if (!std::filesystem::exists(file))
568 throw validation_error{"The file \"" + file.string() + "\" does not exist!"};
569
570 // Check if file is regular and can be opened for reading.
572
573 // Check extension.
574 validate_filename(file);
575 }
576 // LCOV_EXCL_START
578 {
579 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
580 }
581 // LCOV_EXCL_STOP
582 catch (...)
583 {
585 }
586 }
587
593 {
594 return "The input file must exist and read permissions must be granted."
597 }
598};
599
611
640{
641public:
642 // Import from base class.
644
653 virtual ~output_file_validator() = default;
654
664 open_mode{mode}
665 {
668 }
669
680 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
682 {}
683
693
701 explicit output_file_validator(auto &&... extensions)
702 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
703 : output_file_validator{std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
704 {}
705
706 // Import base constructor.
707 using file_validator_base::file_validator_base;
709
710 // Import the base::operator()
711 using file_validator_base::operator();
712
721 virtual void operator()(std::filesystem::path const & file) const override
722 {
724 throw validation_error{"\"" + file.string() + "\" is a directory. Expected a file."};
725
726 try
727 {
728 if ((open_mode == output_file_open_options::create_new) && std::filesystem::exists(file))
729 throw validation_error{"The file \"" + file.string() + "\" already exists!"};
730
731 // Check if file has any write permissions.
732 validate_writeability(file);
733
734 validate_filename(file);
735 }
736 // LCOV_EXCL_START
738 {
739 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
740 }
741 // LCOV_EXCL_STOP
742 catch (...)
743 {
745 }
746 }
747
754 {
755 if (open_mode == output_file_open_options::open_or_create)
756 {
757 return "Write permissions must be granted."
758 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
759 + valid_extensions_help_page_message();
760 }
761 else // open_mode == create_new
762 {
763 return "The output file must not exist already and write permissions must be granted."
764 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
765 + valid_extensions_help_page_message();
766 }
767 }
768
769private:
771 output_file_open_options open_mode{output_file_open_options::create_new};
772};
773
793{
794public:
795 // Import from base class.
797
806 virtual ~input_directory_validator() = default;
807
808 // Import base constructor.
809 using file_validator_base::file_validator_base;
811
812 // Import the base::operator()
813 using file_validator_base::operator();
814
823 virtual void operator()(std::filesystem::path const & dir) const override
824 {
825 try
826 {
827 if (!std::filesystem::exists(dir))
828 throw validation_error{"The directory \"" + dir.string() + "\" does not exists!"};
829
831 throw validation_error{"The path \"" + dir.string() + "\" is not a directory!"};
832
833 // Check if directory has any read permissions.
834 validate_readability(dir);
835 }
836 // LCOV_EXCL_START
838 {
839 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
840 }
841 // LCOV_EXCL_STOP
842 catch (...)
843 {
845 }
846 }
847
854 {
855 return "An existing, readable path for the input directory.";
856 }
857};
858
878{
879public:
880 // Imported from base class.
882
891 virtual ~output_directory_validator() = default;
892
893 // Import base constructor.
894 using file_validator_base::file_validator_base;
896
897 // Import the base::operator().
898 using file_validator_base::operator();
899
908 virtual void operator()(std::filesystem::path const & dir) const override
909 {
910 bool dir_exists = std::filesystem::exists(dir);
911 // Make sure the created dir is deleted after we are done.
913 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
914 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
915 if (static_cast<bool>(ec))
916 throw validation_error{"Cannot create directory: \"" + dir.string() + "\"!"};
917
918 try
919 {
920 if (!dir_exists)
921 {
922 sharg::detail::safe_filesystem_entry dir_guard{dir};
923 validate_writeability(dir / "dummy.txt");
924 dir_guard.remove_all();
925 }
926 else
927 {
928 validate_writeability(dir / "dummy.txt");
929 }
930 }
931 // LCOV_EXCL_START
933 {
934 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
935 }
936 // LCOV_EXCL_STOP
937 catch (...)
938 {
940 }
941 }
942
949 {
950 return "A valid path for the output directory.";
951 }
952};
953
976{
977public:
980
987 regex_validator(std::string const & pattern_) : pattern{pattern_}
988 {}
989
997 void operator()(option_value_type const & cmp) const
998 {
999 std::regex rgx(pattern);
1000 if (!std::regex_match(cmp, rgx))
1001 throw validation_error{"Value " + cmp + " did not match the pattern " + pattern + "."};
1002 }
1003
1013 template <std::ranges::forward_range range_type>
1015 void operator()(range_type const & v) const
1016 {
1017 for (auto && entry : v)
1018 {
1019 // note: we explicitly copy/construct any reference type other than `std::string &`
1020 (*this)(static_cast<std::string const &>(entry));
1021 }
1022 }
1023
1030 {
1031 return "Value must match the pattern '" + pattern + "'.";
1032 }
1033
1034private:
1036 std::string pattern;
1037};
1038
1039namespace detail
1040{
1041
1053struct default_validator
1054{
1056 using option_value_type = std::any;
1057
1059 template <typename option_value_t>
1060 void operator()(option_value_t const & /*cmp*/) const noexcept
1061 {}
1062
1064 std::string get_help_page_message() const
1065 {
1066 return "";
1067 }
1068};
1069
1083template <validator validator1_type, validator validator2_type>
1085class validator_chain_adaptor
1086{
1087public:
1089 using option_value_type =
1091
1095 validator_chain_adaptor() = delete;
1096 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1097 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1098 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1099 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1100
1105 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1106 vali1{std::move(vali1_)},
1107 vali2{std::move(vali2_)}
1108 {}
1109
1111 ~validator_chain_adaptor() = default;
1113
1122 template <typename cmp_type>
1124 void operator()(cmp_type const & cmp) const
1125 {
1126 vali1(cmp);
1127 vali2(cmp);
1128 }
1129
1131 std::string get_help_page_message() const
1132 {
1133 return vali1.get_help_page_message() + " " + vali2.get_help_page_message();
1134 }
1135
1136private:
1138 validator1_type vali1;
1140 validator2_type vali2;
1141};
1142
1143} // namespace detail
1144
1176template <validator validator1_type, validator validator2_type>
1179auto operator|(validator1_type && vali1, validator2_type && vali2)
1180{
1181 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1), std::forward<validator2_type>(vali2)};
1182}
1183
1184} // namespace sharg
T back_inserter(T... args)
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition validators.hpp:79
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:136
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition validators.hpp:104
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition validators.hpp:82
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition validators.hpp:91
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition validators.hpp:121
An abstract base class for the file and directory validators.
Definition validators.hpp:312
file_validator_base()=default
Defaulted.
std::vector< std::string > extensions
Stores the extensions.
Definition validators.hpp:491
void validate_readability(std::filesystem::path const &path) const
Checks if the given path is readable.
Definition validators.hpp:411
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition validators.hpp:368
virtual void operator()(std::filesystem::path const &path) const =0
Tests if the given path is a valid input, respectively output, file or directory.
file_validator_base & operator=(file_validator_base const &)=default
Defaulted.
std::string extensions_str
The extension range as a std:;string for pretty printing.
Definition validators.hpp:494
virtual ~file_validator_base()=default
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition validators.hpp:462
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:475
std::string option_value_type
Type of values that are tested by validator.
Definition validators.hpp:315
file_validator_base(file_validator_base const &)=default
Defaulted.
file_validator_base(file_validator_base &&)=default
Defaulted.
file_validator_base & operator=(file_validator_base &&)=default
Defaulted.
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition validators.hpp:439
A validator that checks if a given path is a valid input directory.
Definition validators.hpp:793
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:853
input_directory_validator()=default
Defaulted.
input_directory_validator(input_directory_validator &&)=default
Defaulted.
input_directory_validator & operator=(input_directory_validator const &)=default
Defaulted.
virtual ~input_directory_validator()=default
Virtual Destructor.
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:823
input_directory_validator & operator=(input_directory_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid input file.
Definition validators.hpp:521
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.
input_file_validator(input_file_validator const &)=default
Defaulted.
input_file_validator()=default
Defaulted.
input_file_validator & operator=(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:542
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is an existing regular file and is readable.
Definition validators.hpp:563
input_file_validator & operator=(input_file_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid output directory.
Definition validators.hpp:878
output_directory_validator(output_directory_validator &&)=default
Defaulted.
output_directory_validator(output_directory_validator const &)=default
Defaulted.
output_directory_validator & operator=(output_directory_validator &&)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is writable.
Definition validators.hpp:908
virtual ~output_directory_validator()=default
Virtual Destructor.
output_directory_validator & operator=(output_directory_validator const &)=default
Defaulted.
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:948
A validator that checks if a given path is a valid output file.
Definition validators.hpp:640
output_file_validator()=default
Defaulted.
output_file_validator(auto &&... extensions)
Constructs from a parameter pack of valid extensions.
Definition validators.hpp:701
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:753
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition validators.hpp:721
virtual ~output_file_validator()=default
Virtual Destructor.
output_file_validator(output_file_validator &&)=default
Defaulted.
output_file_validator(std::vector< std::string > const &extensions)
Constructs from a list of valid extensions.
Definition validators.hpp:690
output_file_validator(output_file_open_options const mode, auto &&... extensions)
Constructs from a given overwrite mode and a parameter pack of valid extensions.
Definition validators.hpp:679
output_file_validator(output_file_open_options const mode, std::vector< std::string > const &extensions)
Constructs from a given overwrite mode and a list of valid extensions.
Definition validators.hpp:663
output_file_validator & operator=(output_file_validator const &)=default
Defaulted.
output_file_validator(output_file_validator const &)=default
Defaulted.
output_file_validator & operator=(output_file_validator &&)=default
Defaulted.
A validator that checks if a matches a regular expression pattern.
Definition validators.hpp:976
void operator()(range_type const &v) const
Tests whether every entry in list v matches the pattern.
Definition validators.hpp:1015
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:1029
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition validators.hpp:997
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition validators.hpp:987
Parser exception thrown when an argument could not be casted to the according type.
Definition exceptions.hpp:180
A validator that checks whether a value is inside a list of valid values.
Definition validators.hpp:175
value_list_validator(option_type, option_types...) -> value_list_validator< option_type >
Deduction guide for a parameter pack.
value_list_validator & operator=(value_list_validator const &)=default
Defaulted.
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:178
value_list_validator()=default
Defaulted.
value_list_validator(range_type rng)
Constructing from a range.
Definition validators.hpp:200
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:260
value_list_validator(value_list_validator &&)=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition validators.hpp:228
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 >
Given a parameter pack of types that are convertible to std::string, delegate to value type std::stri...
~value_list_validator()=default
Defaulted.
value_list_validator & operator=(value_list_validator &&)=default
Defaulted.
value_list_validator(option_types &&... opts)
Constructing from a parameter pack.
Definition validators.hpp:215
value_list_validator(range_type &&rng) -> value_list_validator< std::ranges::range_value_t< range_type > >
Deduction guide for ranges.
The concept for option validators passed to add_option/positional_option.
Definition validators.hpp:49
T create_directory(T... args)
T current_exception(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
Provides parser related exceptions.
T exists(T... args)
T filename(T... args)
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:1179
T has_extension(T... args)
T is_directory(T... args)
T is_regular_file(T... args)
T is_same_v
T move(T... args)
T regex_match(T... args)
T rethrow_exception(T... args)
Provides sharg::detail::safe_filesystem_entry.
T size(T... args)
T substr(T... args)
T throw_with_nested(T... args)
Provides sharg::detail::to_string.
T to_string(T... args)
output_file_open_options
Mode of an output file: Determines whether an existing file can be (silently) overwritten.
Definition validators.hpp:605
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.
Hide me