Skip to content

Commit c94d251

Browse files
committed
[FEATURE] Use enum output_file_open_options to handle overwriting permission
[TEST] Update validator test [DOC] Update changelog, update (tutorial) documentation and code documentation Signed-off-by: Lydia Buntrock <[email protected]>
1 parent 205674c commit c94d251

14 files changed

+214
-79
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Note that 3.1.0 will be the first API stable release and interfaces in this rele
5454
* `seqan3::argument_parser::add_line`
5555
* `seqan3::argument_parser::add_list_item`
5656
Note that other `seqan3::argument_parser::option_spec`s like `REQUIRED` are ignored.
57+
* We expanded the `seqan3::output_file_validator`, with a parameter `seqan3::output_file_open_options` to allow overwriting
58+
output files ([\#2009](https://github.com/seqan/seqan3/pull/2009)).
5759

5860
#### I/O
5961

doc/tutorial/argument_parser/index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,8 @@ of the parsed option value.
390390
The validator throws a seqan3::validation_error exception whenever a given filename's extension is not in the
391391
given list of valid extensions. In addition, the seqan3::input_file_validator checks if the file exists, is a regular
392392
file and is readable.
393-
The seqan3::output_file_validator on the other hand ensures that the output does not already exist (in order to prevent
394-
overwriting an already existing file) and that it can be created.
393+
Moreover, you have to add an additional flag seqan3::output_file_open_options to the seqan3::output_file_validator,
394+
which you can use to indicate whether you want to allow the output files to be overwritten.
395395

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

doc/tutorial/read_mapper/read_mapper_indexer_step1.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
2424
seqan3::input_file_validator{{"fa","fasta"}});
2525
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
2626
seqan3::option_spec::DEFAULT,
27-
seqan3::output_file_validator{{"index"}});
27+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
2828
}
2929

3030
//![main]

doc/tutorial/read_mapper/read_mapper_indexer_step2.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
5151
seqan3::input_file_validator{{"fa","fasta"}});
5252
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
5353
seqan3::option_spec::DEFAULT,
54-
seqan3::output_file_validator{{"index"}});
54+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
5555
}
5656

5757
int main(int argc, char const ** argv)

doc/tutorial/read_mapper/read_mapper_indexer_step3.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
6666
seqan3::input_file_validator{{"fa","fasta"}});
6767
parser.add_option(args.index_path, 'o', "output", "The output index file path.",
6868
seqan3::option_spec::DEFAULT,
69-
seqan3::output_file_validator{{"index"}});
69+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"index"}});
7070
}
7171

7272
int main(int argc, char const ** argv)

doc/tutorial/read_mapper/read_mapper_step1.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
3939
seqan3::input_file_validator{{"index"}});
4040
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
4141
seqan3::option_spec::DEFAULT,
42-
seqan3::output_file_validator{{"sam"}});
42+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
4343
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
4444
seqan3::option_spec::DEFAULT,
4545
seqan3::arithmetic_range_validator{0, 4});

doc/tutorial/read_mapper/read_mapper_step2.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
103103
seqan3::input_file_validator{{"index"}});
104104
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
105105
seqan3::option_spec::DEFAULT,
106-
seqan3::output_file_validator{{"sam"}});
106+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
107107
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
108108
seqan3::option_spec::DEFAULT,
109109
seqan3::arithmetic_range_validator{0, 4});

doc/tutorial/read_mapper/read_mapper_step3.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
127127
seqan3::input_file_validator{{"index"}});
128128
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
129129
seqan3::option_spec::DEFAULT,
130-
seqan3::output_file_validator{{"sam"}});
130+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
131131
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
132132
seqan3::option_spec::DEFAULT,
133133
seqan3::arithmetic_range_validator{0, 4});

doc/tutorial/read_mapper/read_mapper_step4.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ void initialise_argument_parser(seqan3::argument_parser & parser, cmd_arguments
136136
seqan3::input_file_validator{{"index"}});
137137
parser.add_option(args.sam_path, 'o', "output", "The output SAM file path.",
138138
seqan3::option_spec::DEFAULT,
139-
seqan3::output_file_validator{{"sam"}});
139+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"sam"}});
140140
parser.add_option(args.errors, 'e', "error", "Maximum allowed errors.",
141141
seqan3::option_spec::DEFAULT,
142142
seqan3::arithmetic_range_validator{0, 4});

include/seqan3/argument_parser/validators.hpp

+67-34
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212

1313
#pragma once
1414

15-
#include <regex>
15+
#include <seqan3/std/algorithm>
16+
#include <seqan3/std/concepts>
17+
#include <seqan3/std/filesystem>
1618
#include <fstream>
19+
#include <seqan3/std/ranges>
20+
#include <regex>
1721
#include <sstream>
1822

1923
#include <seqan3/argument_parser/exceptions.hpp>
@@ -26,10 +30,6 @@
2630
#include <seqan3/io/detail/safe_filesystem_entry.hpp>
2731
#include <seqan3/range/container/concept.hpp>
2832
#include <seqan3/range/views/join.hpp>
29-
#include <seqan3/std/algorithm>
30-
#include <seqan3/std/concepts>
31-
#include <seqan3/std/filesystem>
32-
#include <seqan3/std/ranges>
3333

3434
namespace seqan3
3535
{
@@ -384,9 +384,8 @@ class file_validator_base
384384

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

391390
// Drop the dot.
392391
std::string drop_less_ext = path.extension().string().substr(1);
@@ -403,8 +402,8 @@ class file_validator_base
403402
// Check if requested extension is present.
404403
if (std::ranges::find_if(extensions, case_insensitive_equal_to) == extensions.end())
405404
{
406-
throw validation_error{detail::to_string("Expected one of the following valid extensions: ",
407-
extensions, "! Got ", drop_less_ext, " instead!")};
405+
throw validation_error{detail::to_string("Expected one of the following valid extensions: ", extensions,
406+
"! Got ", drop_less_ext, " instead!")};
408407
}
409408
}
410409

@@ -587,6 +586,15 @@ class input_file_validator : public file_validator_base
587586
}
588587
};
589588

589+
//!\brief Mode of an output file: Determines whether an existing file can be (silently) overwritten.
590+
enum class output_file_open_options
591+
{
592+
//!\brief Allow to overwrite the output file
593+
open_or_create,
594+
//!\brief Forbid overwriting the output file
595+
create_new
596+
};
597+
590598
/*!\brief A validator that checks if a given path is a valid output file.
591599
* \ingroup argument_parser
592600
* \implements seqan3::validator
@@ -595,9 +603,13 @@ class input_file_validator : public file_validator_base
595603
* \details
596604
*
597605
* On construction, the validator can receive a list (std::vector over std::string) of valid file extensions.
598-
* The class acts as a functor that throws a seqan3::validation_error exception whenever a given filename's
599-
* extension (sts::string) is not in the given list of valid file extensions, if the file already exist, or if the
600-
* parent path does not have the proper writer permissions.
606+
* The class acts as a functor that throws a seqan3::validation_error exception whenever a given filename's extension
607+
* (std::string) is not in the given list of valid file extensions, or if the parent path does not have the proper
608+
* writer permissions.
609+
* In addition, the validator receives a seqan3::output_file_open_options which allows you to specify what to do if your
610+
* output file already exists. seqan3::output_file_open_options::create_new will throw a seqan3::validation_error
611+
* exception if it already exists and seqan3::output_file_open_options::open_or_create will skip this check (that means
612+
* you are allowed to overwrite the existing file).
601613
*
602614
* \include test/snippet/argument_parser/validators_output_file.cpp
603615
*
@@ -613,7 +625,6 @@ template <typename file_t = void>
613625
class output_file_validator : public file_validator_base
614626
{
615627
public:
616-
617628
static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
618629
"Expected either a template type with a static member called valid_formats (a file type) or void.");
619630

@@ -625,24 +636,24 @@ class output_file_validator : public file_validator_base
625636
*/
626637

627638
//!\copydoc seqan3::input_file_validator::input_file_validator()
628-
output_file_validator()
629-
{
630-
if constexpr (!std::same_as<file_t, void>)
631-
file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
632-
}
633-
634-
output_file_validator(output_file_validator const &) = default; //!< Defaulted.
635-
output_file_validator(output_file_validator &&) = default; //!< Defaulted.
636-
output_file_validator & operator=(output_file_validator const &) = default; //!< Defaulted.
637-
output_file_validator & operator=(output_file_validator &&) = default; //!< Defaulted.
638-
virtual ~output_file_validator() = default; //!< Virtual Destructor.
639+
output_file_validator() : output_file_validator{output_file_open_options::create_new}
640+
{}
639641

640-
//!\copydoc seqan3::input_file_validator::input_file_validator(std::vector<std::string>)
641-
explicit output_file_validator(std::vector<std::string> extensions)
642-
//!\cond
643-
requires std::same_as<file_t, void>
644-
//!\endcond
645-
: file_validator_base{}
642+
output_file_validator(output_file_validator const &) = default; //!< Defaulted.
643+
output_file_validator(output_file_validator &&) = default; //!< Defaulted.
644+
output_file_validator & operator=(output_file_validator const &) = default; //!< Defaulted.
645+
output_file_validator & operator=(output_file_validator &&) = default; //!< Defaulted.
646+
virtual ~output_file_validator() = default; //!< Virtual Destructor.
647+
648+
/*!\brief Constructs from a given overwrite mode and a list of valid extensions.
649+
* \param[in] mode A seqan3::output_file_open_options indicating whether the validator throws if a file already
650+
exists.
651+
* \param[in] extensions The valid extensions to validate for. Defaults to
652+
* seqan3::output_file_validator::default_extensions.
653+
*/
654+
explicit output_file_validator(output_file_open_options const mode,
655+
std::vector<std::string> extensions = default_extensions())
656+
: file_validator_base{}, mode{mode}
646657
{
647658
file_validator_base::extensions = std::move(extensions);
648659
}
@@ -651,6 +662,21 @@ class output_file_validator : public file_validator_base
651662
using file_validator_base::file_validator_base;
652663
//!\}
653664

665+
/*!\brief The default extensions of `file_t`.
666+
* \returns A list of default extensions for `file_t`, will be empty if `file_t` is `void`.
667+
*
668+
* \details
669+
*
670+
* If `file_t` does name a valid seqan3 file type that contains a static member `valid_formats` returns the
671+
* extensions of that `file_t` type. Otherwise returns an empty list.
672+
*/
673+
static std::vector<std::string> default_extensions()
674+
{
675+
if constexpr (!std::same_as<file_t, void>)
676+
return detail::valid_file_extensions<typename file_t::valid_formats>();
677+
return {};
678+
}
679+
654680
// Import the base::operator()
655681
using file_validator_base::operator();
656682

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

669695
// Check if file has any write permissions.
@@ -684,9 +710,16 @@ class output_file_validator : public file_validator_base
684710
//!\brief Returns a message that can be appended to the (positional) options help page info.
685711
std::string get_help_page_message() const
686712
{
687-
return "The output file must not exist already and write permissions must be granted." +
688-
valid_extensions_help_page_message();
713+
if (mode == output_file_open_options::open_or_create)
714+
return "Write permissions must be granted." + valid_extensions_help_page_message();
715+
else // mode == create_new
716+
return "The output file must not exist already and write permissions must be granted." +
717+
valid_extensions_help_page_message();
689718
}
719+
720+
private:
721+
//!\brief Stores the current mode of whether it is valid to overwrite the output file.
722+
output_file_open_options mode{output_file_open_options::create_new};
690723
};
691724

692725
/*!\brief A validator that checks if a given path is a valid input directory.

test/snippet/argument_parser/validators_output_directory.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ int main(int argc, const char ** argv)
1010
std::filesystem::path mydir{};
1111

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

1617
// an exception will be thrown if the user specifies a directory that cannot be created by the filesystem either

test/snippet/argument_parser/validators_output_file.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@ int main(int argc, const char ** argv)
99
//! [validator_call]
1010
std::filesystem::path myfile{};
1111

12-
myparser.add_option(myfile,'f',"file","Output file containing the processed sequences.",
13-
seqan3::option_spec::DEFAULT, seqan3::output_file_validator{{"fa","fasta"}});
12+
// Use the seqan3::output_file_open_options to indicate that you allow overwriting existing output files, ...
13+
myparser.add_option(myfile, 'f', "file", "Output file containing the processed sequences.",
14+
seqan3::option_spec::DEFAULT,
15+
seqan3::output_file_validator{seqan3::output_file_open_options::open_or_create, {"fa","fasta"}});
16+
17+
// ... or that you will throw a seqan3::validation_error if the user specified output file already exists
18+
myparser.add_option(myfile, 'g', "file2", "Output file containing the processed sequences.",
19+
seqan3::option_spec::DEFAULT,
20+
seqan3::output_file_validator{seqan3::output_file_open_options::create_new, {"fa","fasta"}});
1421
//! [validator_call]
1522

1623
// an exception will be thrown if the user specifies a filename

test/snippet/argument_parser/validators_output_file_ext_from_file.cpp

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
int main(int argc, const char ** argv)
66
{
77
// Default constructed validator has an empty extension list.
8-
seqan3::output_file_validator validator1{};
8+
seqan3::output_file_validator validator1{seqan3::output_file_open_options::create_new};
99
seqan3::debug_stream << validator1.get_help_page_message() << "\n";
1010

1111
// Specify your own extensions for the output file.
12-
seqan3::output_file_validator validator2{std::vector{std::string{"exe"}, std::string{"fasta"}}};
12+
seqan3::output_file_validator validator2{seqan3::output_file_open_options::create_new,
13+
std::vector{std::string{"exe"}, std::string{"fasta"}}};
1314
seqan3::debug_stream << validator2.get_help_page_message() << "\n";
1415

1516
// Give the seqan3 file type as a template argument to get all valid extensions for this file.
16-
seqan3::output_file_validator<seqan3::sequence_file_output<>> validator3{};
17+
seqan3::output_file_validator<seqan3::sequence_file_output<>> validator3
18+
{
19+
seqan3::output_file_open_options::create_new
20+
};
1721
seqan3::debug_stream << validator3.get_help_page_message() << "\n";
1822

1923
return 0;

0 commit comments

Comments
 (0)