Skip to content

Commit

Permalink
Implement fr_find{first,next} with std::filesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
LegalizeAdulthood committed Feb 29, 2024
1 parent 69cfc7f commit 402baf3
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 176 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ add_library(libid
headers/make_path.h
common/miscovl.cpp headers/miscovl.h
common/miscres.cpp headers/miscres.h
common/path_match.cpp headers/path_match.h
common/prompts1.cpp headers/prompts1.h
common/prompts2.cpp headers/prompts2.h
common/realdos.cpp headers/realdos.h
Expand Down Expand Up @@ -295,6 +296,7 @@ source_group("Header Files/common/ui" FILES
headers/make_path.h
headers/miscovl.h
headers/miscres.h
headers/path_match.h
headers/prompts1.h
headers/prompts2.h
headers/realdos.h
Expand All @@ -312,6 +314,7 @@ source_group("Source Files/common/ui" FILES
common/jiim.cpp
common/miscovl.cpp
common/miscres.cpp
common/path_match.cpp
common/prompts1.cpp
common/prompts2.cpp
common/realdos.cpp
Expand Down
108 changes: 108 additions & 0 deletions common/find_file.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include "find_file.h"

#include "path_match.h"

DIR_SEARCH DTA; // Allocate DTA and define structure

namespace fs = std::filesystem;

static MatchFn s_path_matcher;
enum class dir_pos
{
NONE = 0,
DOT = 1,
DOT_DOT = 2,
FILES = 3
};
static dir_pos s_dir_pos{dir_pos::NONE};
static fs::directory_iterator s_dir_it;

/* fill_dta
*
* Use data in s_find_data to fill in DTA.filename, DTA.attribute and DTA.path
*/
static void fill_dta()
{
DTA.path = s_dir_it->path().string();
DTA.filename = s_dir_it->path().filename().string();
DTA.attribute = is_directory(*s_dir_it) ? SUBDIR : 0;
}

static bool next_match()
{
if (s_dir_pos == dir_pos::NONE)
{
DTA.path = (s_dir_it->path() / ".").string();
DTA.filename = ".";
DTA.attribute = SUBDIR;
s_dir_pos = dir_pos::DOT;
return true;
}
if (s_dir_pos == dir_pos::DOT)
{
DTA.path = (s_dir_it->path() / "..").string();
DTA.filename = "..";
DTA.attribute = SUBDIR;
s_dir_pos = dir_pos::DOT_DOT;
return true;
}
s_dir_pos = dir_pos::FILES;

while (s_dir_it != fs::directory_iterator() && !s_path_matcher(*s_dir_it))
{
++s_dir_it;
}
if (s_dir_it == fs::directory_iterator())
{
s_dir_pos = dir_pos::NONE;
return false;
}

fill_dta();
return true;
}

/* fr_findfirst
*
* Fill in DTA.filename, DTA.path and DTA.attribute for the first file
* matching the wildcard specification in path. Return zero if a file
* is found, or non-zero if a file was not found or an error occurred.
*/
int fr_findfirst(char const *path) // Find 1st file (or subdir) meeting path/filespec
{
const fs::path search{path};
const fs::path search_dir{is_directory(search) ? search : search.parent_path()};
std::error_code err;
s_dir_it = fs::directory_iterator(search_dir, err);
if (err)
{
return 1;
}

s_path_matcher = match_fn(path);
if (is_directory(search) ||
(search.filename() == "*" && (search.extension().string().empty() || search.extension() == ".*")))
{
s_dir_pos = dir_pos::NONE;
}
else
{
s_dir_pos = dir_pos::FILES;
}
return next_match() ? 0 : 1;
}

/* fr_findnext
*
* Find the next file matching the wildcard search begun by fr_findfirst.
* Fill in DTA.filename, DTA.path, and DTA.attribute
*/
int fr_findnext()
{
if (s_dir_pos == dir_pos::FILES)
{
++s_dir_it;
}
return next_match() ? 0 : -1;
}

116 changes: 116 additions & 0 deletions common/path_match.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <path_match.h>

#include <regex>

namespace fs = std::filesystem;

static bool match_wild_string(const std::string &pattern, const std::string &text)
{
if (text.length() != pattern.length())
{
return false;
}
for (std::size_t i = 0; i < text.length(); ++i)
{
if (pattern[i] != '?' && pattern[i] != text[i])
{
return false;
}
}
return true;
}

static std::regex wild_regex(const std::string &wildcard)
{
std::string pat;
for (const char c : wildcard)
{
if (c == '.')
{
pat += "\\.";
}
else if (c == '?')
{
pat += '.';
}
else if (c == '*')
{
pat += ".*";
}
else
{
pat += c;
}
}
return std::regex(pat);
};

MatchFn match_fn(const fs::path &pattern)
{
MatchFn always = [](const fs::path &) { return true; };

MatchFn match_stem;
const std::string pat_stem{pattern.stem().string()};
// match any filename
if (pat_stem == "*")
{
match_stem = always;
}
// match wildcard filename
else if (pat_stem.find('?') != std::string::npos)
{
if (pat_stem.find('*') == std::string::npos)
{
match_stem = [=](const fs::path &path) { return match_wild_string(pat_stem, path.stem().string()); };
}
else
{
const std::regex pat_regex = wild_regex(pat_stem);
match_stem = [=](const fs::path &path) { return std::regex_match(path.stem().string(), pat_regex); };
}
}
// match regex filename
else if (pat_stem.find('*') != std::string::npos)
{
const std::regex pat_regex = wild_regex(pat_stem);
match_stem = [=](const fs::path &path) { return std::regex_match(path.stem().string(), pat_regex); };
}
// match exact filename
else
{
match_stem = [=](const fs::path &path) { return pat_stem == path.stem().string(); };
}

MatchFn match_ext;
const std::string pat_ext{pattern.extension().string()};
// match any extension
if (pat_ext == ".*" || pat_ext.empty())
{
match_ext = always;
}
// match extension with ? wildcards
else if (pat_ext.find('?') != std::string::npos)
{
if (pat_ext.find('*') == std::string::npos)
{
match_ext = [=](const fs::path &path) { return match_wild_string(pat_ext, path.extension().string()); };
}
else
{
const std::regex pat_regex = wild_regex(pat_ext);
match_ext = [=](const fs::path &path) { return std::regex_match(path.extension().string(), pat_regex); };
}
}
else if (pat_ext.find('*') != std::string::npos)
{
const std::regex pat_regex = wild_regex(pat_ext);
match_ext = [=](const fs::path &path) { return std::regex_match(path.extension().string(), pat_regex); };
}
else
// match specific extension
{
match_ext = [=](const fs::path &path) { return pat_ext == path.extension(); };
}

return [=](const fs::path &path) { return match_stem(path) && match_ext(path); };
}
9 changes: 1 addition & 8 deletions common/prompts2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ static int get_screen_corners();
#define TEMPLATE -2 // wild cards present - buiding template
#define SEARCHPATH -3 // no match - building path search name

#define FILEATTR 0x37 // File attributes; select all but volume labels
#define HIDDEN 2
#define SYSTEM 4
#define SUBDIR 16
#define MAXNUMFILES 2977L

DIR_SEARCH DTA; // Allocate DTA and define structure

#define GETFORMULA 0
#define GETLSYS 1
#define GETIFS 2
Expand Down Expand Up @@ -1447,6 +1439,7 @@ int lccompare(void *arg1, void *arg2) // for sort
return stricmp(*choice1, *choice2);
}

#define MAXNUMFILES 2977L

static int speedstate;
bool getafilename(char const *hdg, char const *file_template, char *flname)
Expand Down
10 changes: 6 additions & 4 deletions headers/find_file.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include <filesystem>
#include <functional>
#include <string>

#define FILEATTR 0x37 // File attributes; select all but volume labels
#define HIDDEN 2
#define SYSTEM 4
#define SUBDIR 16
enum
{
SUBDIR = 1
};

struct DIR_SEARCH // Allocate DTA and define structure
{
Expand Down
8 changes: 8 additions & 0 deletions headers/path_match.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <filesystem>
#include <functional>

using MatchFn = std::function<bool(const std::filesystem::path &)>;

MatchFn match_fn(const std::filesystem::path &pattern);
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_executable(test-id
test_find_path.cpp
test_get_ifs_token.cpp
test_make_path.cpp
test_path_match.cpp
test_search_path.cpp
test_update_save_name.cpp
test_find_file.txt.in
Expand Down
37 changes: 23 additions & 14 deletions tests/test_find_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,48 @@
#include <filesystem>
#include <vector>

namespace fs = std::filesystem;

TEST(TestFindFile, firstTextFile)
{
const int result = fr_findfirst((std::filesystem::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());
const int result = fr_findfirst((fs::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());

ASSERT_EQ(0, result);
ASSERT_EQ("find_file1.txt", DTA.filename);
ASSERT_EQ(0, DTA.attribute);
EXPECT_EQ(0, result);
EXPECT_EQ("find_file1.txt", DTA.filename);
EXPECT_EQ(0, DTA.attribute);
}

TEST(TestFindFile, secondTextFile)
{
fr_findfirst((std::filesystem::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());
fr_findfirst((fs::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());

const int second = fr_findnext();

ASSERT_EQ(0, second);
ASSERT_EQ("find_file2.txt", DTA.filename);
ASSERT_EQ(0, DTA.attribute);
EXPECT_EQ(0, second);
EXPECT_EQ("find_file2.txt", DTA.filename);
EXPECT_EQ(0, DTA.attribute);
}

TEST(TestFindFile, noMoreTextFiles)
{
fr_findfirst((std::filesystem::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());
fr_findfirst((fs::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.txt").string().c_str());
fr_findnext();

const int third = fr_findnext();

ASSERT_EQ(-1, third);
EXPECT_EQ(-1, third);
}

TEST(TestFindFile, noMatchingFiles)
{
const int result = fr_findfirst((fs::path(ID_TEST_DATA_FIND_FILE_DIR) / "*.goink").string().c_str());

EXPECT_EQ(1, result);
}

TEST(TestFindFile, allFiles)
{
const std::filesystem::path path{(std::filesystem::path(ID_TEST_DATA_FIND_FILE_DIR) / "*")};
const fs::path path{(fs::path(ID_TEST_DATA_FIND_FILE_DIR) / "*")};
std::vector<int> results;
results.push_back(fr_findfirst(path.string().c_str())); // "."
results.push_back(fr_findnext()); // ".."
Expand All @@ -49,7 +58,7 @@ TEST(TestFindFile, allFiles)
//
results.push_back(fr_findnext()); // "subdir"

ASSERT_EQ(results.end(), std::find_if(results.begin(), results.end(), [](int value) { return value != 0; }));
ASSERT_EQ("subdir", DTA.filename);
ASSERT_EQ(SUBDIR, DTA.attribute);
EXPECT_EQ(results.end(), std::find_if(results.begin(), results.end(), [](int value) { return value != 0; }));
EXPECT_EQ("subdir", DTA.filename);
EXPECT_EQ(SUBDIR, DTA.attribute);
}
Loading

0 comments on commit 402baf3

Please sign in to comment.