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:
144
147
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 {
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 // if directory iterator cannot be created, ec will be set.
418 std::filesystem::directory_iterator{path, ec}; // NOLINT(bugprone-unused-raii)
419 if (static_cast<bool>(ec))
420 throw validation_error{"Cannot read the directory \"" + path.string() + "\"!"};
421 }
422 else
423 {
424 // Must be a regular file.
426 throw validation_error{"Expected a regular file \"" + path.string() + "\"!"};
427
428 std::ifstream file{path};
429 if (!file.is_open() || !file.good())
430 throw validation_error{"Cannot read the file \"" + path.string() + "\"!"};
431 }
432 }
433
441 {
442 // Contingency check. This case should already be handled by the output_file_validator.
443 // Opening a file handle on a directory would delete its contents.
444 // LCOV_EXCL_START
446 throw validation_error{"\"" + path.string() + "\" is a directory. Cannot validate writeability."};
447 // LCOV_EXCL_STOP
448
449 std::ofstream file{path};
451
452 bool is_open = file.is_open();
453 bool is_good = file.good();
454 file.close();
455
456 if (!is_good || !is_open)
457 throw validation_error{"Cannot write \"" + path.string() + "\"!"};
458
459 file_guard.remove();
460 }
461
464 {
465 if (extensions.empty())
466 return "";
467 else
468 return "Valid file extensions are: " + extensions_str + ".";
469 }
470
477 {
478 size_t const suffix_length{suffix.size()};
479 size_t const str_length{str.size()};
480
481 if (suffix_length > str_length)
482 return false;
483
484 for (size_t j = 0, s_start = str_length - suffix_length; j < suffix_length; ++j)
485 if (std::tolower(str[s_start + j]) != std::tolower(suffix[j]))
486 return false;
487
488 return true;
489 }
490
493
496};
497
522{
523public:
524 // Import from base class.
526
535 virtual ~input_file_validator() = default;
536
548
549 // Import base class constructor.
552
553 // Import the base::operator()
554 using file_validator_base::operator();
555
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{"The file \"" + file.string() + "\" does not exist!"};
570
571 // Check if file is regular and can be opened for reading.
573
574 // Check extension.
575 validate_filename(file);
576 }
577 // LCOV_EXCL_START
579 {
580 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
581 }
582 // LCOV_EXCL_STOP
583 catch (...)
584 {
586 }
587 }
588
594 {
595 return "The input file must exist and read permissions must be granted."
598 }
599};
600
605enum class output_file_open_options : uint8_t
606{
611};
612
641{
642public:
643 // Import from base class.
645
654 virtual ~output_file_validator() = default;
655
670
681 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
683 {}
684
694
702 explicit output_file_validator(auto &&... extensions)
703 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
704 : output_file_validator{std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
705 {}
706
707 // Import base constructor.
708 using file_validator_base::file_validator_base;
710
711 // Import the base::operator()
712 using file_validator_base::operator();
713
722 virtual void operator()(std::filesystem::path const & file) const override
723 {
725 throw validation_error{"\"" + file.string() + "\" is a directory. Expected a file."};
726
727 try
728 {
729 if ((open_mode == output_file_open_options::create_new) && std::filesystem::exists(file))
730 throw validation_error{"The file \"" + file.string() + "\" already exists!"};
731
732 // Check if file has any write permissions.
733 validate_writeability(file);
734
735 validate_filename(file);
736 }
737 // LCOV_EXCL_START
739 {
740 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
741 }
742 // LCOV_EXCL_STOP
743 catch (...)
744 {
746 }
747 }
748
755 {
756 if (open_mode == output_file_open_options::open_or_create)
757 {
758 return "Write permissions must be granted."
759 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
760 + valid_extensions_help_page_message();
761 }
762 else // open_mode == create_new
763 {
764 return "The output file must not exist already and write permissions must be granted."
765 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
766 + valid_extensions_help_page_message();
767 }
768 }
769
770private:
772 output_file_open_options open_mode{output_file_open_options::create_new};
773};
774
794{
795public:
796 // Import from base class.
798
807 virtual ~input_directory_validator() = default;
808
809 // Import base constructor.
810 using file_validator_base::file_validator_base;
812
813 // Import the base::operator()
814 using file_validator_base::operator();
815
824 virtual void operator()(std::filesystem::path const & dir) const override
825 {
826 try
827 {
828 if (!std::filesystem::exists(dir))
829 throw validation_error{"The directory \"" + dir.string() + "\" does not exists!"};
830
832 throw validation_error{"The path \"" + dir.string() + "\" is not a directory!"};
833
834 // Check if directory has any read permissions.
835 validate_readability(dir);
836 }
837 // LCOV_EXCL_START
839 {
840 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
841 }
842 // LCOV_EXCL_STOP
843 catch (...)
844 {
846 }
847 }
848
855 {
856 return "An existing, readable path for the input directory.";
857 }
858};
859
879{
880public:
881 // Imported from base class.
883
892 virtual ~output_directory_validator() = default;
893
894 // Import base constructor.
895 using file_validator_base::file_validator_base;
897
898 // Import the base::operator().
899 using file_validator_base::operator();
900
909 virtual void operator()(std::filesystem::path const & dir) const override
910 {
911 bool dir_exists = std::filesystem::exists(dir);
912 // Make sure the created dir is deleted after we are done.
914 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
915 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
916 if (static_cast<bool>(ec))
917 throw validation_error{"Cannot create directory: \"" + dir.string() + "\"!"};
918
919 try
920 {
921 if (!dir_exists)
922 {
924 validate_writeability(dir / "dummy.txt");
925 dir_guard.remove_all();
926 }
927 else
928 {
929 validate_writeability(dir / "dummy.txt");
930 }
931 }
932 // LCOV_EXCL_START
934 {
935 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
936 }
937 // LCOV_EXCL_STOP
938 catch (...)
939 {
941 }
942 }
943
950 {
951 return "A valid path for the output directory.";
952 }
953};
954
977{
978public:
981
988 regex_validator(std::string const & pattern_) : pattern{pattern_}
989 {}
990
998 void operator()(option_value_type const & cmp) const
999 {
1000 std::regex rgx(pattern);
1001 if (!std::regex_match(cmp, rgx))
1002 throw validation_error{"Value " + cmp + " did not match the pattern " + pattern + "."};
1003 }
1004
1014 template <std::ranges::forward_range range_type>
1016 void operator()(range_type const & v) const
1017 {
1018 for (auto && entry : v)
1019 {
1020 // note: we explicitly copy/construct any reference type other than `std::string &`
1021 (*this)(static_cast<std::string const &>(entry));
1022 }
1023 }
1024
1031 {
1032 return "Value must match the pattern '" + pattern + "'.";
1033 }
1034
1035private:
1038};
1039
1040namespace detail
1041{
1042
1055{
1058
1060 template <typename option_value_t>
1061 void operator()(option_value_t const & /*cmp*/) const noexcept
1062 {}
1063
1066 {
1067 return "";
1068 }
1069};
1070
1084template <validator validator1_type, validator validator2_type>
1087{
1088public:
1092
1101
1106 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1107 vali1{std::move(vali1_)},
1108 vali2{std::move(vali2_)}
1109 {}
1110
1114
1123 template <typename cmp_type>
1125 void operator()(cmp_type const & cmp) const
1126 {
1127 vali1(cmp);
1128 vali2(cmp);
1129 }
1130
1133 {
1134 return vali1.get_help_page_message() + " " + vali2.get_help_page_message();
1135 }
1136
1137private:
1139 validator1_type vali1;
1141 validator2_type vali2;
1142};
1143
1144} // namespace detail
1145
1177template <validator validator1_type, validator validator2_type>
1180auto operator|(validator1_type && vali1, validator2_type && vali2)
1181{
1183}
1184
1185} // 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
std::string valid_range_str
The range as string.
Definition validators.hpp:149
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
option_value_type max
Maximum of the range to test.
Definition validators.hpp:146
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition validators.hpp:121
option_value_type min
Minimum of the range to test.
Definition validators.hpp:143
A safe guard to manage a filesystem entry, e.g. a file or a directory.
Definition safe_filesystem_entry.hpp:35
A helper struct to chain validators recursively via the pipe operator.
Definition validators.hpp:1087
validator1_type vali1
The first validator in the chain.
Definition validators.hpp:1139
validator_chain_adaptor(validator_chain_adaptor const &pf)=default
Defaulted.
validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_)
Constructing from two validators.
Definition validators.hpp:1106
validator2_type vali2
The second validator in the chain.
Definition validators.hpp:1141
validator_chain_adaptor & operator=(validator_chain_adaptor &&)=default
Defaulted.
validator_chain_adaptor & operator=(validator_chain_adaptor const &pf)=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:1132
void operator()(cmp_type const &cmp) const
Calls the operator() of each validator on the value cmp.
Definition validators.hpp:1125
~validator_chain_adaptor()=default
The destructor.
validator_chain_adaptor(validator_chain_adaptor &&)=default
Defaulted.
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:492
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:495
virtual ~file_validator_base()=default
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition validators.hpp:463
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:476
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:440
A validator that checks if a given path is a valid input directory.
Definition validators.hpp:794
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:854
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:824
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:522
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:593
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:543
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 & operator=(input_file_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid output directory.
Definition validators.hpp:879
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:909
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:949
A validator that checks if a given path is a valid output file.
Definition validators.hpp:641
output_file_validator()=default
Defaulted.
output_file_validator(auto &&... extensions)
Constructs from a parameter pack of valid extensions.
Definition validators.hpp:702
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:754
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition validators.hpp:722
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:691
output_file_validator(output_file_open_options const mode, std::vector< std::string > extensions)
Constructs from a given overwrite mode and a list of valid extensions.
Definition validators.hpp:664
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:680
output_file_open_options open_mode
Stores the current mode of whether it is valid to overwrite the output file.
Definition validators.hpp:772
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:977
void operator()(range_type const &v) const
Tests whether every entry in list v matches the pattern.
Definition validators.hpp:1016
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:1030
std::string pattern
The pattern to match.
Definition validators.hpp:1037
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition validators.hpp:998
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition validators.hpp:988
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
std::vector< option_value_type > values
Minimum of the range to test.
Definition validators.hpp:267
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)
std::string to_string(value_types &&... values)
Streams all parameters via std::ostringstream and returns a concatenated string.
Definition to_string.hpp:40
auto operator|(validator1_type &&vali1, validator2_type &&vali2)
Enables the chaining of validators.
Definition validators.hpp:1180
T has_extension(T... args)
T is_directory(T... args)
T is_regular_file(T... args)
T is_same_v
T regex_match(T... args)
T rethrow_exception(T... args)
Provides sharg::detail::safe_filesystem_entry.
T size(T... args)
Validator that always returns true.
Definition validators.hpp:1055
void operator()(option_value_t const &) const noexcept
Value cmp always passes validation for any type and never throws.
Definition validators.hpp:1061
std::string get_help_page_message() const
Since no validation is happening the help message is empty.
Definition validators.hpp:1065
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:606
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.
Hide me