diff --git a/nob.h b/nob.h index eb0a140..b420513 100644 --- a/nob.h +++ b/nob.h @@ -156,14 +156,14 @@ #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN -# define _WINUSER_ -# define _WINGDI_ +# define NOGDI # define _IMM_ # define _WINCON_ # include # include # include # include +# include #else # ifdef __APPLE__ # include @@ -177,6 +177,7 @@ # include # include # include +# include #endif #ifdef __HAIKU__ @@ -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. @@ -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 @@ -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; } @@ -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 diff --git a/tests/read_entire_dir.c b/tests/read_entire_dir.c index 460d028..0ff7cab 100644 --- a/tests/read_entire_dir.c +++ b/tests/read_entire_dir.c @@ -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) @@ -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; } diff --git a/tests/read_entire_dir.stderr.txt b/tests/read_entire_dir.stderr.txt index b0254eb..89e427c 100644 --- a/tests/read_entire_dir.stderr.txt +++ b/tests/read_entire_dir.stderr.txt @@ -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