diff --git a/nob.h b/nob.h index 737f61f..de702a6 100644 --- a/nob.h +++ b/nob.h @@ -159,12 +159,14 @@ # include # include # include +# include #else # include # include # include # include # include +# include #endif #ifdef _WIN32 @@ -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 @@ -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; @@ -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; @@ -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 diff --git a/tests/read_entire_dir.c b/tests/read_entire_dir.c index 7483d86..cfc82d9 100644 --- a/tests/read_entire_dir.c +++ b/tests/read_entire_dir.c @@ -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; }