2 ------------------------------------------------------------------------------
3 Licensing information can be found at the end of the file.
4 ------------------------------------------------------------------------------
8 To create implementation (the function definitions)
9 #define CUTE_FILES_IMPLEMENTATION
10 in *one* C/CPP file (translation unit) that includes this file
13 Utility header for traversing directories to apply a function on each found file.
14 Recursively finds sub-directories. Can also be used to iterate over files in a
15 folder manually. All operations done in a cross-platform manner (thx posix!).
17 This header does no dynamic memory allocation, and performs internally safe string
18 copies as necessary. Strings for paths, file names and file extensions are all
19 capped, and intended to use primarily the C run-time stack memory. Feel free to
20 modify the defines in this file to adjust string size limitations.
22 Read the header for specifics on each function.
24 Here's an example to print all files in a folder:
26 cf_dir_open(&dir, "a");
31 cf_read_file(&dir, &file);
32 printf("%s\n", file.name);
39 #if !defined(CUTE_FILES_H)
41 #define CUTE_FILES_WINDOWS 1
42 #define CUTE_FILES_MAC 2
43 #define CUTE_FILES_UNIX 3
46 #define CUTE_FILES_PLATFORM CUTE_FILES_WINDOWS
47 #if !defined(_CRT_SECURE_NO_WARNINGS)
48 #define _CRT_SECURE_NO_WARNINGS
50 #elif defined(__APPLE__)
51 #define CUTE_FILES_PLATFORM CUTE_FILES_MAC
53 #define CUTE_FILES_PLATFORM CUTE_FILES_UNIX
56 #include <string.h> // strerror, strncpy
58 // change to 0 to compile out any debug checks
59 #define CUTE_FILES_DEBUG_CHECKS 1
61 #if CUTE_FILES_DEBUG_CHECKS
63 #include <stdio.h> // printf
64 #include <assert.h> // assert
66 #define CUTE_FILES_ASSERT assert
70 #define CUTE_FILES_ASSERT(...)
72 #endif // CUTE_FILES_DEBUG_CHECKS
74 #define CUTE_FILES_MAX_PATH 1024
75 #define CUTE_FILES_MAX_FILENAME 256
76 #define CUTE_FILES_MAX_EXT 32
81 typedef struct cf_file_t cf_file_t
;
82 typedef struct cf_dir_t cf_dir_t
;
83 typedef struct cf_time_t cf_time_t
;
84 typedef void (cf_callback_t
)(cf_file_t
* file
, void* udata
);
86 // Stores the file extension in cf_file_t::ext, and returns a pointer to
88 const char* cf_get_ext(cf_file_t
* file
);
90 // Applies a function (cb) to all files in a directory. Will recursively visit
91 // all subdirectories. Useful for asset management, file searching, indexing, etc.
92 void cf_traverse(const char* path
, cf_callback_t
* cb
, void* udata
);
94 // Fills out a cf_file_t struct with file information. Does not actually open the
95 // file contents, and instead performs more lightweight OS-specific calls.
96 int cf_read_file(cf_dir_t
* dir
, cf_file_t
* file
);
98 // Once a cf_dir_t is opened, this function can be used to grab another file
99 // from the operating system.
100 void cf_dir_next(cf_dir_t
* dir
);
102 // Performs lightweight OS-specific call to close internal handle.
103 void cf_dir_close(cf_dir_t
* dir
);
105 // Performs lightweight OS-specific call to open a file handle on a directory.
106 int cf_dir_open(cf_dir_t
* dir
, const char* path
);
108 // Compares file last write times. -1 if file at path_a was modified earlier than path_b.
109 // 0 if they are equal. 1 if file at path_b was modified earlier than path_a.
110 int cf_compare_file_times_by_path(const char* path_a
, const char* path_b
);
112 // Retrieves time file was last modified, returns 0 upon failure
113 int cf_get_file_time(const char* path
, cf_time_t
* time
);
115 // Compares file last write times. -1 if time_a was modified earlier than path_b.
116 // 0 if they are equal. 1 if time_b was modified earlier than path_a.
117 int cf_compare_file_times(cf_time_t
* time_a
, cf_time_t
* time_b
);
119 // Returns 1 of file exists, otherwise returns 0.
120 int cf_file_exists(const char* path
);
122 // Returns 1 if the file's extension matches the string in ext
123 // Returns 0 otherwise
124 int cf_match_ext(cf_file_t
* file
, const char* ext
);
126 // Prints detected errors to stdout
127 void cf_do_unit_tests();
129 #if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
131 #if !defined _CRT_SECURE_NO_WARNINGS
132 #define _CRT_SECURE_NO_WARNINGS
138 char path
[CUTE_FILES_MAX_PATH
];
139 char name
[CUTE_FILES_MAX_FILENAME
];
140 char ext
[CUTE_FILES_MAX_EXT
];
148 char path
[CUTE_FILES_MAX_PATH
];
151 WIN32_FIND_DATAA fdata
;
159 #elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
161 #include <sys/stat.h>
168 char path
[CUTE_FILES_MAX_PATH
];
169 char name
[CUTE_FILES_MAX_FILENAME
];
170 char ext
[CUTE_FILES_MAX_EXT
];
179 char path
[CUTE_FILES_MAX_PATH
];
182 struct dirent
* entry
;
195 #ifdef CUTE_FILES_IMPLEMENTATION
196 #ifndef CUTE_FILES_IMPLEMENTATION_ONCE
197 #define CUTE_FILES_IMPLEMENTATION_ONCE
199 #define cf_safe_strcpy(dst, src, n, max) cf_safe_strcpy_internal(dst, src, n, max, __FILE__, __LINE__)
200 static int cf_safe_strcpy_internal(char* dst
, const char* src
, int n
, int max
, const char* file
, int line
)
203 const char* original
= src
;
209 if (!CUTE_FILES_DEBUG_CHECKS
) break;
210 printf("ERROR: String \"%s\" too long to copy on line %d in file %s (max length of %d).\n"
215 CUTE_FILES_ASSERT(0);
226 const char* cf_get_ext(cf_file_t
* file
)
228 char* name
= file
->name
;
230 while (*name
++) if (*name
== '.') period
= name
;
231 if (period
) cf_safe_strcpy(file
->ext
, period
, 0, CUTE_FILES_MAX_EXT
);
232 else file
->ext
[0] = 0;
236 void cf_traverse(const char* path
, cf_callback_t
* cb
, void* udata
)
239 cf_dir_open(&dir
, path
);
244 int res
= cf_read_file(&dir
, &file
);
251 if (file
.is_dir
&& file
.name
[0] != '.')
253 char path2
[CUTE_FILES_MAX_PATH
];
254 int n
= cf_safe_strcpy(path2
, path
, 0, CUTE_FILES_MAX_PATH
);
255 n
= cf_safe_strcpy(path2
, "/", n
- 1, CUTE_FILES_MAX_PATH
);
256 cf_safe_strcpy(path2
, file
.name
, n
-1, CUTE_FILES_MAX_PATH
);
257 cf_traverse(path2
, cb
, udata
);
260 if (file
.is_reg
) cb(&file
, udata
);
267 int cf_match_ext(cf_file_t
* file
, const char* ext
)
269 return !strcmp(file
->ext
, ext
);
272 #if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
274 int cf_read_file(cf_dir_t
* dir
, cf_file_t
* file
)
276 CUTE_FILES_ASSERT(dir
->handle
!= INVALID_HANDLE_VALUE
);
279 char* fpath
= file
->path
;
280 char* dpath
= dir
->path
;
282 n
= cf_safe_strcpy(fpath
, dpath
, 0, CUTE_FILES_MAX_PATH
);
283 n
= cf_safe_strcpy(fpath
, "/", n
- 1, CUTE_FILES_MAX_PATH
);
285 char* dname
= dir
->fdata
.cFileName
;
286 char* fname
= file
->name
;
288 cf_safe_strcpy(fname
, dname
, 0, CUTE_FILES_MAX_FILENAME
);
289 cf_safe_strcpy(fpath
, fname
, n
- 1, CUTE_FILES_MAX_PATH
);
291 size_t max_dword
= MAXDWORD
;
292 file
->size
= ((size_t)dir
->fdata
.nFileSizeHigh
* (max_dword
+ 1)) + (size_t)dir
->fdata
.nFileSizeLow
;
295 file
->is_dir
= !!(dir
->fdata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
296 file
->is_reg
= !!(dir
->fdata
.dwFileAttributes
& FILE_ATTRIBUTE_NORMAL
) ||
297 !(dir
->fdata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
302 void cf_dir_next(cf_dir_t
* dir
)
304 CUTE_FILES_ASSERT(dir
->has_next
);
306 if (!FindNextFileA(dir
->handle
, &dir
->fdata
))
309 DWORD err
= GetLastError();
310 CUTE_FILES_ASSERT(err
== ERROR_SUCCESS
|| err
== ERROR_NO_MORE_FILES
);
314 void cf_dir_close(cf_dir_t
* dir
)
318 if (dir
->handle
!= INVALID_HANDLE_VALUE
) FindClose(dir
->handle
);
321 int cf_dir_open(cf_dir_t
* dir
, const char* path
)
323 int n
= cf_safe_strcpy(dir
->path
, path
, 0, CUTE_FILES_MAX_PATH
);
324 n
= cf_safe_strcpy(dir
->path
, "\\*", n
- 1, CUTE_FILES_MAX_PATH
);
325 dir
->handle
= FindFirstFileA(dir
->path
, &dir
->fdata
);
326 dir
->path
[n
- 3] = 0;
328 if (dir
->handle
== INVALID_HANDLE_VALUE
)
330 printf("ERROR: Failed to open directory (%s): %s.\n", path
, strerror(errno
));
332 CUTE_FILES_ASSERT(0);
341 int cf_compare_file_times_by_path(const char* path_a
, const char* path_b
)
343 FILETIME time_a
= { 0 };
344 FILETIME time_b
= { 0 };
345 WIN32_FILE_ATTRIBUTE_DATA data
;
347 if (GetFileAttributesExA(path_a
, GetFileExInfoStandard
, &data
)) time_a
= data
.ftLastWriteTime
;
348 if (GetFileAttributesExA(path_b
, GetFileExInfoStandard
, &data
)) time_b
= data
.ftLastWriteTime
;
349 return CompareFileTime(&time_a
, &time_b
);
352 int cf_get_file_time(const char* path
, cf_time_t
* time
)
354 FILETIME initialized_to_zero
= { 0 };
355 time
->time
= initialized_to_zero
;
356 WIN32_FILE_ATTRIBUTE_DATA data
;
357 if (GetFileAttributesExA(path
, GetFileExInfoStandard
, &data
))
359 time
->time
= data
.ftLastWriteTime
;
365 int cf_compare_file_times(cf_time_t
* time_a
, cf_time_t
* time_b
)
367 return CompareFileTime(&time_a
->time
, &time_b
->time
);
370 int cf_file_exists(const char* path
)
372 WIN32_FILE_ATTRIBUTE_DATA unused
;
373 return GetFileAttributesExA(path
, GetFileExInfoStandard
, &unused
);
376 #elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
378 int cf_read_file(cf_dir_t
* dir
, cf_file_t
* file
)
380 CUTE_FILES_ASSERT(dir
->entry
);
383 char* fpath
= file
->path
;
384 char* dpath
= dir
->path
;
386 n
= cf_safe_strcpy(fpath
, dpath
, 0, CUTE_FILES_MAX_PATH
);
387 n
= cf_safe_strcpy(fpath
, "/", n
- 1, CUTE_FILES_MAX_PATH
);
389 char* dname
= dir
->entry
->d_name
;
390 char* fname
= file
->name
;
392 cf_safe_strcpy(fname
, dname
, 0, CUTE_FILES_MAX_FILENAME
);
393 cf_safe_strcpy(fpath
, fname
, n
- 1, CUTE_FILES_MAX_PATH
);
395 if (stat(file
->path
, &file
->info
))
398 file
->size
= file
->info
.st_size
;
401 file
->is_dir
= S_ISDIR(file
->info
.st_mode
);
402 file
->is_reg
= S_ISREG(file
->info
.st_mode
);
407 void cf_dir_next(cf_dir_t
* dir
)
409 CUTE_FILES_ASSERT(dir
->has_next
);
410 dir
->entry
= readdir(dir
->dir
);
411 dir
->has_next
= dir
->entry
? 1 : 0;
414 void cf_dir_close(cf_dir_t
* dir
)
417 if (dir
->dir
) closedir(dir
->dir
);
423 int cf_dir_open(cf_dir_t
* dir
, const char* path
)
425 cf_safe_strcpy(dir
->path
, path
, 0, CUTE_FILES_MAX_PATH
);
426 dir
->dir
= opendir(path
);
430 printf("ERROR: Failed to open directory (%s): %s.\n", path
, strerror(errno
));
432 CUTE_FILES_ASSERT(0);
437 dir
->entry
= readdir(dir
->dir
);
438 if (!dir
->dir
) dir
->has_next
= 0;
443 // Warning : untested code! (let me know if it breaks)
444 int cf_compare_file_times_by_path(const char* path_a
, const char* path_b
)
449 if (stat(path_a
, &info
)) return 0;
450 time_a
= info
.st_mtime
;
451 if (stat(path_b
, &info
)) return 0;
452 time_b
= info
.st_mtime
;
453 return (int)difftime(time_a
, time_b
);
456 // Warning : untested code! (let me know if it breaks)
457 int cf_get_file_time(const char* path
, cf_time_t
* time
)
460 if (stat(path
, &info
)) return 0;
461 time
->time
= info
.st_mtime
;
465 // Warning : untested code! (let me know if it breaks)
466 int cf_compare_file_times(cf_time_t
* time_a
, cf_time_t
* time_b
)
468 return (int)difftime(time_a
->time
, time_b
->time
);
471 // Warning : untested code! (let me know if it breaks)
472 int cf_file_exists(const char* path
)
474 return access(path
, F_OK
) != -1;
477 #endif // CUTE_FILES_PLATFORM
479 #endif // CUTE_FILES_IMPLEMENTATION_ONCE
480 #endif // CUTE_FILES_IMPLEMENTATION
483 ------------------------------------------------------------------------------
484 This software is available under 2 licenses - you may choose the one you like.
485 ------------------------------------------------------------------------------
486 ALTERNATIVE A - zlib license
487 Copyright (c) 2017 Randy Gaul http://www.randygaul.net
488 This software is provided 'as-is', without any express or implied warranty.
489 In no event will the authors be held liable for any damages arising from
490 the use of this software.
491 Permission is granted to anyone to use this software for any purpose,
492 including commercial applications, and to alter it and redistribute it
493 freely, subject to the following restrictions:
494 1. The origin of this software must not be misrepresented; you must not
495 claim that you wrote the original software. If you use this software
496 in a product, an acknowledgment in the product documentation would be
497 appreciated but is not required.
498 2. Altered source versions must be plainly marked as such, and must not
499 be misrepresented as being the original software.
500 3. This notice may not be removed or altered from any source distribution.
501 ------------------------------------------------------------------------------
502 ALTERNATIVE B - Public Domain (www.unlicense.org)
503 This is free and unencumbered software released into the public domain.
504 Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
505 software, either in source code form or as a compiled binary, for any purpose,
506 commercial or non-commercial, and by any means.
507 In jurisdictions that recognize copyright laws, the author or authors of this
508 software dedicate any and all copyright interest in the software to the public
509 domain. We make this dedication for the benefit of the public at large and to
510 the detriment of our heirs and successors. We intend this dedication to be an
511 overt act of relinquishment in perpetuity of all present and future rights to
512 this software under copyright law.
513 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
514 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
515 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
516 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
517 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
518 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
519 ------------------------------------------------------------------------------