Commit a8d597dae4b3ef11312d1e1b2a699ad46a41cb27

Authored by Philip Top
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
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&lt;std::string, std::string&gt; 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"};
... ...