From a8d597dae4b3ef11312d1e1b2a699ad46a41cb27 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 25 Nov 2019 11:50:25 -0800 Subject: [PATCH] Filedir checks (#341) * add checks for files and directories so the code can be used in the config check * add use of std::filesystem when available * add some documentation * try a verbatim section * update formatting on validators * update error call to use FileError::Missing * add FileError test for invalid file * format tweak --- include/CLI/App.hpp | 9 +++- include/CLI/Validators.hpp | 84 ++++++++++++++++++++++++++++++-------- tests/IniTest.cpp | 4 ++ 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index a5d3ae23..8a58e422 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -2075,8 +2075,13 @@ class App { } if(!config_name_.empty()) { try { - std::vector values = config_formatter_->from_file(config_name_); - _parse_config(values); + auto path_result = detail::check_path(config_name_.c_str()); + if(path_result == detail::path_type::file) { + std::vector values = config_formatter_->from_file(config_name_); + _parse_config(values); + } else if(config_required_) { + throw FileError::Missing(config_name_); + } } catch(const FileError &) { if(config_required_) throw; diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index 05399095..b39d5373 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -13,11 +13,27 @@ #include #include +// [CLI11:verbatim] +#if(defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) +#define CLI11_CPP17 +#endif + // C standard library // Only needed for existence checking -// Could be swapped for filesystem in C++17 +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +#define CLI11_HAS_FILESYSTEM 1 +#endif +#endif + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#else #include #include +#endif + +// [CLI11:verbatim] namespace CLI { @@ -250,18 +266,54 @@ class CustomValidator : public Validator { // Therefore, this is in detail. namespace detail { +/// CLI enumeration of different file types +enum class path_type { nonexistant, file, directory }; + +#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) { + try { + auto stat = std::filesystem::status(file); + switch(stat.type()) { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistant; + case std::filesystem::file_type::directory: + return path_type::directory; + default: + return path_type::file; + } + } catch(const std::filesystem::filesystem_error &) { + return path_type::nonexistant; + } +} +#else +/// get the type of the path from a file name +inline path_type check_path(const char *file) { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistant; +} +#endif /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: ExistingFileValidator() : Validator("FILE") { func_ = [](std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if(!exist) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistant) { return "File does not exist: " + filename; } - if(is_dir) { + if(path_result == path_type::directory) { return "File is actually a directory: " + filename; } return std::string(); @@ -274,13 +326,11 @@ class ExistingDirectoryValidator : public Validator { public: ExistingDirectoryValidator() : Validator("DIR") { func_ = [](std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if(!exist) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistant) { return "Directory does not exist: " + filename; } - if(!is_dir) { + if(path_result == path_type::file) { return "Directory is actually a file: " + filename; } return std::string(); @@ -293,9 +343,8 @@ class ExistingPathValidator : public Validator { public: ExistingPathValidator() : Validator("PATH(existing)") { func_ = [](std::string &filename) { - struct stat buffer; - bool const exist = stat(filename.c_str(), &buffer) == 0; - if(!exist) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistant) { return "Path does not exist: " + filename; } return std::string(); @@ -308,9 +357,8 @@ class NonexistentPathValidator : public Validator { public: NonexistentPathValidator() : Validator("PATH(non-existing)") { func_ = [](std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - if(exist) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistant) { return "Path already exists: " + filename; } return std::string(); @@ -1017,7 +1065,7 @@ inline std::pair split_program_name(std::string comman std::pair vals; trim(commandline); auto esp = commandline.find_first_of(' ', 1); - while(!ExistingFile(commandline.substr(0, esp)).empty()) { + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { esp = commandline.find_first_of(' ', esp + 1); if(esp == std::string::npos) { // if we have reached the end and haven't found a valid file just assume the first argument is the diff --git a/tests/IniTest.cpp b/tests/IniTest.cpp index afff26bc..d8c2ea10 100644 --- a/tests/IniTest.cpp +++ b/tests/IniTest.cpp @@ -167,6 +167,10 @@ TEST(StringBased, SpacesSections) { EXPECT_EQ("four", output.at(1).inputs.at(0)); } +TEST(StringBased, file_error) { + EXPECT_THROW(std::vector output = CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError); +} + TEST_F(TApp, IniNotRequired) { TempFile tmpini{"TestIniTmp.ini"};