Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 132 additions & 2 deletions nob.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,14 @@
# include <windows.h>
# include <direct.h>
# include <shellapi.h>
# include <shlwapi.h>
#else
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/stat.h>
# include <unistd.h>
# include <fcntl.h>
# include <fnmatch.h>
#endif

#ifdef _WIN32
Expand Down Expand Up @@ -227,14 +229,24 @@ typedef enum {
NOB_FILE_OTHER,
} Nob_File_Type;

// Options for nob_read_entire_dir_opt() function.
typedef struct {
// Read dir recursively
bool recursively;
// Match recursively retrieved results by belowed wildcard string
const char *wildcard;
} Nob_Read_Entire_Dir_Opt;

NOBDEF bool nob_mkdir_if_not_exists(const char *path);
NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path);
NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path);
NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children);
NOBDEF bool nob_read_entire_dir_opt(const char *parent, Nob_File_Paths *children, Nob_Read_Entire_Dir_Opt opt);
NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size);
NOBDEF Nob_File_Type nob_get_file_type(const char *path);
NOBDEF bool nob_delete_file(const char *path);

// Same as nob_read_entire_dir_opt but using cool variadic macro to set the default options.
#define nob_read_entire_dir(parent, children, ...) nob_read_entire_dir_opt((parent), (children), (Nob_Read_Entire_Dir_Opt){__VA_ARGS__})
#define nob_return_defer(value) do { result = (value); goto defer; } while(0)

// Initial capacity of a dynamic array
Expand Down Expand Up @@ -1530,7 +1542,7 @@ NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...)
fprintf(stderr, "\n");
}

NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
NOBDEF bool nob__read_entire_dir(const char *parent, Nob_File_Paths *children)
{
bool result = true;
DIR *dir = NULL;
Expand Down Expand Up @@ -1567,6 +1579,122 @@ NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
return result;
}

NOBDEF bool nob__read_entire_dir_recursively(const char *parent, Nob_File_Paths *children)
{
bool result = true;
DIR *dir = NULL;

dir = opendir(parent);
if (dir == NULL) {
#ifdef _WIN32
nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, nob_win32_error_message(GetLastError()));
#else
nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno));
#endif // _WIN32
nob_return_defer(false);
}

errno = 0;
struct dirent *ent = readdir(dir);
char *path = NULL;
while (ent != NULL) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
goto next_entry;
}
#ifdef _WIN32
if (!strcmp(parent, ".") || !strcmp(parent, ".\\")) {
path = nob_temp_strdup(ent->d_name);
} else {
path = nob_temp_sprintf("%s\\%s", parent, ent->d_name);
}
#else
if (!strcmp(parent, ".") || !strcmp(parent, "./")) {
path = nob_temp_strdup(ent->d_name);
} else {
path = nob_temp_sprintf("%s/%s", parent, ent->d_name);
}
#endif
switch(nob_get_file_type(path)) {
case NOB_FILE_REGULAR:
nob_da_append(children, path);
break;
case NOB_FILE_DIRECTORY:
result = result && nob__read_entire_dir_recursively(path, children);
break;
case NOB_FILE_SYMLINK:
case NOB_FILE_OTHER:
break;
default:
NOB_UNREACHABLE("nob_read_entire_dir_recursively");
}
next_entry:
ent = readdir(dir);
}

if (errno != 0) {
#ifdef _WIN32
nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, nob_win32_error_message(GetLastError()));
#else
nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno));
#endif // _WIN32
nob_return_defer(false);
}

defer:
if (dir) closedir(dir);
return result;
}

NOBDEF bool nob__read_entire_dir_wildcard(const char *parent, Nob_File_Paths *children, const char *pattern)
{
Nob_File_Paths paths = {0};
bool result = true;

if (!nob__read_entire_dir_recursively(parent, &paths)) {
nob_return_defer(false);
}
#ifdef _WIN32
// https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathmatchspecw
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mbstowcs-s-mbstowcs-s-l
wchar_t pszFile[MAX_PATH], pszSpec[MAX_PATH];
size_t pszFileSize, pszSpecSize;
if (mbstowcs_s(&pszSpecSize, pszSpec, MAX_PATH, pattern, strlen(pattern) + 1)) {
nob_log(NOB_ERROR, "Could not converts multibyte characters to wide characters", parent, nob_win32_error_message(GetLastError()));
nob_return_defer(false);
}
nob_da_foreach(const char *, path, &paths) {
if (mbstowcs_s(&pszFileSize, pszFile, MAX_PATH, *path, strlen(*path) + 1)) {
nob_log(NOB_ERROR, "Could not converts multibyte characters to wide characters", parent, nob_win32_error_message(GetLastError()));
continue;
}
if (PathMatchSpecW((LPCWSTR)pszFile, (LPCWSTR)pszSpec)) {
nob_da_append(children, *path);
}
}
#else
nob_da_foreach(const char *, path, &paths) {
if(!fnmatch(pattern, *path, FNM_PATHNAME)) {
nob_da_append(children, *path);
}
}
#endif

defer:
nob_da_free(paths);
return result;
}

NOBDEF bool nob_read_entire_dir_opt(const char *parent, Nob_File_Paths *children, Nob_Read_Entire_Dir_Opt opt)
{
if (opt.wildcard) {
return nob__read_entire_dir_wildcard(parent, children, opt.wildcard);
}
if (opt.recursively) {
return nob__read_entire_dir_recursively(parent, children);
}
return nob__read_entire_dir(parent, children);
}

NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size)
{
bool result = true;
Expand Down Expand Up @@ -2223,6 +2351,8 @@ NOBDEF int closedir(DIR *dirp)
#define mkdir_if_not_exists nob_mkdir_if_not_exists
#define copy_file nob_copy_file
#define copy_directory_recursively nob_copy_directory_recursively
#define Read_Entire_Dir_Opt Nob_Read_Entire_Dir_Opt
#define read_entire_dir_opt nob_read_entire_dir_opt
#define read_entire_dir nob_read_entire_dir
#define write_entire_file nob_write_entire_file
#define get_file_type nob_get_file_type
Expand Down
98 changes: 89 additions & 9 deletions tests/read_entire_dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,99 @@
#define NOB_STRIP_PREFIX
#include "nob.h"

bool mktestdir() {
// build/tests/read_entire_dir.cwd
// ├── external
// │ ├── foobar
// │ │ ├── foobar.h
// │ │ ├── libfoobar.a
// │ │ └── libfoobar.so
// │ └── foobarbaz
// │ ├── foobarbaz.h
// │ ├── libfoobarbaz.a
// │ └── libfoobarbaz.so
// ├── include
// │ ├── bar
// │ │ └── bar.h
// │ ├── baz.h
// │ └── foo
// │ └── foo.h
// └── src
// ├── bar
// │ └── bar.c
// ├── baz.c
// └── foo
// └── foo.c
return mkdir_if_not_exists("src")
&& mkdir_if_not_exists("src/foo")
&& mkdir_if_not_exists("src/bar")
&& mkdir_if_not_exists("include")
&& mkdir_if_not_exists("include/foo")
&& mkdir_if_not_exists("include/bar")
&& mkdir_if_not_exists("external")
&& mkdir_if_not_exists("external/foobar")
&& mkdir_if_not_exists("external/foobarbaz")
&& write_entire_file("src/foo/foo.c", NULL, 0)
&& write_entire_file("src/bar/bar.c", NULL, 0)
&& write_entire_file("src/baz.c", NULL, 0)
&& write_entire_file("include/foo/foo.h", NULL, 0)
&& write_entire_file("include/bar/bar.h", NULL, 0)
&& write_entire_file("include/baz.h", NULL, 0)
&& write_entire_file("external/foobar/foobar.h", NULL, 0)
&& write_entire_file("external/foobar/libfoobar.a", NULL, 0)
&& write_entire_file("external/foobar/libfoobar.so", NULL, 0)
&& write_entire_file("external/foobarbaz/foobarbaz.h", NULL, 0)
&& write_entire_file("external/foobarbaz/libfoobarbaz.a", NULL, 0)
&& write_entire_file("external/foobarbaz/libfoobarbaz.so", NULL, 0);
}

int main(void)
{
if (!write_entire_file("foo.txt", NULL, 0)) return 1;
if (!write_entire_file("bar.txt", NULL, 0)) return 1;
if (!write_entire_file("baz.txt", NULL, 0)) return 1;

// Test nob_read_entire_dir()
if (!mktestdir()) return 1;
Nob_File_Paths children = {0};
if (!nob_read_entire_dir(".", &children)) return 1;
nob_log(INFO, "Tests:");
for (size_t i = 0; i < children.count; ++i) {
if (*children.items[i] != '.') {
nob_log(INFO, " %s", children.items[i]);
if (!read_entire_dir(".", &children)) return 1;
nob_log(INFO, "read_entire_dir():");
da_foreach(const char *, child, &children) {
if (**child != '.') {
nob_log(INFO, " %s", *child);
}
}

// Test nob_read_entire_dir() with recursively option
nob_da_free(children);
children = (Nob_File_Paths){0};
if (!read_entire_dir(".", &children, .recursively = true)) return 1;
nob_log(INFO, "read_entire_dir() recursively:");
da_foreach(const char *, child, &children) {
nob_log(INFO, " %s", *child);
}

// Test nob_read_entire_dir() with wildcard option
File_Paths srcs = {0}, headers = {0}, libs = {0}, dlls = {0};
if (!read_entire_dir(".", &srcs, .wildcard = "src/*.c")) return 1;
if (!read_entire_dir(".", &srcs, .wildcard = "src/**/*.c")) return 1;
if (!read_entire_dir(".", &headers, .wildcard = "include/*.h")) return 1;
if (!read_entire_dir(".", &headers, .wildcard = "include/**/*.h")) return 1;
if (!read_entire_dir(".", &headers, .wildcard = "external/**/*.h")) return 1;
if (!read_entire_dir(".", &libs, .wildcard = "external/**/*.a")) return 1;
if (!read_entire_dir(".", &dlls, .wildcard = "external/**/*.so")) return 1;
nob_log(INFO, "read_entire_dir() wildcard:");
nob_log(INFO, "srcs:");
da_foreach(const char *, src, &srcs) {
nob_log(INFO, " %s", *src);
}
nob_log(INFO, "headers:");
da_foreach(const char *, header, &headers) {
nob_log(INFO, " %s", *header);
}
nob_log(INFO, "libs:");
da_foreach(const char *, lib, &libs) {
nob_log(INFO, " %s", *lib);
}
nob_log(INFO, "dlls:");
da_foreach(const char *, dll, &dlls) {
nob_log(INFO, " %s", *dll);
}
return 0;
}