From bef23880300415439e91e2a569285c92507fa7d6 Mon Sep 17 00:00:00 2001 From: Rafi Wiener Date: Mon, 4 Mar 2019 14:50:51 +0200 Subject: [PATCH] add read and write file validators #249 Signed-off-by: Rafi Wiener Signed-off-by: Rafi Wiener --- README.md | 2 ++ examples/validators.cpp | 2 +- include/CLI/Validators.hpp | 23 ++++++++++++++++--- tests/AppTest.cpp | 45 ++++++++++++++++++++++++++++++++++++++ tests/app_helper.hpp | 9 ++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c4a8550d4..a88b9ae9c 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,8 @@ CLI11 has several Validators built-in that perform some common checks - `CLI::AsNumberWithUnit(...)`:🆕 Modify the ` ` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`). - `CLI::AsSizeValue(...)`: 🆕 Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10. - `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 ea0b2a296..dff81cbfb 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -267,7 +268,7 @@ class CustomValidator : public Validator { namespace detail { /// CLI enumeration of different file types -enum class path_type { nonexistant, file, directory }; +enum class path_type { nonexistant, file, directory, bad_permission }; #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 /// get the type of the path from a file name @@ -307,8 +308,8 @@ inline path_type check_path(const char *file) { /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: - ExistingFileValidator() : Validator("FILE") { - func_ = [](std::string &filename) { + ExistingFileValidator(std::ios_base::openmode mode = std::ios::in) : Validator("FILE") { + func_ = [mode](std::string &filename) { auto path_result = check_path(filename.c_str()); if(path_result == path_type::nonexistant) { return "File does not exist: " + filename; @@ -316,6 +317,16 @@ class ExistingFileValidator : public Validator { if(path_result == path_type::directory) { return "File is actually a directory: " + filename; } + if(mode){ + std::fstream myfile; + try { + myfile.open(filename, mode); + } catch(const std::exception &err) { + } + if(myfile.bad()){ + return "File doesn't have the wanted permission: " + filename; + } + } return std::string(); }; } @@ -444,6 +455,12 @@ class Number : 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(std::ios::in); + +/// Check that the file exist and available for write +const detail::ExistingFileValidator ExistingWritableFile(std::ios::out); + /// 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 60ec382a5..03f8b4beb 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -1,6 +1,7 @@ #include "app_helper.hpp" #include #include +#include #include "gmock/gmock.h" @@ -1697,6 +1698,50 @@ 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); +#ifdef __linux__ + my_chmod(myfile.c_str(), 0); + EXPECT_THROW(run(), CLI::ValidationError); +#endif + 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); + + my_chmod(myfile.c_str(), S_IREAD); + EXPECT_THROW(run(), CLI::ValidationError); + + int ret = std::remove(myfile.c_str()); + EXPECT_EQ(ret, 0); + EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); +} + TEST_F(TApp, NotFileExists) { std::string myfile{"TestNonFileNotUsed.txt"}; EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); diff --git a/tests/app_helper.hpp b/tests/app_helper.hpp index eb8eb2b7d..19f391304 100644 --- a/tests/app_helper.hpp +++ b/tests/app_helper.hpp @@ -8,6 +8,7 @@ #include "gtest/gtest.h" #include +#include using input_t = std::vector; @@ -55,3 +56,11 @@ inline void unset_env(std::string name) { unsetenv(name.c_str()); #endif } + +inline int my_chmod(const char *path, int mode) { +#ifdef _WIN32 + return _chmod(path, mode); +#else + return chmod(path, mode); +#endif +}