From 2189c059921742c641f6f8ad6d78c69b3ad0089d Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 19:51:56 +0800 Subject: [PATCH 1/8] Add incremental directory iterator Functions: - `nob_is_dir_empty()` - `nob_dir_iter_open()` - `nob_dir_iter_next()` - `nob_dir_iter_close()` - `nob_dir_iter_getname()` --- nob.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/nob.h b/nob.h index 737f61f..fb3180e 100644 --- a/nob.h +++ b/nob.h @@ -235,6 +235,12 @@ NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t siz NOBDEF Nob_File_Type nob_get_file_type(const char *path); NOBDEF bool nob_delete_file(const char *path); +NOBDEF int nob_is_dir_empty(const char *path); +NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path); +NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter); +NOBDEF bool nob_dir_iter_close(Nob_Dir_Iter **iter); +NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter); + #define nob_return_defer(value) do { result = (value); goto defer; } while(0) // Initial capacity of a dynamic array @@ -1644,6 +1650,79 @@ NOBDEF bool nob_delete_file(const char *path) #endif // _WIN32 } +// Returns true, false, and -1. +NOBDEF int nob_is_dir_empty(const char *path) { + DIR *dir = opendir(path); + if (dir == NULL) { +#ifdef _WIN32 + nob_log(NOB_ERROR, "Could not open directory %s: %s", path, nob_win32_error_message(GetLastError())); +#else + nob_log(NOB_ERROR, "Could not open directory %s: %s", path, strerror(errno)); +#endif // _WIN32 + return -1; + } + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + closedir(dir); + return false; + } + + closedir(dir); + return true; +} + +// Returns NULL if gets failed +NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path) { + if (iter == NULL) return false; + if (path == NULL) return false; + + iter->path = path; + iter->dir = opendir(path); + iter->current = NULL; + + if (iter->dir == NULL) { +#ifdef _WIN32 + nob_log(NOB_ERROR, "Could not open directory %s: %s", path, nob_win32_error_message(GetLastError())); +#else + nob_log(NOB_ERROR, "Could not open directory %s: %s", path, strerror(errno)); +#endif // _WIN32 + return false; + } + + return true; +} + +NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter) { + if (iter->dir == NULL) return false; + + struct dirent *ent; + while ((ent = readdir(iter->dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + iter->current = ent; + return true; + } + + iter->current = NULL; + return false; +} + +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter **iter) { + if (iter && *iter) { + if ((*iter)->dir) + closedir((*iter)->dir); + NOB_FREE(*iter); + *iter = NULL; + } +} + +NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter) { + return iter->current ? iter->current->d_name : NULL; +} + NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) { bool result = true; From 40665bee40fc0d72c81f4852672ce5884c2baec3 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 20:01:45 +0800 Subject: [PATCH 2/8] Add stripped aliases of functions --- nob.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nob.h b/nob.h index fb3180e..d9721cc 100644 --- a/nob.h +++ b/nob.h @@ -2306,6 +2306,11 @@ NOBDEF int closedir(DIR *dirp) #define write_entire_file nob_write_entire_file #define get_file_type nob_get_file_type #define delete_file nob_delete_file + #define is_dir_empty nob_is_dir_empty + #define dir_iter_open nob_dir_iter_open + #define dir_iter_next nob_dir_iter_next + #define dir_iter_close nob_dir_iter_close + #define dir_iter_getname nob_dir_iter_getname #define return_defer nob_return_defer #define da_append nob_da_append #define da_free nob_da_free From 8edb0540eb63df885e9628bae2c0fb5feee867c3 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 20:07:48 +0800 Subject: [PATCH 3/8] Fix definition --- nob.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/nob.h b/nob.h index d9721cc..17cdfc8 100644 --- a/nob.h +++ b/nob.h @@ -235,12 +235,6 @@ NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t siz NOBDEF Nob_File_Type nob_get_file_type(const char *path); NOBDEF bool nob_delete_file(const char *path); -NOBDEF int nob_is_dir_empty(const char *path); -NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path); -NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter); -NOBDEF bool nob_dir_iter_close(Nob_Dir_Iter **iter); -NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter); - #define nob_return_defer(value) do { result = (value); goto defer; } while(0) // Initial capacity of a dynamic array @@ -766,6 +760,18 @@ NOBDEF char *nob_win32_error_message(DWORD err); #endif // NOB_H_ +typedef struct { + const char *path; + DIR *dir; + struct dirent *current; +} Nob_Dir_Iter; + +NOBDEF int nob_is_dir_empty(const char *path); +NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path); +NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter); +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter **iter); +NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter); + #ifdef NOB_IMPLEMENTATION // This is like nob_proc_wait() but waits asynchronously. Depending on the platform ms means different thing. From 72b0eb1cefccb258bc2706ce0527434201be6cd2 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 21:32:35 +0800 Subject: [PATCH 4/8] Add recursive directory deleter New APIs in `nob.h`: - `nob_delete_directory()`: deletes empty directory - `nob_delete_directory_recursively()`: deletes directory recursively One internal function: - `nob__delete_directory_recursively()` --- nob.h | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/nob.h b/nob.h index 17cdfc8..5e44eba 100644 --- a/nob.h +++ b/nob.h @@ -234,6 +234,9 @@ 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); +NOBDEF bool nob_delete_directory(const char *path); +static bool nob__delete_directory_recursively(const char *path); +NOBDEF bool nob_delete_directory_recursively(const char *path); #define nob_return_defer(value) do { result = (value); goto defer; } while(0) @@ -1656,6 +1659,104 @@ NOBDEF bool nob_delete_file(const char *path) #endif // _WIN32 } +NOBDEF bool nob_delete_directory(const char *path) { + nob_log(NOB_INFO, "deleting directory %s", path); + if (nob_get_file_type(path) != NOB_FILE_DIRECTORY) { + nob_log(NOB_ERROR, "Not a directory %s", path); + return false; + } +#ifdef _WIN32 + if (!RemoveDirectoryA(path)) return false; + return true; +#else + if (remove(path) < 0) return false; + return true; +#endif // _WIN32 +} + +static bool nob__delete_directory_recursively(const char *path) { + bool result = true; + Nob_String_Builder path_sb = {0}; + size_t temp_checkpoint = nob_temp_save(); + + Nob_File_Type type = nob_get_file_type(path); + if (type < 0) return false; + + switch (type) { + case NOB_FILE_DIRECTORY: { + nob_log(NOB_INFO, "deleting directory %s recursively", path); + + Nob_Dir_Iter *iter = malloc(sizeof(Nob_Dir_Iter)); + if (iter == NULL) { + nob_log(NOB_ERROR, "Could not allocate memory"); + free(iter); + nob_return_defer(false); + } + if (!nob_dir_iter_open(iter, path)) { + nob_log(NOB_ERROR, "Could not open directory %s", path); + free(iter); + nob_return_defer(false); + } + + while (nob_dir_iter_next(iter)) { + char *dname = nob_dir_iter_getname(iter); + if (dname == NULL) { + nob_log(NOB_ERROR, "Could not get directory's name"); + nob_dir_iter_close(&iter); + nob_return_defer(false); + } + + if (strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) + continue; + + path_sb.count = 0; + nob_sb_append_cstr(&path_sb, path); + nob_sb_append_cstr(&path_sb, "/"); + nob_sb_append_cstr(&path_sb, dname); + nob_sb_append_null(&path_sb); + + if (!nob__delete_directory_recursively(path_sb.items)) + nob_return_defer(false); + } + + if (!nob_delete_directory(path)) { + nob_log(NOB_ERROR, "Could not delete directory %s", path); + nob_return_defer(false); + } + } break; + case NOB_FILE_REGULAR: + case NOB_FILE_SYMLINK: { + nob_return_defer(nob_delete_file(path)); + } break; + case NOB_FILE_OTHER: { + nob_log(NOB_ERROR, "Unsupported type of file %s", path); + nob_return_defer(false); + } break; + + default: NOB_UNREACHABLE("nob__delete_directory_recursively"); + } + +defer: + nob_temp_rewind(temp_checkpoint); + nob_da_free(path_sb); + return result; +} + +NOBDEF bool nob_delete_directory_recursively(const char *path) { + if (!nob_file_exists(path)) { + nob_log(NOB_WARNING, "No such directory: %s", path); + return false; + } + + Nob_File_Type type = nob_get_file_type(path); + if (type == NOB_FILE_DIRECTORY) { + return nob__delete_directory_recursively(path); + } else { + nob_log(NOB_WARNING, "Not a directory: %s", path); + return false; + } +} + // Returns true, false, and -1. NOBDEF int nob_is_dir_empty(const char *path) { DIR *dir = opendir(path); From cf77bbc0d9bfa04e67784cbd75947547b4658d51 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 23:15:44 +0800 Subject: [PATCH 5/8] Change allocation method to stack allocation --- nob.c | 1 + nob.h | 20 +++++++++----------- tests/dir_iter.c | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 tests/dir_iter.c diff --git a/nob.c b/nob.c index a580562..97e0842 100644 --- a/nob.c +++ b/nob.c @@ -24,6 +24,7 @@ const char *test_names[] = { "sb_appendf", "da_foreach", "temp_aligned_alloc", + "dir_iter" }; #define test_names_count ARRAY_LEN(test_names) diff --git a/nob.h b/nob.h index 17cdfc8..ea61d98 100644 --- a/nob.h +++ b/nob.h @@ -769,8 +769,8 @@ typedef struct { NOBDEF int nob_is_dir_empty(const char *path); NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path); NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter); -NOBDEF void nob_dir_iter_close(Nob_Dir_Iter **iter); -NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter); +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter *iter); +NOBDEF const char *nob_dir_iter_getname(Nob_Dir_Iter iter); #ifdef NOB_IMPLEMENTATION @@ -1716,17 +1716,14 @@ NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter) { return false; } -NOBDEF void nob_dir_iter_close(Nob_Dir_Iter **iter) { - if (iter && *iter) { - if ((*iter)->dir) - closedir((*iter)->dir); - NOB_FREE(*iter); - *iter = NULL; - } +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter *iter) { + if (!iter) return; + if (iter->dir) + closedir(iter->dir); } -NOBDEF char *nob_dir_iter_getname(Nob_Dir_Iter *iter) { - return iter->current ? iter->current->d_name : NULL; +NOBDEF const char *nob_dir_iter_getname(Nob_Dir_Iter iter) { + return iter.current ? iter.current->d_name : NULL; } NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) @@ -2313,6 +2310,7 @@ NOBDEF int closedir(DIR *dirp) #define get_file_type nob_get_file_type #define delete_file nob_delete_file #define is_dir_empty nob_is_dir_empty + #define Dir_Iter Nob_Dir_Iter #define dir_iter_open nob_dir_iter_open #define dir_iter_next nob_dir_iter_next #define dir_iter_close nob_dir_iter_close diff --git a/tests/dir_iter.c b/tests/dir_iter.c new file mode 100644 index 0000000..181ed99 --- /dev/null +++ b/tests/dir_iter.c @@ -0,0 +1,22 @@ +#define NOB_IMPLEMENTATION +#define NOB_STRIP_PREFIX +#include "nob.h" + +int main(void) { + Dir_Iter iter = {0}; + if (!dir_iter_open(&iter, ".")) + return 1; + + Cmd cmd = {0}; + cmd_append(&cmd, "touch", "file1", "file2", "file3"); + if (!cmd_run(&cmd)) return 1; + + while (dir_iter_next(&iter)) { + const char *dname = dir_iter_getname(iter); + if (dname == NULL) + continue; + nob_log(NOB_INFO, "dname: %s", dname); + } + + return 0; +} From 7c5fce1f04370b97cafd6d36d1d33dc7dcda25da Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 23:19:24 +0800 Subject: [PATCH 6/8] Fix `nob_dir_iter_close()` --- nob.h | 9 ++++----- tests/dir_iter.c | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nob.h b/nob.h index ea61d98..694410c 100644 --- a/nob.h +++ b/nob.h @@ -769,7 +769,7 @@ typedef struct { NOBDEF int nob_is_dir_empty(const char *path); NOBDEF bool nob_dir_iter_open(Nob_Dir_Iter *iter, const char *path); NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter); -NOBDEF void nob_dir_iter_close(Nob_Dir_Iter *iter); +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter iter); NOBDEF const char *nob_dir_iter_getname(Nob_Dir_Iter iter); #ifdef NOB_IMPLEMENTATION @@ -1716,10 +1716,9 @@ NOBDEF bool nob_dir_iter_next(Nob_Dir_Iter *iter) { return false; } -NOBDEF void nob_dir_iter_close(Nob_Dir_Iter *iter) { - if (!iter) return; - if (iter->dir) - closedir(iter->dir); +NOBDEF void nob_dir_iter_close(Nob_Dir_Iter iter) { + if (iter.dir) + closedir(iter.dir); } NOBDEF const char *nob_dir_iter_getname(Nob_Dir_Iter iter) { diff --git a/tests/dir_iter.c b/tests/dir_iter.c index 181ed99..80e35ff 100644 --- a/tests/dir_iter.c +++ b/tests/dir_iter.c @@ -18,5 +18,7 @@ int main(void) { nob_log(NOB_INFO, "dname: %s", dname); } + dir_iter_close(iter); + return 0; } From f03f50610fb4c2b8354f26cd6cd187691195e1b7 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 23:42:57 +0800 Subject: [PATCH 7/8] Change `nob__delete_directory_recursively()` due to 7c5fce1 Change allocation method from dynamic allocation (malloc) to stack allocation. --- nob.c | 3 ++- nob.h | 18 +++++++----------- tests/delete_rec.c | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 tests/delete_rec.c diff --git a/nob.c b/nob.c index 97e0842..ba9fb94 100644 --- a/nob.c +++ b/nob.c @@ -24,7 +24,8 @@ const char *test_names[] = { "sb_appendf", "da_foreach", "temp_aligned_alloc", - "dir_iter" + "dir_iter", + "delete_rec" }; #define test_names_count ARRAY_LEN(test_names) diff --git a/nob.h b/nob.h index a12c807..203c16a 100644 --- a/nob.h +++ b/nob.h @@ -1686,23 +1686,17 @@ static bool nob__delete_directory_recursively(const char *path) { case NOB_FILE_DIRECTORY: { nob_log(NOB_INFO, "deleting directory %s recursively", path); - Nob_Dir_Iter *iter = malloc(sizeof(Nob_Dir_Iter)); - if (iter == NULL) { - nob_log(NOB_ERROR, "Could not allocate memory"); - free(iter); - nob_return_defer(false); - } - if (!nob_dir_iter_open(iter, path)) { + Nob_Dir_Iter iter = {0}; + if (!nob_dir_iter_open(&iter, path)) { nob_log(NOB_ERROR, "Could not open directory %s", path); - free(iter); nob_return_defer(false); } - while (nob_dir_iter_next(iter)) { - char *dname = nob_dir_iter_getname(iter); + while (nob_dir_iter_next(&iter)) { + const char *dname = nob_dir_iter_getname(iter); if (dname == NULL) { nob_log(NOB_ERROR, "Could not get directory's name"); - nob_dir_iter_close(&iter); + nob_dir_iter_close(iter); nob_return_defer(false); } @@ -2409,6 +2403,8 @@ NOBDEF int closedir(DIR *dirp) #define write_entire_file nob_write_entire_file #define get_file_type nob_get_file_type #define delete_file nob_delete_file + #define delete_directory nob_delete_directory + #define delete_directory_recursively nob_delete_directory_recursively #define is_dir_empty nob_is_dir_empty #define Dir_Iter Nob_Dir_Iter #define dir_iter_open nob_dir_iter_open diff --git a/tests/delete_rec.c b/tests/delete_rec.c new file mode 100644 index 0000000..7d24327 --- /dev/null +++ b/tests/delete_rec.c @@ -0,0 +1,21 @@ +#define NOB_IMPLEMENTATION +#define NOB_STRIP_PREFIX +#include "nob.h" + +int main(void) { + Cmd cmd = {0}; + bool result; + + mkdir_if_not_exists("dir"); + cmd_append(&cmd, "touch", "dir/file1", "dir/file2", "dir/file3"); + if (!cmd_run(&cmd)) return 1; + mkdir_if_not_exists("dir/subdir1"); + mkdir_if_not_exists("dir/subdir2"); + mkdir_if_not_exists("dir/subdir3"); + cmd_append(&cmd, "touch", "dir/subdir1/file", "dir/subdir2/file", "dir/subdir3/file"); + if (!cmd_run(&cmd)) return 1; + + result = !delete_directory_recursively("./dir"); + + return result; +} From c16f899cfb9819e9b12f80a9b166028155867145 Mon Sep 17 00:00:00 2001 From: rovol Date: Mon, 6 Oct 2025 23:57:10 +0800 Subject: [PATCH 8/8] Fix `nob__delete_directory_recursively()` From `nob_da_free(path_sb)` to `nob_sb_free(path_sb)`. --- nob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nob.h b/nob.h index 203c16a..37fe398 100644 --- a/nob.h +++ b/nob.h @@ -1732,7 +1732,7 @@ static bool nob__delete_directory_recursively(const char *path) { defer: nob_temp_rewind(temp_checkpoint); - nob_da_free(path_sb); + nob_sb_free(path_sb); return result; }