Sharg 1.1.0
The argument parser for bio-c++ tools.
 
Loading...
Searching...
No Matches
validators.hpp
Go to the documentation of this file.
1// --------------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2023, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2023, 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 && (!std::same_as<std::remove_cvref_t<range_type>, std::filesystem::path>)
246 void operator()(range_type const & range) const
247 {
248 std::for_each(std::ranges::begin(range),
249 std::ranges::end(range),
250 [&](auto cmp)
251 {
252 (*this)(cmp);
253 });
254 }
255
262 {
263 return detail::to_string("Value must be one of ", values, ".");
264 }
265
266private:
269};
270
276template <typename option_type, typename... option_types>
277 requires (std::constructible_from<std::string, std::decay_t<option_types>> && ...
278 && std::constructible_from<std::string, std::decay_t<option_type>>)
280
282template <typename range_type>
283 requires (std::ranges::forward_range<std::decay_t<range_type>>
284 && std::constructible_from<std::string, std::ranges::range_value_t<range_type>>)
286
288template <typename option_type, typename... option_types>
290
292template <typename range_type>
293 requires (std::ranges::forward_range<std::decay_t<range_type>>)
296
313{
314public:
317
326 virtual ~file_validator_base() = default;
328
338 virtual void operator()(std::filesystem::path const & path) const = 0;
339
350 template <std::ranges::forward_range range_type>
351 requires (std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
352 && !std::convertible_to<range_type, std::filesystem::path const &>)
353 void operator()(range_type const & v) const
354 {
355 std::for_each(v.begin(),
356 v.end(),
357 [&](auto cmp)
358 {
359 this->operator()(cmp);
360 });
361 }
362
363protected:
370 {
371 // If no valid extensions are given we can safely return here.
372 if (extensions.empty())
373 return;
374
375 // Check if extension is available.
376 if (!path.has_extension())
377 {
378 throw validation_error{"The given filename " + path.string()
379 + " has no extension. Expected one of the "
380 "following valid extensions:"
381 + extensions_str + "!"};
382 }
383
384 std::string file_path{path.filename().string()};
385
386 // Leading dot indicates a hidden file is not part of the extension.
387 if (file_path.front() == '.')
388 file_path.erase(0, 1);
389
390 // Store a string_view containing all extensions for a better error message.
391 std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
392
393 // Compares the extensions in lower case.
394 auto case_insensitive_ends_with = [&](std::string const & ext)
395 {
396 return case_insensitive_string_ends_with(file_path, ext);
397 };
398
399 // Check if requested extension is present.
400 if (std::find_if(extensions.begin(), extensions.end(), case_insensitive_ends_with) == extensions.end())
401 {
402 throw validation_error{"Expected one of the following valid extensions: " + extensions_str + "! Got "
403 + all_extensions + " instead!"};
404 }
405 }
406
413 {
414 // Check if input directory is readable.
416 {
417 std::error_code ec{};
418 std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
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};
450 sharg::detail::safe_filesystem_entry file_guard{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
544 {
547 }
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 {
585 std::rethrow_exception(std::current_exception());
586 }
587 }
588
594 {
595 return "The input file must exist and read permissions must be granted."
598 }
599};
600
606{
611};
612
641{
642public:
643 // Import from base class.
645
654 virtual ~output_file_validator() = default;
655
665 open_mode{mode}
666 {
669 }
670
681 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
682 : output_file_validator{mode, std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
683 {}
684
693 {}
694
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.
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.
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 {
745 std::rethrow_exception(std::current_exception());
746 }
747 }
748
755 {
756 if (open_mode == output_file_open_options::open_or_create)
757 {
758 return "Write permissions must be granted."
761 }
762 else // open_mode == create_new
763 {
764 return "The output file must not exist already and write permissions must be granted."
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.
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.
836 }
837 // LCOV_EXCL_START
839 {
840 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
841 }
842 // LCOV_EXCL_STOP
843 catch (...)
844 {
845 std::rethrow_exception(std::current_exception());
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.
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 {
923 sharg::detail::safe_filesystem_entry dir_guard{dir};
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 {
940 std::rethrow_exception(std::current_exception());
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>
1015 requires std::convertible_to<std::ranges::range_reference_t<range_type>, std::string const &>
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:
1037 std::string pattern;
1038};
1039
1040namespace detail
1041{
1042
1054struct default_validator
1055{
1057 using option_value_type = std::any;
1058
1060 template <typename option_value_t>
1061 void operator()(option_value_t const & /*cmp*/) const noexcept
1062 {}
1063
1065 std::string get_help_page_message() const
1066 {
1067 return "";
1068 }
1069};
1070
1084template <validator validator1_type, validator validator2_type>
1085 requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
1086class validator_chain_adaptor
1087{
1088public:
1090 using option_value_type =
1092
1096 validator_chain_adaptor() = delete;
1097 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1098 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1099 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1100 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1101
1106 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1107 vali1{std::move(vali1_)},
1108 vali2{std::move(vali2_)}
1109 {}
1110
1112 ~validator_chain_adaptor() = default;
1114
1123 template <typename cmp_type>
1124 requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1125 void operator()(cmp_type const & cmp) const
1126 {
1127 vali1(cmp);
1128 vali2(cmp);
1129 }
1130
1132 std::string get_help_page_message() const
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>
1178 requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1180auto operator|(validator1_type && vali1, validator2_type && vali2)
1181{
1182 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1), std::forward<validator2_type>(vali2)};
1183}
1184
1185} // 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:313
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:412
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition: validators.hpp:369
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:316
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, auto &&... extensions)
Constructs from a given overwrite mode and a parameter pack of valid extensions.
Definition: validators.hpp:680
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:664
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
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:183
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:176
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:261
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:1180
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:606
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.