assorted
[vg.git] / dep / randygaul / cute_files.h
diff --git a/dep/randygaul/cute_files.h b/dep/randygaul/cute_files.h
new file mode 100644 (file)
index 0000000..fcd585e
--- /dev/null
@@ -0,0 +1,520 @@
+/*
+       ------------------------------------------------------------------------------
+               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.
+       ------------------------------------------------------------------------------
+*/