123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- /*
- Path handling utilities.
- */
- String remove_extension_from_path(String const &s) {
- for (isize i = s.len-1; i >= 0; i--) {
- if (s[i] == '.') {
- return substring(s, 0, i);
- }
- }
- return s;
- }
- String remove_directory_from_path(String const &s) {
- isize len = 0;
- for (isize i = s.len-1; i >= 0; i--) {
- if (s[i] == '/' ||
- s[i] == '\\') {
- break;
- }
- len += 1;
- }
- return substring(s, s.len-len, s.len);
- }
- bool path_is_directory(String path);
- String directory_from_path(String const &s) {
- if (path_is_directory(s)) {
- return s;
- }
- isize i = s.len-1;
- for (; i >= 0; i--) {
- if (s[i] == '/' ||
- s[i] == '\\') {
- break;
- }
- }
- if (i >= 0) {
- return substring(s, 0, i);
- }
- return substring(s, 0, 0);
- }
- #if defined(GB_SYSTEM_WINDOWS)
- bool path_is_directory(String path) {
- gbAllocator a = heap_allocator();
- String16 wstr = string_to_string16(a, path);
- defer (gb_free(a, wstr.text));
- i32 attribs = GetFileAttributesW(wstr.text);
- if (attribs < 0) return false;
- return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
- }
- #else
- bool path_is_directory(String path) {
- gbAllocator a = heap_allocator();
- char *copy = cast(char *)copy_string(a, path).text;
- defer (gb_free(a, copy));
- struct stat s;
- if (stat(copy, &s) == 0) {
- return (s.st_mode & S_IFDIR) != 0;
- }
- return false;
- }
- #endif
- String path_to_full_path(gbAllocator a, String path) {
- gbAllocator ha = heap_allocator();
- char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
- defer (gb_free(ha, path_c));
- char *fullpath = gb_path_get_full_name(a, path_c);
- String res = string_trim_whitespace(make_string_c(fullpath));
- #if defined(GB_SYSTEM_WINDOWS)
- for (isize i = 0; i < res.len; i++) {
- if (res.text[i] == '\\') {
- res.text[i] = '/';
- }
- }
- #endif
- return copy_string(a, res);
- }
- struct Path {
- String basename;
- String name;
- String ext;
- };
- // NOTE(Jeroen): Naively turns a Path into a string.
- String path_to_string(gbAllocator a, Path path) {
- if (path.basename.len + path.name.len + path.ext.len == 0) {
- return make_string(nullptr, 0);
- }
- isize len = path.basename.len + 1 + path.name.len + 1;
- if (path.ext.len > 0) {
- len += path.ext.len + 1;
- }
- u8 *str = gb_alloc_array(a, u8, len);
- isize i = 0;
- gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
- gb_memmove(str+i, "/", 1); i += 1;
- gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len;
- if (path.ext.len > 0) {
- gb_memmove(str+i, ".", 1); i += 1;
- gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len;
- }
- str[i] = 0;
- String res = make_string(str, i);
- res = string_trim_whitespace(res);
- return res;
- }
- // NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
- String path_to_full_path(gbAllocator a, Path path) {
- String temp = path_to_string(heap_allocator(), path);
- defer (gb_free(heap_allocator(), temp.text));
- return path_to_full_path(a, temp);
- }
- // NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
- // and then breaks it into its components to make a Path.
- Path path_from_string(gbAllocator a, String const &path) {
- Path res = {};
- if (path.len == 0) return res;
- String fullpath = path_to_full_path(a, path);
- defer (gb_free(heap_allocator(), fullpath.text));
- res.basename = directory_from_path(fullpath);
- res.basename = copy_string(a, res.basename);
- if (path_is_directory(fullpath)) {
- // It's a directory. We don't need to tinker with the name and extension.
- // It could have a superfluous trailing `/`. Remove it if so.
- if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
- res.basename.len--;
- }
- return res;
- }
- isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
- res.name = substring(fullpath, name_start, fullpath.len);
- res.name = remove_extension_from_path(res.name);
- res.name = copy_string(a, res.name);
- res.ext = path_extension(fullpath, false); // false says not to include the dot.
- res.ext = copy_string(a, res.ext);
- return res;
- }
- // NOTE(Jeroen): Takes a path String and returns the last path element.
- String last_path_element(String const &path) {
- isize count = 0;
- u8 * start = (u8 *)(&path.text[path.len - 1]);
- for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
- count++;
- start--;
- }
- if (count > 0) {
- start++; // Advance past the `/` and return the substring.
- String res = make_string(start, count);
- return res;
- }
- // Must be a root path like `/` or `C:/`, return empty String.
- return STR_LIT("");
- }
- bool path_is_directory(Path path) {
- String path_string = path_to_full_path(heap_allocator(), path);
- defer (gb_free(heap_allocator(), path_string.text));
- return path_is_directory(path_string);
- }
- struct FileInfo {
- String name;
- String fullpath;
- i64 size;
- bool is_dir;
- };
- enum ReadDirectoryError {
- ReadDirectory_None,
- ReadDirectory_InvalidPath,
- ReadDirectory_NotExists,
- ReadDirectory_Permission,
- ReadDirectory_NotDir,
- ReadDirectory_Empty,
- ReadDirectory_Unknown,
- ReadDirectory_COUNT,
- };
- i64 get_file_size(String path) {
- char *c_str = alloc_cstring(heap_allocator(), path);
- defer (gb_free(heap_allocator(), c_str));
- gbFile f = {};
- gbFileError err = gb_file_open(&f, c_str);
- defer (gb_file_close(&f));
- if (err != gbFileError_None) {
- return -1;
- }
- return gb_file_size(&f);
- }
- #if defined(GB_SYSTEM_WINDOWS)
- ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
- GB_ASSERT(fi != nullptr);
- gbAllocator a = heap_allocator();
- while (path.len > 0) {
- Rune end = path[path.len-1];
- if (end == '/') {
- path.len -= 1;
- } else if (end == '\\') {
- path.len -= 1;
- } else {
- break;
- }
- }
- if (path.len == 0) {
- return ReadDirectory_InvalidPath;
- }
- {
- char *c_str = alloc_cstring(a, path);
- defer (gb_free(a, c_str));
- gbFile f = {};
- gbFileError file_err = gb_file_open(&f, c_str);
- defer (gb_file_close(&f));
- switch (file_err) {
- case gbFileError_Invalid: return ReadDirectory_InvalidPath;
- case gbFileError_NotExists: return ReadDirectory_NotExists;
- // case gbFileError_Permission: return ReadDirectory_Permission;
- }
- }
- if (!path_is_directory(path)) {
- return ReadDirectory_NotDir;
- }
- char *new_path = gb_alloc_array(a, char, path.len+3);
- defer (gb_free(a, new_path));
- gb_memmove(new_path, path.text, path.len);
- gb_memmove(new_path+path.len, "/*", 2);
- new_path[path.len+2] = 0;
- String np = make_string(cast(u8 *)new_path, path.len+2);
- String16 wstr = string_to_string16(a, np);
- defer (gb_free(a, wstr.text));
- WIN32_FIND_DATAW file_data = {};
- HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
- if (find_file == INVALID_HANDLE_VALUE) {
- return ReadDirectory_Unknown;
- }
- defer (FindClose(find_file));
- array_init(fi, a, 0, 100);
- do {
- wchar_t *filename_w = file_data.cFileName;
- i64 size = cast(i64)file_data.nFileSizeLow;
- size |= (cast(i64)file_data.nFileSizeHigh) << 32;
- String name = string16_to_string(a, make_string16_c(filename_w));
- if (name == "." || name == "..") {
- gb_free(a, name.text);
- continue;
- }
- String filepath = {};
- filepath.len = path.len+1+name.len;
- filepath.text = gb_alloc_array(a, u8, filepath.len+1);
- defer (gb_free(a, filepath.text));
- gb_memmove(filepath.text, path.text, path.len);
- gb_memmove(filepath.text+path.len, "/", 1);
- gb_memmove(filepath.text+path.len+1, name.text, name.len);
- FileInfo info = {};
- info.name = name;
- info.fullpath = path_to_full_path(a, filepath);
- info.size = size;
- info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
- array_add(fi, info);
- } while (FindNextFileW(find_file, &file_data));
- if (fi->count == 0) {
- return ReadDirectory_Empty;
- }
- return ReadDirectory_None;
- }
- #elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
- #include <dirent.h>
- ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
- GB_ASSERT(fi != nullptr);
- gbAllocator a = heap_allocator();
- char *c_path = alloc_cstring(a, path);
- defer (gb_free(a, c_path));
- DIR *dir = opendir(c_path);
- if (!dir) {
- switch (errno) {
- case ENOENT:
- return ReadDirectory_NotExists;
- case EACCES:
- return ReadDirectory_Permission;
- case ENOTDIR:
- return ReadDirectory_NotDir;
- default:
- // ENOMEM: out of memory
- // EMFILE: per-process limit on open fds reached
- // ENFILE: system-wide limit on total open files reached
- return ReadDirectory_Unknown;
- }
- GB_PANIC("unreachable");
- }
- array_init(fi, a, 0, 100);
- for (;;) {
- struct dirent *entry = readdir(dir);
- if (entry == nullptr) {
- break;
- }
- String name = make_string_c(entry->d_name);
- if (name == "." || name == "..") {
- continue;
- }
- String filepath = {};
- filepath.len = path.len+1+name.len;
- filepath.text = gb_alloc_array(a, u8, filepath.len+1);
- defer (gb_free(a, filepath.text));
- gb_memmove(filepath.text, path.text, path.len);
- gb_memmove(filepath.text+path.len, "/", 1);
- gb_memmove(filepath.text+path.len+1, name.text, name.len);
- filepath.text[filepath.len] = 0;
- struct stat dir_stat = {};
- if (stat((char *)filepath.text, &dir_stat)) {
- continue;
- }
- if (S_ISDIR(dir_stat.st_mode)) {
- continue;
- }
- i64 size = dir_stat.st_size;
- FileInfo info = {};
- info.name = name;
- info.fullpath = path_to_full_path(a, filepath);
- info.size = size;
- array_add(fi, info);
- }
- if (fi->count == 0) {
- return ReadDirectory_Empty;
- }
- return ReadDirectory_None;
- }
- #else
- #error Implement read_directory
- #endif
|