diff --git a/README.md b/README.md index 69eeb4aca..0877bc9af 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,8 @@ CLI11 has several Validators built in that perform some common checks - `CLI::Transformer(...)`: 🚧 Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details. - `CLI::CheckedTransformer(...)`: 🚧 Modify the input using a map, and Require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details. - `CLI::ExistingFile`: Requires that the file exists if given. +- `CLI::ExistingReadFile`: Requires that the file given exists and have permission to read it. +- `CLI::ExistingWriteFile`: Requires that the file given exists and have permission to write to it. - `CLI::ExistingDirectory`: Requires that the directory exists. - `CLI::ExistingPath`: Requires that the path (file or directory) exists. - `CLI::NonexistentPath`: Requires that the path does not exist. diff --git a/examples/validators.cpp b/examples/validators.cpp index d431ed223..b5ac431f5 100644 --- a/examples/validators.cpp +++ b/examples/validators.cpp @@ -5,7 +5,7 @@ int main(int argc, char **argv) { CLI::App app("Validator checker"); std::string file; - app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingFile); + app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingReadableFile); int count; app.add_option("-v,--value", count, "Value in range")->check(CLI::Range(3, 6)); diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index a549194b1..6e6917e59 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -216,8 +216,8 @@ namespace detail { /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: - ExistingFileValidator() : Validator("FILE") { - func_ = [](std::string &filename) { + ExistingFileValidator(unsigned short mode = 0) : Validator("FILE") { + func_ = [mode](std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; bool is_dir = (buffer.st_mode & S_IFDIR) != 0; @@ -226,6 +226,9 @@ class ExistingFileValidator : public Validator { } else if(is_dir) { return "File is actually a directory: " + filename; } + if(mode && !(buffer.st_mode & mode)) { + return "File doesn't have the wanted permission: " + filename; + } return std::string(); }; } @@ -328,6 +331,12 @@ class PositiveNumber : public Validator { /// Check for existing file (returns error message if check fails) const detail::ExistingFileValidator ExistingFile; +/// Check that the file exist and available for read +const detail::ExistingFileValidator ExistingReadableFile(S_IREAD); + +/// Check that the file exist and available for write +const detail::ExistingFileValidator ExistingWritableFile(S_IWRITE); + /// Check for an existing directory (returns error message if check fails) const detail::ExistingDirectoryValidator ExistingDirectory; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 5a9d8731d..a7413249d 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -1466,6 +1466,49 @@ TEST_F(TApp, FileExists) { EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); } +TEST_F(TApp, FileExistsForRead) { + std::string myfile{"TestNonFileNotUsed.txt"}; + EXPECT_FALSE(CLI::ExistingReadableFile(myfile).empty()); + + bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file + EXPECT_TRUE(ok); + + std::string filename = "Failed"; + app.add_option("--file", filename)->check(CLI::ExistingReadableFile); + args = {"--file", myfile}; + + run(); + + EXPECT_EQ(myfile, filename); + + chmod(myfile.c_str(), 0); + EXPECT_THROW(run(), CLI::ValidationError); + + std::remove(myfile.c_str()); + EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); +} + +TEST_F(TApp, FileExistsForWrite) { + std::string myfile{"TestNonFileNotUsed.txt"}; + EXPECT_FALSE(CLI::ExistingWritableFile(myfile).empty()); + + bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file + EXPECT_TRUE(ok); + + std::string filename = "Failed"; + app.add_option("--file", filename)->check(CLI::ExistingWritableFile); + args = {"--file", myfile}; + + run(); + EXPECT_EQ(myfile, filename); + + chmod(myfile.c_str(), S_IRUSR); + EXPECT_THROW(run(), CLI::ValidationError); + + std::remove(myfile.c_str()); + EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); +} + TEST_F(TApp, NotFileExists) { std::string myfile{"TestNonFileNotUsed.txt"}; EXPECT_FALSE(CLI::ExistingFile(myfile).empty());