sharg 1.0.0
THE argument parser for bio-c++ tools.
validators.hpp
Go to the documentation of this file.
1// --------------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2022, 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/sharg-parser/blob/main/LICENSE.md
6// --------------------------------------------------------------------------------------------------------
7
13#pragma once
14
15#include <any>
16#include <concepts>
17#include <fstream>
18#include <ranges>
19#include <regex>
20
23#include <sharg/exceptions.hpp>
24
25namespace sharg
26{
27
48// clang-format off
49template <typename validator_type>
50concept validator = std::copyable<std::remove_cvref_t<validator_type>> &&
52 requires(validator_type validator,
54 {
55 {validator(value)} -> std::same_as<void>;
56 {validator.get_help_page_message()} -> std::same_as<std::string>;
57 };
58// clang-format on
59
77template <typename option_value_t>
78 requires std::is_arithmetic_v<option_value_t>
80{
81public:
83 using option_value_type = option_value_t;
84
93 min{min_},
94 max{max_},
95 valid_range_str{"[" + std::to_string(min_) + "," + std::to_string(max_) + "]"}
96 {}
97
105 void operator()(option_value_type const & cmp) const
106 {
107 if (!((cmp <= max) && (cmp >= min)))
108 throw validation_error{"Value " + std::to_string(cmp) + " is not in range " + valid_range_str + "."};
109 }
110
120 template <std::ranges::forward_range range_type>
121 requires std::is_arithmetic_v<std::ranges::range_value_t<range_type>>
122 void operator()(range_type const & range) const
123 {
124 std::for_each(range.begin(),
125 range.end(),
126 [&](auto cmp)
127 {
128 (*this)(cmp);
129 });
130 }
131
138 {
139 return std::string{"Value must be in range "} + valid_range_str + ".";
140 }
141
142private:
144 option_value_type min{};
145
147 option_value_type max{};
148
150 std::string valid_range_str{};
151};
152
174template <parsable option_value_t>
176{
177public:
179 using option_value_type = option_value_t;
180
190
199 template <std::ranges::forward_range range_type>
200 requires std::constructible_from<option_value_type, std::ranges::range_rvalue_reference_t<range_type>>
201 value_list_validator(range_type rng) // No &&, because rng will be moved.
202 {
203 std::move(rng.begin(), rng.end(), std::back_inserter(values));
204 }
205
214 template <typename... option_types>
215 requires ((std::constructible_from<option_value_type, option_types> && ...))
216 value_list_validator(option_types &&... opts)
217 {
218 (values.emplace_back(std::forward<option_types>(opts)), ...);
219 }
221
229 void operator()(option_value_type const & cmp) const
230 {
231 if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
232 throw validation_error{detail::to_string("Value ", cmp, " is not one of ", values, ".")};
233 }
234
243 template <std::ranges::forward_range range_type>
244 requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type>
245 void operator()(range_type const & range) const
246 {
247 std::for_each(std::ranges::begin(range),
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>
276 requires (std::constructible_from<std::string, std::decay_t<option_types>> && ...
277 && std::constructible_from<std::string, std::decay_t<option_type>>)
279
281template <typename range_type>
282 requires (std::ranges::forward_range<std::decay_t<range_type>>
283 && std::constructible_from<std::string, std::ranges::range_value_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>
350 requires (std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
351 && !std::convertible_to<range_type, std::filesystem::path const &>)
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
439 {
440 std::ofstream file{path};
441 sharg::detail::safe_filesystem_entry file_guard{path};
442
443 bool is_open = file.is_open();
444 bool is_good = file.good();
445 file.close();
446
447 if (!is_good || !is_open)
448 throw validation_error{"Cannot write \"" + path.string() + "\"!"};
449
450 file_guard.remove();
451 }
452
455 {
456 if (extensions.empty())
457 return "";
458 else
459 return "Valid file extensions are: " + extensions_str + ".";
460 }
461
468 {
469 size_t const suffix_length{suffix.size()};
470 size_t const str_length{str.size()};
471
472 if (suffix_length > str_length)
473 return false;
474
475 for (size_t j = 0, s_start = str_length - suffix_length; j < suffix_length; ++j)
476 if (std::tolower(str[s_start + j]) != std::tolower(suffix[j]))
477 return false;
478
479 return true;
480 }
481
484
487};
488
513{
514public:
515 // Import from base class.
517
526 virtual ~input_file_validator() = default;
527
535 {
538 }
539
540 // Import base class constructor.
543
544 // Import the base::operator()
545 using file_validator_base::operator();
546
555 virtual void operator()(std::filesystem::path const & file) const override
556 {
557 try
558 {
559 if (!std::filesystem::exists(file))
560 throw validation_error{"The file \"" + file.string() + "\" does not exist!"};
561
562 // Check if file is regular and can be opened for reading.
564
565 // Check extension.
566 validate_filename(file);
567 }
568 // LCOV_EXCL_START
570 {
571 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
572 }
573 // LCOV_EXCL_STOP
574 catch (...)
575 {
576 std::rethrow_exception(std::current_exception());
577 }
578 }
579
585 {
586 return "The input file must exist and read permissions must be granted."
589 }
590};
591
597{
602};
603
632{
633public:
634 // Import from base class.
636
645 virtual ~output_file_validator() = default;
646
656 open_mode{mode}
657 {
660 }
661
672 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
673 : output_file_validator{mode, std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
674 {}
675
684 {}
685
694 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
695 : output_file_validator{std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
696 {}
697
698 // Import base constructor.
701
702 // Import the base::operator()
703 using file_validator_base::operator();
704
713 virtual void operator()(std::filesystem::path const & file) const override
714 {
715 try
716 {
717 if ((open_mode == output_file_open_options::create_new) && std::filesystem::exists(file))
718 throw validation_error{"The file \"" + file.string() + "\" already exists!"};
719
720 // Check if file has any write permissions.
722
723 validate_filename(file);
724 }
725 // LCOV_EXCL_START
727 {
728 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
729 }
730 // LCOV_EXCL_STOP
731 catch (...)
732 {
733 std::rethrow_exception(std::current_exception());
734 }
735 }
736
743 {
744 if (open_mode == output_file_open_options::open_or_create)
745 {
746 return "Write permissions must be granted."
749 }
750 else // open_mode == create_new
751 {
752 return "The output file must not exist already and write permissions must be granted."
755 }
756 }
757
758private:
760 output_file_open_options open_mode{output_file_open_options::create_new};
761};
762
782{
783public:
784 // Import from base class.
786
795 virtual ~input_directory_validator() = default;
796
797 // Import base constructor.
800
801 // Import the base::operator()
802 using file_validator_base::operator();
803
812 virtual void operator()(std::filesystem::path const & dir) const override
813 {
814 try
815 {
816 if (!std::filesystem::exists(dir))
817 throw validation_error{"The directory \"" + dir.string() + "\" does not exists!"};
818
820 throw validation_error{"The path \"" + dir.string() + "\" is not a directory!"};
821
822 // Check if directory has any read permissions.
824 }
825 // LCOV_EXCL_START
827 {
828 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
829 }
830 // LCOV_EXCL_STOP
831 catch (...)
832 {
833 std::rethrow_exception(std::current_exception());
834 }
835 }
836
843 {
844 return "An existing, readable path for the input directory.";
845 }
846};
847
867{
868public:
869 // Imported from base class.
871
880 virtual ~output_directory_validator() = default;
881
882 // Import base constructor.
885
886 // Import the base::operator().
887 using file_validator_base::operator();
888
897 virtual void operator()(std::filesystem::path const & dir) const override
898 {
899 bool dir_exists = std::filesystem::exists(dir);
900 // Make sure the created dir is deleted after we are done.
902 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
903 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
904 if (static_cast<bool>(ec))
905 throw validation_error{"Cannot create directory: \"" + dir.string() + "\"!"};
906
907 try
908 {
909 if (!dir_exists)
910 {
911 sharg::detail::safe_filesystem_entry dir_guard{dir};
912 validate_writeability(dir / "dummy.txt");
913 dir_guard.remove_all();
914 }
915 else
916 {
917 validate_writeability(dir / "dummy.txt");
918 }
919 }
920 // LCOV_EXCL_START
922 {
923 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
924 }
925 // LCOV_EXCL_STOP
926 catch (...)
927 {
928 std::rethrow_exception(std::current_exception());
929 }
930 }
931
938 {
939 return "A valid path for the output directory.";
940 }
941};
942
965{
966public:
969
976 regex_validator(std::string const & pattern_) : pattern{pattern_}
977 {}
978
986 void operator()(option_value_type const & cmp) const
987 {
988 std::regex rgx(pattern);
989 if (!std::regex_match(cmp, rgx))
990 throw validation_error{"Value " + cmp + " did not match the pattern " + pattern + "."};
991 }
992
1002 template <std::ranges::forward_range range_type>
1003 requires std::convertible_to<std::ranges::range_reference_t<range_type>, std::string const &>
1004 void operator()(range_type const & v) const
1005 {
1006 for (auto && entry : v)
1007 {
1008 // note: we explicitly copy/construct any reference type other than `std::string &`
1009 (*this)(static_cast<std::string const &>(entry));
1010 }
1011 }
1012
1019 {
1020 return "Value must match the pattern '" + pattern + "'.";
1021 }
1022
1023private:
1025 std::string pattern;
1026};
1027
1028namespace detail
1029{
1030
1042struct default_validator
1043{
1045 using option_value_type = std::any;
1046
1048 template <typename option_value_t>
1049 void operator()(option_value_t const & /*cmp*/) const noexcept
1050 {}
1051
1053 std::string get_help_page_message() const
1054 {
1055 return "";
1056 }
1057};
1058
1072template <validator validator1_type, validator validator2_type>
1073 requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
1074class validator_chain_adaptor
1075{
1076public:
1078 using option_value_type =
1080
1084 validator_chain_adaptor() = delete;
1085 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1086 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1087 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1088 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1089
1094 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1095 vali1{std::move(vali1_)},
1096 vali2{std::move(vali2_)}
1097 {}
1098
1100 ~validator_chain_adaptor() = default;
1102
1111 template <typename cmp_type>
1112 requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1113 void operator()(cmp_type const & cmp) const
1114 {
1115 vali1(cmp);
1116 vali2(cmp);
1117 }
1118
1120 std::string get_help_page_message() const
1121 {
1122 return vali1.get_help_page_message() + " " + vali2.get_help_page_message();
1123 }
1124
1125private:
1127 validator1_type vali1;
1129 validator2_type vali2;
1130};
1131
1132} // namespace detail
1133
1165template <validator validator1_type, validator validator2_type>
1166 requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1168auto operator|(validator1_type && vali1, validator2_type && vali2)
1169{
1170 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1), std::forward<validator2_type>(vali2)};
1171}
1172
1173} // namespace sharg
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition: validators.hpp:80
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:137
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition: validators.hpp:105
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition: validators.hpp:83
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition: validators.hpp:92
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition: validators.hpp:122
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:483
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:486
virtual ~file_validator_base()=default
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition: validators.hpp:454
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:467
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:438
A validator that checks if a given path is a valid input directory.
Definition: validators.hpp:782
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:842
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:812
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:513
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:584
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:534
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is an existing regular file and is readable.
Definition: validators.hpp:555
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:867
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:897
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:937
A validator that checks if a given path is a valid output file.
Definition: validators.hpp:632
output_file_validator()=default
Defaulted.
output_file_validator(auto &&... extensions)
Constructs from a parameter pack of valid extensions.
Definition: validators.hpp:693
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:742
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition: validators.hpp:713
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:682
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:671
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:655
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:965
void operator()(range_type const &v) const
Tests whether every entry in list v matches the pattern.
Definition: validators.hpp:1004
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:1018
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:986
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition: validators.hpp:976
Parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:183
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:176
void operator()(range_type const &range) const
Tests whether every element in range lies inside values.
Definition: validators.hpp:245
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:179
value_list_validator()=default
Defaulted.
value_list_validator(range_type rng)
Constructing from a range.
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: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:229
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:216
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:50
T create_directory(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)
auto operator|(validator1_type &&vali1, validator2_type &&vali2)
Enables the chaining of validators.
Definition: validators.hpp:1168
T has_extension(T... args)
T is_directory(T... args)
T is_regular_file(T... args)
T move(T... args)
Provides sharg::detail::safe_filesystem_entry.
T size(T... args)
T substr(T... args)
Provides sharg::detail::to_string.
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.