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
103 changes: 94 additions & 9 deletions nob.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,14 @@

#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# define _WINUSER_
# define _WINGDI_
# define NOGDI
# define _IMM_
# define _WINCON_
# include <windows.h>
# include <direct.h>
# include <io.h>
# include <shellapi.h>
# include <shlwapi.h>
#else
# ifdef __APPLE__
# include <mach-o/dyld.h>
Expand All @@ -177,6 +177,7 @@
# include <unistd.h>
# include <fcntl.h>
# include <dirent.h>
# include <fnmatch.h>
#endif

#ifdef __HAIKU__
Expand Down Expand Up @@ -254,10 +255,12 @@ typedef enum {
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_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);
// nob_filepath_match() reports whether name matches the shell pattern.
// For Windows, it uses PathMatchSpecW(); for others, it uses fnmatch().
NOBDEF bool nob_filepath_match(const char *pattern, const char *name);

typedef enum {
// If the current file is a directory go inside of it.
Expand Down Expand Up @@ -330,6 +333,17 @@ NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir);
NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir);
NOBDEF void nob_dir_entry_close(Nob_Dir_Entry dir);

typedef struct {
// Read dir recursively no matter how deep it is
bool recursively;
// Filter results by nob_filepath_match()
const char *wildcard;
} Nob_Read_Entire_Dir_Opt;

NOBDEF bool nob_read_entire_dir_opt(const char *parent, Nob_File_Paths *children, Nob_Read_Entire_Dir_Opt opt);

#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 @@ -1826,15 +1840,84 @@ NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_
return ok;
}

NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
bool nob__read_entire_dir_visit_recursively(Nob_Walk_Entry entry)
{
Nob_File_Paths *children = (Nob_File_Paths*)entry.data;
if (entry.type != NOB_FILE_DIRECTORY) {
nob_da_append(children, nob_temp_strdup(entry.path));
}
return true;
}

bool nob__read_entire_dir_visit(Nob_Walk_Entry entry)
{
if (entry.level == 1) {
Nob_File_Paths *children = (Nob_File_Paths*)entry.data;
nob_da_append(children, nob_temp_file_name(entry.path));
}
if (entry.level > 1) *entry.action = NOB_WALK_SKIP;
return true;
}

NOBDEF bool nob_read_entire_dir_opt(const char *parent, Nob_File_Paths *children, Nob_Read_Entire_Dir_Opt opt)
{
bool result = true;
Nob_Dir_Entry dir = {0};
if (!nob_dir_entry_open(parent, &dir)) nob_return_defer(false);
while (nob_dir_entry_next(&dir)) nob_da_append(children, nob_temp_strdup(dir.name));
if (dir.error) nob_return_defer(false);
Nob_File_Paths paths = {0};

if (opt.recursively) {
if (!nob_walk_dir(parent, nob__read_entire_dir_visit_recursively, &paths, .post_order= true)) {
nob_return_defer(false);
}
} else {
if (!nob_walk_dir(parent, nob__read_entire_dir_visit, &paths, .post_order= true)) {
nob_return_defer(false);
}
}

if (opt.wildcard) {
Nob_File_Paths filtered = {0};
nob_da_foreach(const char *, path, &paths) {
if (nob_filepath_match(opt.wildcard, *path)) {
nob_da_append(&filtered, nob_temp_strdup(*path));
}
}
nob_da_free(paths);
paths = filtered;
}

defer:
nob_da_append_many(children, paths.items, paths.count);
nob_da_free(paths);
return result;
}

NOBDEF bool nob_filepath_match(const char *pattern, const char *name)
{
bool result = true;

#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 \"%s\" to wide characters: %s", pattern, nob_win32_error_message(GetLastError()));
nob_return_defer(false);
}
if (mbstowcs_s(&pszFileSize, pszFile, MAX_PATH, name, strlen(name) + 1)) {
nob_log(NOB_ERROR, "Could not converts \"%s\" to wide characters: %s", name, nob_win32_error_message(GetLastError()));
nob_return_defer(false);
}
if (!PathMatchSpecW((LPCWSTR)pszFile, (LPCWSTR)pszSpec)) {
nob_return_defer(false);
}
#else
if (fnmatch(pattern, name, FNM_PATHNAME)) {
nob_return_defer(false);
}
#endif

defer:
nob_dir_entry_close(dir);
return result;
}

Expand Down Expand Up @@ -2542,7 +2625,9 @@ NOBDEF char *nob_temp_running_executable_path(void)
#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 nob_read_entire_dir
#define filepath_match nob_filepath_match
#define WALK_CONT NOB_WALK_CONT
#define WALK_SKIP NOB_WALK_SKIP
#define WALK_STOP NOB_WALK_STOP
Expand Down
102 changes: 95 additions & 7 deletions tests/read_entire_dir.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "shared.h"
#define NOB_IMPLEMENTATION
#define NOB_NO_ECHO
#include "nob.h"

int compar_paths(const void *a, const void *b)
Expand All @@ -9,19 +10,106 @@ int compar_paths(const void *a, const void *b)
return strcmp(*ap, *bp);
}

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;
if (!mktestdir()) return 1;

// Test nob_read_entire_dir()
Nob_File_Paths children = {0};
if (!nob_read_entire_dir(".", &children)) return 1;
qsort(children.items, children.count, sizeof(*children.items), compar_paths);
nob_log(INFO, "Tests:");
for (size_t i = 0; i < children.count; ++i) {
if (*children.items[i] == '.') continue;
nob_log(INFO, " %s", children.items[i]);
nob_log(INFO, "read_entire_dir():");
nob_da_foreach(const char *, child, &children) {
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;
qsort(children.items, children.count, sizeof(*children.items), compar_paths);
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, .recursively = true, .wildcard = "./src/*.c")) return 1;
if (!read_entire_dir(".", &srcs, .recursively = true, .wildcard = "./src/**/*.c")) return 1;
if (!read_entire_dir(".", &headers, .recursively = true, .wildcard = "./include/*.h")) return 1;
if (!read_entire_dir(".", &headers, .recursively = true, .wildcard = "./include/**/*.h")) return 1;
if (!read_entire_dir(".", &headers, .recursively = true, .wildcard = "./external/**/*.h")) return 1;
if (!read_entire_dir(".", &libs, .recursively = true, .wildcard = "./external/**/*.a")) return 1;
if (!read_entire_dir(".", &dlls, .recursively = true, .wildcard = "./external/**/*.so")) return 1;
qsort(srcs.items, srcs.count, sizeof(*srcs.items), compar_paths);
qsort(headers.items, headers.count, sizeof(*headers.items), compar_paths);
qsort(libs.items, libs.count, sizeof(*libs.items), compar_paths);
qsort(dlls.items, dlls.count, sizeof(*dlls.items), compar_paths);

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;
}
38 changes: 34 additions & 4 deletions tests/read_entire_dir.stderr.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
[INFO] Tests:
[INFO] bar.txt
[INFO] baz.txt
[INFO] foo.txt
[INFO] read_entire_dir():
[INFO] external
[INFO] include
[INFO] src
[INFO] read_entire_dir() recursively:
[INFO] ./external/foobar/foobar.h
[INFO] ./external/foobar/libfoobar.a
[INFO] ./external/foobar/libfoobar.so
[INFO] ./external/foobarbaz/foobarbaz.h
[INFO] ./external/foobarbaz/libfoobarbaz.a
[INFO] ./external/foobarbaz/libfoobarbaz.so
[INFO] ./include/bar/bar.h
[INFO] ./include/baz.h
[INFO] ./include/foo/foo.h
[INFO] ./src/bar/bar.c
[INFO] ./src/baz.c
[INFO] ./src/foo/foo.c
[INFO] read_entire_dir() wildcard:
[INFO] srcs:
[INFO] ./src/bar/bar.c
[INFO] ./src/baz.c
[INFO] ./src/foo/foo.c
[INFO] headers:
[INFO] ./external/foobar/foobar.h
[INFO] ./external/foobarbaz/foobarbaz.h
[INFO] ./include/bar/bar.h
[INFO] ./include/baz.h
[INFO] ./include/foo/foo.h
[INFO] libs:
[INFO] ./external/foobar/libfoobar.a
[INFO] ./external/foobarbaz/libfoobarbaz.a
[INFO] dlls:
[INFO] ./external/foobar/libfoobar.so
[INFO] ./external/foobarbaz/libfoobarbaz.so