Skip to content

[FEATURE] Allow overwrite existing files, add flags for permission handling #2009

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Note that 3.1.0 will be the first API stable release and interfaces in this rele
* `seqan3::argument_parser::add_line`
* `seqan3::argument_parser::add_list_item`
Note that other `seqan3::argument_parser::option_spec`s like `REQUIRED` are ignored.
* We expanded the `seqan3::output_file_validator`, with a parameter `seqan3::output_file_open_options` to allow overwriting
output files ([\#2009](https://github.com/seqan/seqan3/pull/2009)).

#### I/O

Expand Down
4 changes: 2 additions & 2 deletions doc/tutorial/argument_parser/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ of the parsed option value.
The validator throws a seqan3::validation_error exception whenever a given filename's extension is not in the
given list of valid extensions. In addition, the seqan3::input_file_validator checks if the file exists, is a regular
file and is readable.
The seqan3::output_file_validator on the other hand ensures that the output does not already exist (in order to prevent
overwriting an already existing file) and that it can be created.
Moreover, you have to add an additional flag seqan3::output_file_open_options to the seqan3::output_file_validator,
which you can use to indicate whether you want to allow the output files to be overwritten.

\note If you want to allow any extension just use a default constructed file validator.

Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_indexer_step1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"fa","fasta"}});
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"index"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
}

//![main]
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_indexer_step2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"fa","fasta"}});
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"index"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
}

int main(int argc, char const ** argv)
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_indexer_step3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"fa","fasta"}});
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"index"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
}

int main(int argc, char const ** argv)
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_step1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"index"}});
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"sam"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
seqan3::option_spec::DEFAULT,
seqan3::arithmetic_range_validator{0, 4});
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_step2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"index"}});
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"sam"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
seqan3::option_spec::DEFAULT,
seqan3::arithmetic_range_validator{0, 4});
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_step3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"index"}});
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"sam"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
seqan3::option_spec::DEFAULT,
seqan3::arithmetic_range_validator{0, 4});
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/read_mapper/read_mapper_step4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
seqan3::input_file_validator{{"index"}});
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{{"sam"}});
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
seqan3::option_spec::DEFAULT,
seqan3::arithmetic_range_validator{0, 4});
Expand Down
101 changes: 67 additions & 34 deletions include/seqan3/argument_parser/validators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

#pragma once

#include <regex>
#include <seqan3/std/algorithm>
#include <seqan3/std/concepts>
#include <seqan3/std/filesystem>
#include <fstream>
#include <seqan3/std/ranges>
#include <regex>
#include <sstream>

#include <seqan3/argument_parser/exceptions.hpp>
Expand All @@ -26,10 +30,6 @@
#include <seqan3/io/detail/safe_filesystem_entry.hpp>
#include <seqan3/range/container/concept.hpp>
#include <seqan3/range/views/join.hpp>
#include <seqan3/std/algorithm>
#include <seqan3/std/concepts>
#include <seqan3/std/filesystem>
#include <seqan3/std/ranges>

namespace seqan3
{
Expand Down Expand Up @@ -384,9 +384,8 @@ class file_validator_base

// Check if extension is available.
if (!path.has_extension())
throw validation_error{detail::to_string("The given filename ", path.string(),
" has no extension. Expected one of the following valid"
" extensions:", extensions, "!")};
throw validation_error{detail::to_string("The given filename ", path.string(), " has no extension. Expected"
" one of the following valid extensions:", extensions, "!")};

// Drop the dot.
std::string drop_less_ext = path.extension().string().substr(1);
Expand All @@ -403,8 +402,8 @@ class file_validator_base
// Check if requested extension is present.
if (std::ranges::find_if(extensions, case_insensitive_equal_to) == extensions.end())
{
throw validation_error{detail::to_string("Expected one of the following valid extensions: ",
extensions, "! Got ", drop_less_ext, " instead!")};
throw validation_error{detail::to_string("Expected one of the following valid extensions: ", extensions,
"! Got ", drop_less_ext, " instead!")};
}
}

Expand Down Expand Up @@ -587,6 +586,15 @@ class input_file_validator : public file_validator_base
}
};

//!\brief Mode of an output file: Determines whether an existing file can be (silently) overwritten.
enum class output_file_open_options
{
//!\brief Allow to overwrite the output file
open_or_create,
//!\brief Forbid overwriting the output file
create_new
};

/*!\brief A validator that checks if a given path is a valid output file.
* \ingroup argument_parser
* \implements seqan3::validator
Expand All @@ -595,9 +603,13 @@ class input_file_validator : public file_validator_base
* \details
*
* On construction, the validator can receive a list (std::vector over std::string) of valid file extensions.
* The class acts as a functor that throws a seqan3::validation_error exception whenever a given filename's
* extension (sts::string) is not in the given list of valid file extensions, if the file already exist, or if the
* parent path does not have the proper writer permissions.
* The class acts as a functor that throws a seqan3::validation_error exception whenever a given filename's extension
* (std::string) is not in the given list of valid file extensions, or if the parent path does not have the proper
* writer permissions.
* In addition, the validator receives a seqan3::output_file_open_options which allows you to specify what to do if your
* output file already exists. seqan3::output_file_open_options::create_new will throw a seqan3::validation_error
* exception if it already exists and seqan3::output_file_open_options::open_or_create will skip this check (that means
* you are allowed to overwrite the existing file).
*
* \include test/snippet/argument_parser/validators_output_file.cpp
*
Expand All @@ -613,7 +625,6 @@ template <typename file_t = void>
class output_file_validator : public file_validator_base
{
public:

static_assert(std::same_as<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.");

Expand All @@ -625,24 +636,24 @@ class output_file_validator : public file_validator_base
*/

//!\copydoc seqan3::input_file_validator::input_file_validator()
output_file_validator()
{
if constexpr (!std::same_as<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.
output_file_validator() : output_file_validator{output_file_open_options::create_new}
{}

//!\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_as<file_t, void>
//!\endcond
: file_validator_base{}
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.

/*!\brief Constructs from a given overwrite mode and a list of valid extensions.
* \param[in] mode A seqan3::output_file_open_options indicating whether the validator throws if a file already
exists.
* \param[in] extensions The valid extensions to validate for. Defaults to
* seqan3::output_file_validator::default_extensions.
*/
explicit output_file_validator(output_file_open_options const mode,
std::vector<std::string> extensions = default_extensions())
: file_validator_base{}, mode{mode}
{
file_validator_base::extensions = std::move(extensions);
}
Expand All @@ -651,6 +662,21 @@ class output_file_validator : public file_validator_base
using file_validator_base::file_validator_base;
//!\}

/*!\brief The default extensions of `file_t`.
* \returns A list of default extensions for `file_t`, will be empty if `file_t` is `void`.
*
* \details
*
* If `file_t` does name a valid seqan3 file type that contains a static member `valid_formats` returns the
* extensions of that `file_t` type. Otherwise returns an empty list.
*/
static std::vector<std::string> default_extensions()
{
if constexpr (!std::same_as<file_t, void>)
return detail::valid_file_extensions<typename file_t::valid_formats>();
return {};
}

// Import the base::operator()
using file_validator_base::operator();

Expand All @@ -663,7 +689,7 @@ class output_file_validator : public file_validator_base
{
try
{
if (std::filesystem::exists(file))
if ((mode == output_file_open_options::create_new) && std::filesystem::exists(file))
throw validation_error{detail::to_string("The file ", file, " already exists!")};

// Check if file has any write permissions.
Expand All @@ -684,9 +710,16 @@ 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 "The output file must not exist already and write permissions must be granted." +
valid_extensions_help_page_message();
if (mode == output_file_open_options::open_or_create)
return "Write permissions must be granted." + valid_extensions_help_page_message();
else // mode == create_new
return "The output file must not exist already and write permissions must be granted." +
valid_extensions_help_page_message();
}

private:
//!\brief Stores the current mode of whether it is valid to overwrite the output file.
output_file_open_options mode{output_file_open_options::create_new};
};

/*!\brief A validator that checks if a given path is a valid input directory.
Expand Down
3 changes: 2 additions & 1 deletion test/snippet/argument_parser/validators_output_directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ int main(int argc, const char ** argv)
std::filesystem::path mydir{};

myparser.add_option(mydir, 'd', "dir", "The output directory for storing the files.",
seqan3::option_spec::DEFAULT, seqan3::output_directory_validator{});
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{seqan3::output_file_open_options::create_new});
//! [validator_call]

// an exception will be thrown if the user specifies a directory that cannot be created by the filesystem either
Expand Down
11 changes: 9 additions & 2 deletions test/snippet/argument_parser/validators_output_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ int main(int argc, const char ** argv)
//! [validator_call]
std::filesystem::path myfile{};

myparser.add_option(myfile,'f',"file","Output file containing the processed sequences.",
seqan3::option_spec::DEFAULT, seqan3::output_file_validator{{"fa","fasta"}});
// Use the seqan3::output_file_open_options to indicate that you allow overwriting existing output files, ...
myparser.add_option(myfile, 'f', "file", "Output file containing the processed sequences.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{seqan3::output_file_open_options::open_or_create, {"fa","fasta"}});

// ... or that you will throw a seqan3::validation_error if the user specified output file already exists
myparser.add_option(myfile, 'g', "file2", "Output file containing the processed sequences.",
seqan3::option_spec::DEFAULT,
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"fa","fasta"}});
//! [validator_call]

// an exception will be thrown if the user specifies a filename
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
int main(int argc, const char ** argv)
{
// Default constructed validator has an empty extension list.
seqan3::output_file_validator validator1{};
seqan3::output_file_validator validator1{seqan3::output_file_open_options::create_new};
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::output_file_validator validator2{seqan3::output_file_open_options::create_new,
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::output_file_validator<seqan3::sequence_file_output<>> validator3
{
seqan3::output_file_open_options::create_new
};
seqan3::debug_stream << validator3.get_help_page_message() << "\n";

return 0;
Expand Down
Loading