Skip to content

Commit

Permalink
add read and write file validators #249
Browse files Browse the repository at this point in the history
Signed-off-by: Rafi Wiener <rafiw@mellanox.com>
  • Loading branch information
Rafi Wiener authored and rafiw committed Dec 7, 2019
1 parent d9379cc commit 66a3428
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ CLI11 has several Validators built-in that perform some common checks
- `CLI::AsNumberWithUnit(...)`:🆕 Modify the `<NUMBER> <UNIT>` 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.
Expand Down
2 changes: 1 addition & 1 deletion examples/validators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
25 changes: 21 additions & 4 deletions include/CLI/Validators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cmath>
#include <functional>
#include <iostream>
#include <fstream>
#include <limits>
#include <map>
#include <memory>
Expand Down Expand Up @@ -267,11 +268,11 @@ 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
inline path_type check_path(const char *file) {
inline path_type check_path(const char *file, unsigned int mode=0) {
try {
auto stat = std::filesystem::status(file);
switch(stat.type()) {
Expand Down Expand Up @@ -307,15 +308,25 @@ 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;
}
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();
};
}
Expand Down Expand Up @@ -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;

Expand Down
45 changes: 45 additions & 0 deletions tests/AppTest.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "app_helper.hpp"
#include <complex>
#include <cstdlib>
#include <sys/stat.h>

#include "gmock/gmock.h"

Expand Down Expand Up @@ -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<bool>(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<bool>(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());
Expand Down
9 changes: 9 additions & 0 deletions tests/app_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "gtest/gtest.h"
#include <iostream>
#include <sys/stat.h>

using input_t = std::vector<std::string>;

Expand Down Expand Up @@ -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
}

0 comments on commit 66a3428

Please sign in to comment.