Skip to content

Commit a0c0e18

Browse files
Rafi Wienerhenryiii
Rafi Wiener
authored andcommitted
add read and write file validators #249
Signed-off-by: Rafi Wiener <[email protected]>
1 parent 433fd91 commit a0c0e18

File tree

5 files changed

+68
-3
lines changed

5 files changed

+68
-3
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ CLI11 has several Validators built-in that perform some common checks
342342
- `CLI::Transformer(...)`: 🚧 Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
343343
- `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.
344344
- `CLI::ExistingFile`: Requires that the file exists if given.
345+
- `CLI::ExistingReadFile`: Requires that the file given exists and have permission to read it.
346+
- `CLI::ExistingWriteFile`: Requires that the file given exists and have permission to write to it.
345347
- `CLI::ExistingDirectory`: Requires that the directory exists.
346348
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
347349
- `CLI::NonexistentPath`: Requires that the path does not exist.

examples/validators.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ int main(int argc, char **argv) {
55
CLI::App app("Validator checker");
66

77
std::string file;
8-
app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingFile);
8+
app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingReadableFile);
99

1010
int count;
1111
app.add_option("-v,--value", count, "Value in range")->check(CLI::Range(3, 6));

include/CLI/Validators.hpp

+11-2
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ namespace detail {
216216
/// Check for an existing file (returns error message if check fails)
217217
class ExistingFileValidator : public Validator {
218218
public:
219-
ExistingFileValidator() : Validator("FILE") {
220-
func_ = [](std::string &filename) {
219+
ExistingFileValidator(unsigned short mode = 0) : Validator("FILE") {
220+
func_ = [mode](std::string &filename) {
221221
struct stat buffer;
222222
bool exist = stat(filename.c_str(), &buffer) == 0;
223223
bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
@@ -226,6 +226,9 @@ class ExistingFileValidator : public Validator {
226226
} else if(is_dir) {
227227
return "File is actually a directory: " + filename;
228228
}
229+
if(mode && !(buffer.st_mode & mode)) {
230+
return "File doesn't have the wanted permission: " + filename;
231+
}
229232
return std::string();
230233
};
231234
}
@@ -342,6 +345,12 @@ class Number : public Validator {
342345
/// Check for existing file (returns error message if check fails)
343346
const detail::ExistingFileValidator ExistingFile;
344347

348+
/// Check that the file exist and available for read
349+
const detail::ExistingFileValidator ExistingReadableFile(S_IREAD);
350+
351+
/// Check that the file exist and available for write
352+
const detail::ExistingFileValidator ExistingWritableFile(S_IWRITE);
353+
345354
/// Check for an existing directory (returns error message if check fails)
346355
const detail::ExistingDirectoryValidator ExistingDirectory;
347356

tests/AppTest.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "app_helper.hpp"
22
#include <complex>
33
#include <cstdlib>
4+
#include <sys/stat.h>
45

56
#include "gmock/gmock.h"
67

@@ -1476,6 +1477,50 @@ TEST_F(TApp, FileExists) {
14761477
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
14771478
}
14781479

1480+
TEST_F(TApp, FileExistsForRead) {
1481+
std::string myfile{"TestNonFileNotUsed.txt"};
1482+
EXPECT_FALSE(CLI::ExistingReadableFile(myfile).empty());
1483+
1484+
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
1485+
EXPECT_TRUE(ok);
1486+
1487+
std::string filename = "Failed";
1488+
app.add_option("--file", filename)->check(CLI::ExistingReadableFile);
1489+
args = {"--file", myfile};
1490+
1491+
run();
1492+
1493+
EXPECT_EQ(myfile, filename);
1494+
#ifdef __linux__
1495+
my_chmod(myfile.c_str(), 0);
1496+
EXPECT_THROW(run(), CLI::ValidationError);
1497+
#endif
1498+
std::remove(myfile.c_str());
1499+
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1500+
}
1501+
1502+
TEST_F(TApp, FileExistsForWrite) {
1503+
std::string myfile{"TestNonFileNotUsed.txt"};
1504+
EXPECT_FALSE(CLI::ExistingWritableFile(myfile).empty());
1505+
1506+
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
1507+
EXPECT_TRUE(ok);
1508+
1509+
std::string filename = "Failed";
1510+
app.add_option("--file", filename)->check(CLI::ExistingWritableFile);
1511+
args = {"--file", myfile};
1512+
1513+
run();
1514+
EXPECT_EQ(myfile, filename);
1515+
1516+
my_chmod(myfile.c_str(), S_IREAD);
1517+
EXPECT_THROW(run(), CLI::ValidationError);
1518+
1519+
int ret = std::remove(myfile.c_str());
1520+
EXPECT_EQ(ret, 0);
1521+
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1522+
}
1523+
14791524
TEST_F(TApp, NotFileExists) {
14801525
std::string myfile{"TestNonFileNotUsed.txt"};
14811526
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());

tests/app_helper.hpp

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "gtest/gtest.h"
1010
#include <iostream>
11+
#include <sys/stat.h>
1112

1213
typedef std::vector<std::string> input_t;
1314

@@ -55,3 +56,11 @@ inline void unset_env(std::string name) {
5556
unsetenv(name.c_str());
5657
#endif
5758
}
59+
60+
inline int my_chmod(const char *path, int mode) {
61+
#ifdef _WIN32
62+
return _chmod(path, mode);
63+
#else
64+
return chmod(path, mode);
65+
#endif
66+
}

0 commit comments

Comments
 (0)