Skip to content

Commit

Permalink
[FEATURE] Adapt input/output file validator to read extensions from a…
Browse files Browse the repository at this point in the history
… file.
  • Loading branch information
rrahn committed Aug 15, 2019
1 parent a706277 commit 7e97653
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 25 deletions.
106 changes: 89 additions & 17 deletions include/seqan3/argument_parser/validators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <seqan3/core/detail/to_string.hpp>
#include <seqan3/core/type_traits/basic.hpp>
#include <seqan3/core/type_traits/pre.hpp>
#include <seqan3/io/detail/misc.hpp>
#include <seqan3/io/detail/safe_filesystem_entry.hpp>
#include <seqan3/range/container/concept.hpp>
#include <seqan3/range/view/drop.hpp>
Expand Down Expand Up @@ -236,11 +237,15 @@ value_list_validator(std::initializer_list<const char *>) -> value_list_validato

/*!\brief An abstract base class for the file and directory validators.
* \ingroup argument_parser
* \tparam file_t The type of the file to get the valid extensions for; `void` on default.
*
* \details
*
* This class provides a common interface for seqan3::input_file_validator and the seqan3::output_file_validator as
* well as the seqan3::input_directory_validator and seqan3::output_directory_validator.
*
* The type can be further specialised for the seqan3::input_file_validator and the seqan3::output_file_validator
* using the template argument to determine the valid extensions from the given file type.
*/
class file_validator_base
{
Expand All @@ -258,12 +263,6 @@ class file_validator_base
file_validator_base & operator=(file_validator_base const &) = default; //!< Defaulted.
file_validator_base & operator=(file_validator_base &&) = default; //!< Defaulted.
virtual ~file_validator_base() = default; //!< Virtual destructor.

/*!\brief Constructs from a set of valid extensions.
* \param[in] extensions The valid extensions to validate for.
*/
explicit file_validator_base(std::vector<std::string> extensions) : extensions{std::move(extensions)}
{}
//!\}

/*!\brief Tests if the given path is a valid input, respectively output, file or directory.
Expand Down Expand Up @@ -384,6 +383,7 @@ class file_validator_base
/*!\brief A validator that checks if a given path is a valid input file.
* \ingroup argument_parser
* \implements seqan3::Validator
* \tparam file_t The type of the file to get the valid extensions for; `void` on default.
*
* \details
*
Expand All @@ -394,24 +394,66 @@ class file_validator_base
*
* \include test/snippet/argument_parser/validators_input_file.cpp
*
* The valid extensions can also be obtained from a seqan3 formatted file type, e.g. seqan3::sequence_input_file, if
* it is given as template argument to this class. The following snippet demonstrates the different ways to instantiate
* the seqan3::output_file_validator.
*
* \include test/snippet/argument_parser/validators_input_file_ext_from_file.cpp
*
* \note The validator works on every type that can be implicitly converted to std::filesystem::path.
*/
template <typename file_t = void>
class input_file_validator : public file_validator_base
{
public:

static_assert(std::Same<file_t, void> || detail::has_type_valid_formats<file_t>,
"Expected either a template type with a static member called valid_formats (a file type) or void.");

// Import from base class.
using file_validator_base::value_type;
using typename file_validator_base::value_type;

/*!\name Constructors, destructor and assignment
* \{
*/
input_file_validator() = default; //!< Defaulted.

/*!\brief Default constructor.
*
* \details
*
* If the class' template argument `file_t` names a valid seqan3 file type that contains a
* static member `valid_formats`, e.g. seqan3::sequence_input_file::valid_formats, then it generates the
* list of valid extensions from this file. Otherwise the extensions list is empty.
*/
input_file_validator()
{
if constexpr (!std::Same<file_t, void>)
file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
}

input_file_validator(input_file_validator const &) = default; //!< Defaulted.
input_file_validator(input_file_validator &&) = default; //!< Defaulted.
input_file_validator & operator=(input_file_validator const &) = default; //!< Defaulted.
input_file_validator & operator=(input_file_validator &&) = default; //!< Defaulted.
virtual ~input_file_validator() = default; //!< Virtual destructor.

/*!\brief Constructs from a given collection of valid extensions.
* \param[in] extensions The valid extensions to validate for.
*
* \details
*
* This constructor is only available if `file_t` does not name a valid seqan3 file type that contains a
* static member `valid_formats`.
*/
explicit input_file_validator(std::vector<std::string> extensions)
//!\cond
requires std::Same<file_t, void>
//!\endcond
: file_validator_base{}
{
file_validator_base::extensions = std::move(extensions);
}

// Import base class constructor.
using file_validator_base::file_validator_base;
//!\}
Expand Down Expand Up @@ -450,15 +492,16 @@ class input_file_validator : public file_validator_base
//!\brief Returns a message that can be appended to the (positional) options help page info.
std::string get_help_page_message() const
{
return detail::to_string("Valid input file formats: ",
file_validator_base::extensions | std::view::join(std::string{", "}),
".");
return detail::to_string("Valid input file formats: [",
extensions | std::view::join(std::string{", "}),
"]");
}
};

/*!\brief A validator that checks if a given path is a valid output file.
* \ingroup argument_parser
* \implements seqan3::Validator
* \tparam file_t The type of the file to get the valid extensions for; `void` on default.
*
* \details
*
Expand All @@ -469,24 +512,52 @@ class input_file_validator : public file_validator_base
*
* \include test/snippet/argument_parser/validators_output_file.cpp
*
* The valid extensions can also be obtained from a seqan3 formatted file type, e.g. seqan3::sequence_input_file, if
* it is given as template argument to this class. The following snippet demonstrates the different ways to instantiate
* the seqan3::output_file_validator.
*
* \include test/snippet/argument_parser/validators_output_file_ext_from_file.cpp
*
* \note The validator works on every type that can be implicitly converted to std::filesystem::path.
*/
template <typename file_t = void>
class output_file_validator : public file_validator_base
{
public:

static_assert(std::Same<file_t, void> || detail::has_type_valid_formats<file_t>,
"Expected either a template type with a static member called valid_formats (a file type) or void.");

// Import from base class.
using file_validator_base::value_type;
using typename file_validator_base::value_type;

/*!\name Constructors, destructor and assignment
* \{
*/
output_file_validator() = default; //!< Defaulted.

//!\copydoc seqan3::input_file_validator::input_file_validator()
output_file_validator()
{
if constexpr (!std::Same<file_t, void>)
file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
}

output_file_validator(output_file_validator const &) = default; //!< Defaulted.
output_file_validator(output_file_validator &&) = default; //!< Defaulted.
output_file_validator & operator=(output_file_validator const &) = default; //!< Defaulted.
output_file_validator & operator=(output_file_validator &&) = default; //!< Defaulted.
virtual ~output_file_validator() = default; //!< Virtual Destructor.

//!\copydoc seqan3::input_file_validator::input_file_validator(std::vector<std::string>)
explicit output_file_validator(std::vector<std::string> extensions)
//!\cond
requires std::Same<file_t, void>
//!\endcond
: file_validator_base{}
{
file_validator_base::extensions = std::move(extensions);
}

// Import base constructor.
using file_validator_base::file_validator_base;
//!\}
Expand Down Expand Up @@ -524,8 +595,9 @@ class output_file_validator : public file_validator_base
//!\brief Returns a message that can be appended to the (positional) options help page info.
std::string get_help_page_message() const
{
return detail::to_string("Valid output file formats: ",
file_validator_base::extensions | std::view::join(std::string{", "}), ".");
return detail::to_string("Valid output file formats: [",
extensions | std::view::join(std::string{", "}),
"]");
}
};

Expand All @@ -547,7 +619,7 @@ class input_directory_validator : public file_validator_base
{
public:
// Import from base class.
using file_validator_base::value_type;
using typename file_validator_base::value_type;

/*!\name Constructors, destructor and assignment
* \{
Expand Down Expand Up @@ -619,7 +691,7 @@ class output_directory_validator : public file_validator_base
{
public:
// Imported from base class.
using file_validator_base::value_type;
using typename file_validator_base::value_type;

/*!\name Constructors, destructor and assignment
* \{
Expand Down
1 change: 0 additions & 1 deletion include/seqan3/io/detail/magic_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#include <array>
#include <tuple>

#include <seqan3/core/type_traits/basic.hpp>
#include <seqan3/core/type_traits/template_inspection.hpp>
#include <seqan3/core/platform.hpp>
#include <seqan3/core/type_list/type_list.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <seqan3/argument_parser/validators.hpp>
#include <seqan3/io/sequence_file/input.hpp>
#include <seqan3/core/debug_stream.hpp>

int main(int argc, const char ** argv)
{
// Default constructed validator has an empty extension list.
seqan3::input_file_validator validator1{};
seqan3::debug_stream << validator1.get_help_page_message() << "\n";

// Specify your own extensions for the input file.
seqan3::input_file_validator validator2{std::vector{std::string{"exe"}, std::string{"fasta"}}};
seqan3::debug_stream << validator2.get_help_page_message() << "\n";

// Give the seqan3 file type as a template argument to get all valid extensions for this file.
seqan3::input_file_validator<seqan3::sequence_file_input<>> validator3{};
seqan3::debug_stream << validator3.get_help_page_message() << "\n";

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <seqan3/argument_parser/validators.hpp>
#include <seqan3/io/sequence_file/output.hpp>
#include <seqan3/core/debug_stream.hpp>

int main(int argc, const char ** argv)
{
// Default constructed validator has an empty extension list.
seqan3::output_file_validator validator1{};
seqan3::debug_stream << validator1.get_help_page_message() << "\n";

// Specify your own extensions for the output file.
seqan3::output_file_validator validator2{std::vector{std::string{"exe"}, std::string{"fasta"}}};
seqan3::debug_stream << validator2.get_help_page_message() << "\n";

// Give the seqan3 file type as a template argument to get all valid extensions for this file.
seqan3::output_file_validator<seqan3::sequence_file_output<>> validator3{};
seqan3::debug_stream << validator3.get_help_page_message() << "\n";

return 0;
}
54 changes: 48 additions & 6 deletions test/unit/argument_parser/format_parse_validators_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@

using namespace seqan3;

struct dummy_file
{

struct format1
{
static inline std::vector<std::string> file_extensions{ {"fa"}, {"fasta"}};
};

struct format2
{
static inline std::vector<std::string> file_extensions{ {"sam"}, {"bam"}};
};

using valid_formats = type_list<format1, format2>;
};

std::string const basic_options_str = "OPTIONS"
"Basic options:"
"-h, --help Prints the help page."
Expand All @@ -47,8 +63,8 @@ TEST(validator_test, fullfill_concept)
EXPECT_TRUE(Validator<arithmetic_range_validator>);
EXPECT_TRUE(Validator<value_list_validator<double>>);
EXPECT_TRUE(Validator<value_list_validator<std::string>>);
EXPECT_TRUE(Validator<input_file_validator>);
EXPECT_TRUE(Validator<output_file_validator>);
EXPECT_TRUE(Validator<input_file_validator<>>);
EXPECT_TRUE(Validator<output_file_validator<>>);
EXPECT_TRUE(Validator<input_directory_validator>);
EXPECT_TRUE(Validator<output_directory_validator>);
EXPECT_TRUE(Validator<regex_validator>);
Expand Down Expand Up @@ -92,6 +108,11 @@ TEST(validator_test, input_file)
EXPECT_THROW(my_validator(does_not_exist), parser_invalid_argument);
}

{ // read from file
input_file_validator<dummy_file> my_validator{};
EXPECT_NO_THROW(my_validator(tmp_name.get_path()));
}

std::filesystem::path file_in_path;

// option
Expand Down Expand Up @@ -135,13 +156,21 @@ TEST(validator_test, input_file)
"==========="
"POSITIONAL ARGUMENTS"
" ARGUMENT-1 (std::filesystem::path)"
" desc Valid input file formats: fa, sam, fasta."} +
" desc Valid input file formats: [fa, sam, fasta]"} +
basic_options_str +
basic_version_str;
EXPECT_TRUE(ranges::equal((my_stdout | std::view::filter(!is_space)), expected | std::view::filter(!is_space)));
}
}

TEST(validator_test, input_file_ext_from_file)
{
// Give as a template argument the seqan3 file type to get all valid extensions for this file.
input_file_validator<dummy_file> validator{};

EXPECT_EQ(validator.get_help_page_message(), "Valid input file formats: [fa, fasta, sam, bam]");
}

TEST(validator_test, output_file)
{
test::tmp_filename tmp_name{"testbox.fasta"};
Expand All @@ -166,7 +195,7 @@ TEST(validator_test, output_file)

{ // file has wrong format.
output_file_validator my_validator{std::vector{std::string{"sam"}}};
EXPECT_THROW(my_validator(tmp_name.get_path()), parser_invalid_argument);
EXPECT_THROW(my_validator(tmp_name.get_path()), parser_invalid_argument);
}

{ // file has no extension.
Expand All @@ -176,6 +205,11 @@ TEST(validator_test, output_file)
EXPECT_THROW(my_validator(no_extension), parser_invalid_argument);
}

{ // read from file
output_file_validator<dummy_file> my_validator{};
EXPECT_NO_THROW(my_validator(tmp_name.get_path()));
}

std::filesystem::path file_out_path;

// option
Expand Down Expand Up @@ -220,13 +254,21 @@ TEST(validator_test, output_file)
"==========="
"POSITIONAL ARGUMENTS"
" ARGUMENT-1 (std::filesystem::path)"
" desc Valid output file formats: fa, sam, fasta."} +
" desc Valid output file formats: [fa, sam, fasta]"} +
basic_options_str +
basic_version_str;
EXPECT_TRUE(ranges::equal((my_stdout | std::view::filter(!is_space)), expected | std::view::filter(!is_space)));
}
}

TEST(validator_test, output_file_ext_from_file)
{
// Give as a template argument the seqan3 file type to get all valid extensions for this file.
output_file_validator<dummy_file> validator{};

EXPECT_EQ(validator.get_help_page_message(), "Valid output file formats: [fa, fasta, sam, bam]");
}

TEST(validator_test, input_directory)
{
test::tmp_filename tmp_name{"testbox.fasta"};
Expand Down Expand Up @@ -937,7 +979,7 @@ TEST(validator_test, chaining_validators)
basic_options_str +
" -s, --string-option (std::string)"
" desc Default:. Value must match the pattern '(/[^/]+)+/.*\\.[^/\\.]+$'. "
" Valid output file formats: sa, so."
" Valid output file formats: [sa, so]"
" Value must match the pattern '.*'."} +
basic_version_str;
EXPECT_TRUE(ranges::equal((my_stdout | ranges::view::remove_if(is_space)),
Expand Down
1 change: 0 additions & 1 deletion test/unit/io/detail/misc_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ TEST(misc, valid_file_extensions)

TEST(misc, valid_compression_extensions)
{

std::vector<std::string> valid_compression = detail::valid_file_extensions<detail::compression_formats>();

#if defined(SEQAN3_HAS_ZLIB)
Expand Down

0 comments on commit 7e97653

Please sign in to comment.