Commit a8d597dae4b3ef11312d1e1b2a699ad46a41cb27
Committed by
Henry Schreiner
1 parent
418b7175
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
Showing
3 changed files
with
77 additions
and
20 deletions
include/CLI/App.hpp
| @@ -2075,8 +2075,13 @@ class App { | @@ -2075,8 +2075,13 @@ class App { | ||
| 2075 | } | 2075 | } |
| 2076 | if(!config_name_.empty()) { | 2076 | if(!config_name_.empty()) { |
| 2077 | try { | 2077 | try { |
| 2078 | - std::vector<ConfigItem> values = config_formatter_->from_file(config_name_); | ||
| 2079 | - _parse_config(values); | 2078 | + auto path_result = detail::check_path(config_name_.c_str()); |
| 2079 | + if(path_result == detail::path_type::file) { | ||
| 2080 | + std::vector<ConfigItem> values = config_formatter_->from_file(config_name_); | ||
| 2081 | + _parse_config(values); | ||
| 2082 | + } else if(config_required_) { | ||
| 2083 | + throw FileError::Missing(config_name_); | ||
| 2084 | + } | ||
| 2080 | } catch(const FileError &) { | 2085 | } catch(const FileError &) { |
| 2081 | if(config_required_) | 2086 | if(config_required_) |
| 2082 | throw; | 2087 | throw; |
include/CLI/Validators.hpp
| @@ -13,11 +13,27 @@ | @@ -13,11 +13,27 @@ | ||
| 13 | #include <memory> | 13 | #include <memory> |
| 14 | #include <string> | 14 | #include <string> |
| 15 | 15 | ||
| 16 | +// [CLI11:verbatim] | ||
| 17 | +#if(defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) | ||
| 18 | +#define CLI11_CPP17 | ||
| 19 | +#endif | ||
| 20 | + | ||
| 16 | // C standard library | 21 | // C standard library |
| 17 | // Only needed for existence checking | 22 | // Only needed for existence checking |
| 18 | -// Could be swapped for filesystem in C++17 | 23 | +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM |
| 24 | +#if __has_include(<filesystem>) | ||
| 25 | +#define CLI11_HAS_FILESYSTEM 1 | ||
| 26 | +#endif | ||
| 27 | +#endif | ||
| 28 | + | ||
| 29 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | ||
| 30 | +#include <filesystem> | ||
| 31 | +#else | ||
| 19 | #include <sys/stat.h> | 32 | #include <sys/stat.h> |
| 20 | #include <sys/types.h> | 33 | #include <sys/types.h> |
| 34 | +#endif | ||
| 35 | + | ||
| 36 | +// [CLI11:verbatim] | ||
| 21 | 37 | ||
| 22 | namespace CLI { | 38 | namespace CLI { |
| 23 | 39 | ||
| @@ -250,18 +266,54 @@ class CustomValidator : public Validator { | @@ -250,18 +266,54 @@ class CustomValidator : public Validator { | ||
| 250 | // Therefore, this is in detail. | 266 | // Therefore, this is in detail. |
| 251 | namespace detail { | 267 | namespace detail { |
| 252 | 268 | ||
| 269 | +/// CLI enumeration of different file types | ||
| 270 | +enum class path_type { nonexistant, file, directory }; | ||
| 271 | + | ||
| 272 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | ||
| 273 | +/// get the type of the path from a file name | ||
| 274 | +inline path_type check_path(const char *file) { | ||
| 275 | + try { | ||
| 276 | + auto stat = std::filesystem::status(file); | ||
| 277 | + switch(stat.type()) { | ||
| 278 | + case std::filesystem::file_type::none: | ||
| 279 | + case std::filesystem::file_type::not_found: | ||
| 280 | + return path_type::nonexistant; | ||
| 281 | + case std::filesystem::file_type::directory: | ||
| 282 | + return path_type::directory; | ||
| 283 | + default: | ||
| 284 | + return path_type::file; | ||
| 285 | + } | ||
| 286 | + } catch(const std::filesystem::filesystem_error &) { | ||
| 287 | + return path_type::nonexistant; | ||
| 288 | + } | ||
| 289 | +} | ||
| 290 | +#else | ||
| 291 | +/// get the type of the path from a file name | ||
| 292 | +inline path_type check_path(const char *file) { | ||
| 293 | +#if defined(_MSC_VER) | ||
| 294 | + struct __stat64 buffer; | ||
| 295 | + if(_stat64(file, &buffer) == 0) { | ||
| 296 | + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; | ||
| 297 | + } | ||
| 298 | +#else | ||
| 299 | + struct stat buffer; | ||
| 300 | + if(stat(file, &buffer) == 0) { | ||
| 301 | + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; | ||
| 302 | + } | ||
| 303 | +#endif | ||
| 304 | + return path_type::nonexistant; | ||
| 305 | +} | ||
| 306 | +#endif | ||
| 253 | /// Check for an existing file (returns error message if check fails) | 307 | /// Check for an existing file (returns error message if check fails) |
| 254 | class ExistingFileValidator : public Validator { | 308 | class ExistingFileValidator : public Validator { |
| 255 | public: | 309 | public: |
| 256 | ExistingFileValidator() : Validator("FILE") { | 310 | ExistingFileValidator() : Validator("FILE") { |
| 257 | func_ = [](std::string &filename) { | 311 | func_ = [](std::string &filename) { |
| 258 | - struct stat buffer; | ||
| 259 | - bool exist = stat(filename.c_str(), &buffer) == 0; | ||
| 260 | - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; | ||
| 261 | - if(!exist) { | 312 | + auto path_result = check_path(filename.c_str()); |
| 313 | + if(path_result == path_type::nonexistant) { | ||
| 262 | return "File does not exist: " + filename; | 314 | return "File does not exist: " + filename; |
| 263 | } | 315 | } |
| 264 | - if(is_dir) { | 316 | + if(path_result == path_type::directory) { |
| 265 | return "File is actually a directory: " + filename; | 317 | return "File is actually a directory: " + filename; |
| 266 | } | 318 | } |
| 267 | return std::string(); | 319 | return std::string(); |
| @@ -274,13 +326,11 @@ class ExistingDirectoryValidator : public Validator { | @@ -274,13 +326,11 @@ class ExistingDirectoryValidator : public Validator { | ||
| 274 | public: | 326 | public: |
| 275 | ExistingDirectoryValidator() : Validator("DIR") { | 327 | ExistingDirectoryValidator() : Validator("DIR") { |
| 276 | func_ = [](std::string &filename) { | 328 | func_ = [](std::string &filename) { |
| 277 | - struct stat buffer; | ||
| 278 | - bool exist = stat(filename.c_str(), &buffer) == 0; | ||
| 279 | - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; | ||
| 280 | - if(!exist) { | 329 | + auto path_result = check_path(filename.c_str()); |
| 330 | + if(path_result == path_type::nonexistant) { | ||
| 281 | return "Directory does not exist: " + filename; | 331 | return "Directory does not exist: " + filename; |
| 282 | } | 332 | } |
| 283 | - if(!is_dir) { | 333 | + if(path_result == path_type::file) { |
| 284 | return "Directory is actually a file: " + filename; | 334 | return "Directory is actually a file: " + filename; |
| 285 | } | 335 | } |
| 286 | return std::string(); | 336 | return std::string(); |
| @@ -293,9 +343,8 @@ class ExistingPathValidator : public Validator { | @@ -293,9 +343,8 @@ class ExistingPathValidator : public Validator { | ||
| 293 | public: | 343 | public: |
| 294 | ExistingPathValidator() : Validator("PATH(existing)") { | 344 | ExistingPathValidator() : Validator("PATH(existing)") { |
| 295 | func_ = [](std::string &filename) { | 345 | func_ = [](std::string &filename) { |
| 296 | - struct stat buffer; | ||
| 297 | - bool const exist = stat(filename.c_str(), &buffer) == 0; | ||
| 298 | - if(!exist) { | 346 | + auto path_result = check_path(filename.c_str()); |
| 347 | + if(path_result == path_type::nonexistant) { | ||
| 299 | return "Path does not exist: " + filename; | 348 | return "Path does not exist: " + filename; |
| 300 | } | 349 | } |
| 301 | return std::string(); | 350 | return std::string(); |
| @@ -308,9 +357,8 @@ class NonexistentPathValidator : public Validator { | @@ -308,9 +357,8 @@ class NonexistentPathValidator : public Validator { | ||
| 308 | public: | 357 | public: |
| 309 | NonexistentPathValidator() : Validator("PATH(non-existing)") { | 358 | NonexistentPathValidator() : Validator("PATH(non-existing)") { |
| 310 | func_ = [](std::string &filename) { | 359 | func_ = [](std::string &filename) { |
| 311 | - struct stat buffer; | ||
| 312 | - bool exist = stat(filename.c_str(), &buffer) == 0; | ||
| 313 | - if(exist) { | 360 | + auto path_result = check_path(filename.c_str()); |
| 361 | + if(path_result != path_type::nonexistant) { | ||
| 314 | return "Path already exists: " + filename; | 362 | return "Path already exists: " + filename; |
| 315 | } | 363 | } |
| 316 | return std::string(); | 364 | return std::string(); |
| @@ -1017,7 +1065,7 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman | @@ -1017,7 +1065,7 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman | ||
| 1017 | std::pair<std::string, std::string> vals; | 1065 | std::pair<std::string, std::string> vals; |
| 1018 | trim(commandline); | 1066 | trim(commandline); |
| 1019 | auto esp = commandline.find_first_of(' ', 1); | 1067 | auto esp = commandline.find_first_of(' ', 1); |
| 1020 | - while(!ExistingFile(commandline.substr(0, esp)).empty()) { | 1068 | + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { |
| 1021 | esp = commandline.find_first_of(' ', esp + 1); | 1069 | esp = commandline.find_first_of(' ', esp + 1); |
| 1022 | if(esp == std::string::npos) { | 1070 | if(esp == std::string::npos) { |
| 1023 | // if we have reached the end and haven't found a valid file just assume the first argument is the | 1071 | // if we have reached the end and haven't found a valid file just assume the first argument is the |
tests/IniTest.cpp
| @@ -167,6 +167,10 @@ TEST(StringBased, SpacesSections) { | @@ -167,6 +167,10 @@ TEST(StringBased, SpacesSections) { | ||
| 167 | EXPECT_EQ("four", output.at(1).inputs.at(0)); | 167 | EXPECT_EQ("four", output.at(1).inputs.at(0)); |
| 168 | } | 168 | } |
| 169 | 169 | ||
| 170 | +TEST(StringBased, file_error) { | ||
| 171 | + EXPECT_THROW(std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError); | ||
| 172 | +} | ||
| 173 | + | ||
| 170 | TEST_F(TApp, IniNotRequired) { | 174 | TEST_F(TApp, IniNotRequired) { |
| 171 | 175 | ||
| 172 | TempFile tmpini{"TestIniTmp.ini"}; | 176 | TempFile tmpini{"TestIniTmp.ini"}; |