Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add read and write file validators #249 #250

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 20 additions & 3 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,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
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
}