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 | 2075 | } |
| 2076 | 2076 | if(!config_name_.empty()) { |
| 2077 | 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 | 2085 | } catch(const FileError &) { |
| 2081 | 2086 | if(config_required_) |
| 2082 | 2087 | throw; | ... | ... |
include/CLI/Validators.hpp
| ... | ... | @@ -13,11 +13,27 @@ |
| 13 | 13 | #include <memory> |
| 14 | 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 | 21 | // C standard library |
| 17 | 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 | 32 | #include <sys/stat.h> |
| 20 | 33 | #include <sys/types.h> |
| 34 | +#endif | |
| 35 | + | |
| 36 | +// [CLI11:verbatim] | |
| 21 | 37 | |
| 22 | 38 | namespace CLI { |
| 23 | 39 | |
| ... | ... | @@ -250,18 +266,54 @@ class CustomValidator : public Validator { |
| 250 | 266 | // Therefore, this is in detail. |
| 251 | 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 | 307 | /// Check for an existing file (returns error message if check fails) |
| 254 | 308 | class ExistingFileValidator : public Validator { |
| 255 | 309 | public: |
| 256 | 310 | ExistingFileValidator() : Validator("FILE") { |
| 257 | 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 | 314 | return "File does not exist: " + filename; |
| 263 | 315 | } |
| 264 | - if(is_dir) { | |
| 316 | + if(path_result == path_type::directory) { | |
| 265 | 317 | return "File is actually a directory: " + filename; |
| 266 | 318 | } |
| 267 | 319 | return std::string(); |
| ... | ... | @@ -274,13 +326,11 @@ class ExistingDirectoryValidator : public Validator { |
| 274 | 326 | public: |
| 275 | 327 | ExistingDirectoryValidator() : Validator("DIR") { |
| 276 | 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 | 331 | return "Directory does not exist: " + filename; |
| 282 | 332 | } |
| 283 | - if(!is_dir) { | |
| 333 | + if(path_result == path_type::file) { | |
| 284 | 334 | return "Directory is actually a file: " + filename; |
| 285 | 335 | } |
| 286 | 336 | return std::string(); |
| ... | ... | @@ -293,9 +343,8 @@ class ExistingPathValidator : public Validator { |
| 293 | 343 | public: |
| 294 | 344 | ExistingPathValidator() : Validator("PATH(existing)") { |
| 295 | 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 | 348 | return "Path does not exist: " + filename; |
| 300 | 349 | } |
| 301 | 350 | return std::string(); |
| ... | ... | @@ -308,9 +357,8 @@ class NonexistentPathValidator : public Validator { |
| 308 | 357 | public: |
| 309 | 358 | NonexistentPathValidator() : Validator("PATH(non-existing)") { |
| 310 | 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 | 362 | return "Path already exists: " + filename; |
| 315 | 363 | } |
| 316 | 364 | return std::string(); |
| ... | ... | @@ -1017,7 +1065,7 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman |
| 1017 | 1065 | std::pair<std::string, std::string> vals; |
| 1018 | 1066 | trim(commandline); |
| 1019 | 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 | 1069 | esp = commandline.find_first_of(' ', esp + 1); |
| 1022 | 1070 | if(esp == std::string::npos) { |
| 1023 | 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 | 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 | 174 | TEST_F(TApp, IniNotRequired) { |
| 171 | 175 | |
| 172 | 176 | TempFile tmpini{"TestIniTmp.ini"}; | ... | ... |