--- /dev/null
+/*
+ ------------------------------------------------------------------------------
+ Licensing information can be found at the end of the file.
+ ------------------------------------------------------------------------------
+
+ cute_files.h - v1.0
+
+ To create implementation (the function definitions)
+ #define CUTE_FILES_IMPLEMENTATION
+ in *one* C/CPP file (translation unit) that includes this file
+
+ Summary:
+ Utility header for traversing directories to apply a function on each found file.
+ Recursively finds sub-directories. Can also be used to iterate over files in a
+ folder manually. All operations done in a cross-platform manner (thx posix!).
+
+ This header does no dynamic memory allocation, and performs internally safe string
+ copies as necessary. Strings for paths, file names and file extensions are all
+ capped, and intended to use primarily the C run-time stack memory. Feel free to
+ modify the defines in this file to adjust string size limitations.
+
+ Read the header for specifics on each function.
+
+ Here's an example to print all files in a folder:
+ cf_dir_t dir;
+ cf_dir_open(&dir, "a");
+
+ while (dir.has_next)
+ {
+ cf_file_t file;
+ cf_read_file(&dir, &file);
+ printf("%s\n", file.name);
+ cf_dir_next(&dir);
+ }
+
+ cf_dir_close(&dir);
+*/
+
+#if !defined(CUTE_FILES_H)
+
+#define CUTE_FILES_WINDOWS 1
+#define CUTE_FILES_MAC 2
+#define CUTE_FILES_UNIX 3
+
+#if defined(_WIN32)
+ #define CUTE_FILES_PLATFORM CUTE_FILES_WINDOWS
+ #if !defined(_CRT_SECURE_NO_WARNINGS)
+ #define _CRT_SECURE_NO_WARNINGS
+ #endif
+#elif defined(__APPLE__)
+ #define CUTE_FILES_PLATFORM CUTE_FILES_MAC
+#else
+ #define CUTE_FILES_PLATFORM CUTE_FILES_UNIX
+#endif
+
+#include <string.h> // strerror, strncpy
+
+// change to 0 to compile out any debug checks
+#define CUTE_FILES_DEBUG_CHECKS 1
+
+#if CUTE_FILES_DEBUG_CHECKS
+
+ #include <stdio.h> // printf
+ #include <assert.h> // assert
+ #include <errno.h>
+ #define CUTE_FILES_ASSERT assert
+
+#else
+
+ #define CUTE_FILES_ASSERT(...)
+
+#endif // CUTE_FILES_DEBUG_CHECKS
+
+#define CUTE_FILES_MAX_PATH 1024
+#define CUTE_FILES_MAX_FILENAME 256
+#define CUTE_FILES_MAX_EXT 32
+
+struct cf_file_t;
+struct cf_dir_t;
+struct cf_time_t;
+typedef struct cf_file_t cf_file_t;
+typedef struct cf_dir_t cf_dir_t;
+typedef struct cf_time_t cf_time_t;
+typedef void (cf_callback_t)(cf_file_t* file, void* udata);
+
+// Stores the file extension in cf_file_t::ext, and returns a pointer to
+// cf_file_t::ext
+const char* cf_get_ext(cf_file_t* file);
+
+// Applies a function (cb) to all files in a directory. Will recursively visit
+// all subdirectories. Useful for asset management, file searching, indexing, etc.
+void cf_traverse(const char* path, cf_callback_t* cb, void* udata);
+
+// Fills out a cf_file_t struct with file information. Does not actually open the
+// file contents, and instead performs more lightweight OS-specific calls.
+int cf_read_file(cf_dir_t* dir, cf_file_t* file);
+
+// Once a cf_dir_t is opened, this function can be used to grab another file
+// from the operating system.
+void cf_dir_next(cf_dir_t* dir);
+
+// Performs lightweight OS-specific call to close internal handle.
+void cf_dir_close(cf_dir_t* dir);
+
+// Performs lightweight OS-specific call to open a file handle on a directory.
+int cf_dir_open(cf_dir_t* dir, const char* path);
+
+// Compares file last write times. -1 if file at path_a was modified earlier than path_b.
+// 0 if they are equal. 1 if file at path_b was modified earlier than path_a.
+int cf_compare_file_times_by_path(const char* path_a, const char* path_b);
+
+// Retrieves time file was last modified, returns 0 upon failure
+int cf_get_file_time(const char* path, cf_time_t* time);
+
+// Compares file last write times. -1 if time_a was modified earlier than path_b.
+// 0 if they are equal. 1 if time_b was modified earlier than path_a.
+int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b);
+
+// Returns 1 of file exists, otherwise returns 0.
+int cf_file_exists(const char* path);
+
+// Returns 1 if the file's extension matches the string in ext
+// Returns 0 otherwise
+int cf_match_ext(cf_file_t* file, const char* ext);
+
+// Prints detected errors to stdout
+void cf_do_unit_tests();
+
+#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
+
+#if !defined _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <windows.h>
+
+ struct cf_file_t
+ {
+ char path[CUTE_FILES_MAX_PATH];
+ char name[CUTE_FILES_MAX_FILENAME];
+ char ext[CUTE_FILES_MAX_EXT];
+ int is_dir;
+ int is_reg;
+ size_t size;
+ };
+
+ struct cf_dir_t
+ {
+ char path[CUTE_FILES_MAX_PATH];
+ int has_next;
+ HANDLE handle;
+ WIN32_FIND_DATAA fdata;
+ };
+
+ struct cf_time_t
+ {
+ FILETIME time;
+ };
+
+#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
+
+ #include <sys/stat.h>
+ #include <dirent.h>
+ #include <unistd.h>
+ #include <time.h>
+
+ struct cf_file_t
+ {
+ char path[CUTE_FILES_MAX_PATH];
+ char name[CUTE_FILES_MAX_FILENAME];
+ char ext[CUTE_FILES_MAX_EXT];
+ int is_dir;
+ int is_reg;
+ int size;
+ struct stat info;
+ };
+
+ struct cf_dir_t
+ {
+ char path[CUTE_FILES_MAX_PATH];
+ int has_next;
+ DIR* dir;
+ struct dirent* entry;
+ };
+
+ struct cf_time_t
+ {
+ time_t time;
+ };
+
+#endif
+
+#define CUTE_FILES_H
+#endif
+
+#ifdef CUTE_FILES_IMPLEMENTATION
+#ifndef CUTE_FILES_IMPLEMENTATION_ONCE
+#define CUTE_FILES_IMPLEMENTATION_ONCE
+
+#define cf_safe_strcpy(dst, src, n, max) cf_safe_strcpy_internal(dst, src, n, max, __FILE__, __LINE__)
+static int cf_safe_strcpy_internal(char* dst, const char* src, int n, int max, const char* file, int line)
+{
+ int c;
+ const char* original = src;
+
+ do
+ {
+ if (n >= max)
+ {
+ if (!CUTE_FILES_DEBUG_CHECKS) break;
+ printf("ERROR: String \"%s\" too long to copy on line %d in file %s (max length of %d).\n"
+ , original
+ , line
+ , file
+ , max);
+ CUTE_FILES_ASSERT(0);
+ }
+
+ c = *src++;
+ dst[n] = c;
+ ++n;
+ } while (c);
+
+ return n;
+}
+
+const char* cf_get_ext(cf_file_t* file)
+{
+ char* name = file->name;
+ char* period = NULL;
+ while (*name++) if (*name == '.') period = name;
+ if (period) cf_safe_strcpy(file->ext, period, 0, CUTE_FILES_MAX_EXT);
+ else file->ext[0] = 0;
+ return file->ext;
+}
+
+void cf_traverse(const char* path, cf_callback_t* cb, void* udata)
+{
+ cf_dir_t dir;
+ cf_dir_open(&dir, path);
+
+ while (dir.has_next)
+ {
+ cf_file_t file;
+ int res = cf_read_file(&dir, &file);
+
+ if (res == 0) {
+ cf_dir_next(&dir);
+ continue;
+ }
+
+ if (file.is_dir && file.name[0] != '.')
+ {
+ char path2[CUTE_FILES_MAX_PATH];
+ int n = cf_safe_strcpy(path2, path, 0, CUTE_FILES_MAX_PATH);
+ n = cf_safe_strcpy(path2, "/", n - 1, CUTE_FILES_MAX_PATH);
+ cf_safe_strcpy(path2, file.name, n -1, CUTE_FILES_MAX_PATH);
+ cf_traverse(path2, cb, udata);
+ }
+
+ if (file.is_reg) cb(&file, udata);
+ cf_dir_next(&dir);
+ }
+
+ cf_dir_close(&dir);
+}
+
+int cf_match_ext(cf_file_t* file, const char* ext)
+{
+ return !strcmp(file->ext, ext);
+}
+
+#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
+
+ int cf_read_file(cf_dir_t* dir, cf_file_t* file)
+ {
+ CUTE_FILES_ASSERT(dir->handle != INVALID_HANDLE_VALUE);
+
+ int n = 0;
+ char* fpath = file->path;
+ char* dpath = dir->path;
+
+ n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH);
+ n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH);
+
+ char* dname = dir->fdata.cFileName;
+ char* fname = file->name;
+
+ cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME);
+ cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH);
+
+ size_t max_dword = MAXDWORD;
+ file->size = ((size_t)dir->fdata.nFileSizeHigh * (max_dword + 1)) + (size_t)dir->fdata.nFileSizeLow;
+ cf_get_ext(file);
+
+ file->is_dir = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+ file->is_reg = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+ !(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+
+ return 1;
+ }
+
+ void cf_dir_next(cf_dir_t* dir)
+ {
+ CUTE_FILES_ASSERT(dir->has_next);
+
+ if (!FindNextFileA(dir->handle, &dir->fdata))
+ {
+ dir->has_next = 0;
+ DWORD err = GetLastError();
+ CUTE_FILES_ASSERT(err == ERROR_SUCCESS || err == ERROR_NO_MORE_FILES);
+ }
+ }
+
+ void cf_dir_close(cf_dir_t* dir)
+ {
+ dir->path[0] = 0;
+ dir->has_next = 0;
+ if (dir->handle != INVALID_HANDLE_VALUE) FindClose(dir->handle);
+ }
+
+ int cf_dir_open(cf_dir_t* dir, const char* path)
+ {
+ int n = cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH);
+ n = cf_safe_strcpy(dir->path, "\\*", n - 1, CUTE_FILES_MAX_PATH);
+ dir->handle = FindFirstFileA(dir->path, &dir->fdata);
+ dir->path[n - 3] = 0;
+
+ if (dir->handle == INVALID_HANDLE_VALUE)
+ {
+ printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno));
+ cf_dir_close(dir);
+ CUTE_FILES_ASSERT(0);
+ return 0;
+ }
+
+ dir->has_next = 1;
+
+ return 1;
+ }
+
+ int cf_compare_file_times_by_path(const char* path_a, const char* path_b)
+ {
+ FILETIME time_a = { 0 };
+ FILETIME time_b = { 0 };
+ WIN32_FILE_ATTRIBUTE_DATA data;
+
+ if (GetFileAttributesExA(path_a, GetFileExInfoStandard, &data)) time_a = data.ftLastWriteTime;
+ if (GetFileAttributesExA(path_b, GetFileExInfoStandard, &data)) time_b = data.ftLastWriteTime;
+ return CompareFileTime(&time_a, &time_b);
+ }
+
+ int cf_get_file_time(const char* path, cf_time_t* time)
+ {
+ FILETIME initialized_to_zero = { 0 };
+ time->time = initialized_to_zero;
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ if (GetFileAttributesExA(path, GetFileExInfoStandard, &data))
+ {
+ time->time = data.ftLastWriteTime;
+ return 1;
+ }
+ return 0;
+ }
+
+ int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b)
+ {
+ return CompareFileTime(&time_a->time, &time_b->time);
+ }
+
+ int cf_file_exists(const char* path)
+ {
+ WIN32_FILE_ATTRIBUTE_DATA unused;
+ return GetFileAttributesExA(path, GetFileExInfoStandard, &unused);
+ }
+
+#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
+
+ int cf_read_file(cf_dir_t* dir, cf_file_t* file)
+ {
+ CUTE_FILES_ASSERT(dir->entry);
+
+ int n = 0;
+ char* fpath = file->path;
+ char* dpath = dir->path;
+
+ n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH);
+ n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH);
+
+ char* dname = dir->entry->d_name;
+ char* fname = file->name;
+
+ cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME);
+ cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH);
+
+ if (stat(file->path, &file->info))
+ return 0;
+
+ file->size = file->info.st_size;
+ cf_get_ext(file);
+
+ file->is_dir = S_ISDIR(file->info.st_mode);
+ file->is_reg = S_ISREG(file->info.st_mode);
+
+ return 1;
+ }
+
+ void cf_dir_next(cf_dir_t* dir)
+ {
+ CUTE_FILES_ASSERT(dir->has_next);
+ dir->entry = readdir(dir->dir);
+ dir->has_next = dir->entry ? 1 : 0;
+ }
+
+ void cf_dir_close(cf_dir_t* dir)
+ {
+ dir->path[0] = 0;
+ if (dir->dir) closedir(dir->dir);
+ dir->dir = 0;
+ dir->has_next = 0;
+ dir->entry = 0;
+ }
+
+ int cf_dir_open(cf_dir_t* dir, const char* path)
+ {
+ cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH);
+ dir->dir = opendir(path);
+
+ if (!dir->dir)
+ {
+ printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno));
+ cf_dir_close(dir);
+ CUTE_FILES_ASSERT(0);
+ return 0;
+ }
+
+ dir->has_next = 1;
+ dir->entry = readdir(dir->dir);
+ if (!dir->dir) dir->has_next = 0;
+
+ return 1;
+ }
+
+ // Warning : untested code! (let me know if it breaks)
+ int cf_compare_file_times_by_path(const char* path_a, const char* path_b)
+ {
+ time_t time_a;
+ time_t time_b;
+ struct stat info;
+ if (stat(path_a, &info)) return 0;
+ time_a = info.st_mtime;
+ if (stat(path_b, &info)) return 0;
+ time_b = info.st_mtime;
+ return (int)difftime(time_a, time_b);
+ }
+
+ // Warning : untested code! (let me know if it breaks)
+ int cf_get_file_time(const char* path, cf_time_t* time)
+ {
+ struct stat info;
+ if (stat(path, &info)) return 0;
+ time->time = info.st_mtime;
+ return 1;
+ }
+
+ // Warning : untested code! (let me know if it breaks)
+ int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b)
+ {
+ return (int)difftime(time_a->time, time_b->time);
+ }
+
+ // Warning : untested code! (let me know if it breaks)
+ int cf_file_exists(const char* path)
+ {
+ return access(path, F_OK) != -1;
+ }
+
+#endif // CUTE_FILES_PLATFORM
+
+#endif // CUTE_FILES_IMPLEMENTATION_ONCE
+#endif // CUTE_FILES_IMPLEMENTATION
+
+/*
+ ------------------------------------------------------------------------------
+ This software is available under 2 licenses - you may choose the one you like.
+ ------------------------------------------------------------------------------
+ ALTERNATIVE A - zlib license
+ Copyright (c) 2017 Randy Gaul http://www.randygaul.net
+ This software is provided 'as-is', without any express or implied warranty.
+ In no event will the authors be held liable for any damages arising from
+ the use of this software.
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not
+ be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+ ------------------------------------------------------------------------------
+ ALTERNATIVE B - Public Domain (www.unlicense.org)
+ This is free and unencumbered software released into the public domain.
+ Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+ software, either in source code form or as a compiled binary, for any purpose,
+ commercial or non-commercial, and by any means.
+ In jurisdictions that recognize copyright laws, the author or authors of this
+ software dedicate any and all copyright interest in the software to the public
+ domain. We make this dedication for the benefit of the public at large and to
+ the detriment of our heirs and successors. We intend this dedication to be an
+ overt act of relinquishment in perpetuity of all present and future rights to
+ this software under copyright law.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ ------------------------------------------------------------------------------
+*/