diff options
author | gramanas <anastasis.gramm2@gmail.com> | 2024-05-31 21:00:57 +0300 |
---|---|---|
committer | gramanas <anastasis.gramm2@gmail.com> | 2024-05-31 21:00:57 +0300 |
commit | a5a82709028c475d0ff6cd54f6d07f16375e156e (patch) | |
tree | db1386b3e5af7075ee6e5c45c3a4964cdecdc9d2 /src/b.h | |
parent | 1f6538bcaf379cb3fe257a0053e4e7567f8ddb98 (diff) | |
download | cgame-a5a82709028c475d0ff6cd54f6d07f16375e156e.tar.gz cgame-a5a82709028c475d0ff6cd54f6d07f16375e156e.tar.bz2 cgame-a5a82709028c475d0ff6cd54f6d07f16375e156e.zip |
Allaboard
Diffstat (limited to 'src/b.h')
-rw-r--r-- | src/b.h | 860 |
1 files changed, 860 insertions, 0 deletions
@@ -0,0 +1,860 @@ +// This is an attempt on build library for building C with C akin to nob.h from +// tsoding which is itself based on https://github.com/tsoding/nobuild +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 OR COPYRIGHT HOLDERS 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. + +#ifndef B_H_ +#define B_H_ + +#define B_ASSERT assert +#define B_REALLOC realloc +#define B_FREE free + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> + +#define B_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) +#define B_ARRAY_GET(array, index) \ + (B_ASSERT(index >= 0), B_ASSERT(index < B_ARRAY_LEN(array)), array[index]) + +typedef enum { + B_INFO, + B_WARNING, + B_ERROR, +} B_Log_Level; + +void b_log(B_Log_Level level, const char *fmt, ...); + +// It is an equivalent of shift command from bash. It basically pops a command line +// argument from the beginning. +char *b_shift_args(int *argc, char ***argv); + +typedef struct { + const char **items; + size_t count; + size_t capacity; +} B_File_Paths; + +typedef enum { + B_FILE_REGULAR = 0, + B_FILE_DIRECTORY, + B_FILE_SYMLINK, + B_FILE_OTHER, +} B_File_Type; + +bool b_mkdir_if_not_exists(const char *path); +bool b_copy_file(const char *src_path, const char *dst_path); +bool b_copy_directory_recursively(const char *src_path, const char *dst_path); +bool b_read_entire_dir(const char *parent, B_File_Paths *children); +bool b_write_entire_file(const char *path, const void *data, size_t size); +B_File_Type b_get_file_type(const char *path); + +#define b_return_defer(value) do { result = (value); goto defer; } while(0) + +// Initial capacity of a dynamic array +#define B_DA_INIT_CAP 256 + +// Append an item to a dynamic array +#define b_da_append(da, item) \ + do { \ + if ((da)->count >= (da)->capacity) { \ + (da)->capacity = (da)->capacity == 0 ? B_DA_INIT_CAP : (da)->capacity*2; \ + (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ + } \ + \ + (da)->items[(da)->count++] = (item); \ + } while (0) + +#define b_da_free(da) B_FREE((da).items) + +// Append several items to a dynamic array +#define b_da_append_many(da, new_items, new_items_count) \ + do { \ + if ((da)->count + (new_items_count) > (da)->capacity) { \ + if ((da)->capacity == 0) { \ + (da)->capacity = B_DA_INIT_CAP; \ + } \ + while ((da)->count + (new_items_count) > (da)->capacity) { \ + (da)->capacity *= 2; \ + } \ + (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ + } \ + memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ + (da)->count += (new_items_count); \ + } while (0) + +typedef struct { + char *items; + size_t count; + size_t capacity; +} B_String_Builder; + +bool b_read_entire_file(const char *path, B_String_Builder *sb); + +// Append a sized buffer to a string builder +#define b_sb_append_buf(sb, buf, size) b_da_append_many(sb, buf, size) + +// Append a NULL-terminated string to a string builder +#define b_sb_append_cstr(sb, cstr) \ + do { \ + const char *s = (cstr); \ + size_t n = strlen(s); \ + b_da_append_many(sb, s, n); \ + } while (0) + +// Append a single NULL character at the end of a string builder. So then you can +// use it a NULL-terminated C string +#define b_sb_append_null(sb) b_da_append_many(sb, "", 1) + +// Free the memory allocated by a string builder +#define b_sb_free(sb) B_FREE((sb).items) + +// Process handle +typedef int B_Proc; +#define B_INVALID_PROC (-1) + +typedef struct { + B_Proc *items; + size_t count; + size_t capacity; +} B_Procs; + +bool b_procs_wait(B_Procs procs); + +// Wait until the process has finished +bool b_proc_wait(B_Proc proc); + +// A command - the main workhorse of B. B is all about building commands an running them +typedef struct { + const char **items; + size_t count; + size_t capacity; +} B_Cmd; + +// Render a string representation of a command into a string builder. Keep in mind the the +// string builder is not NULL-terminated by default. Use b_sb_append_null if you plan to +// use it as a C string. +void b_cmd_render(B_Cmd cmd, B_String_Builder *render); + +#define b_cmd_append(cmd, ...) \ + b_da_append_many(cmd, ((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) + +// Free all the memory allocated by command arguments +#define b_cmd_free(cmd) B_FREE(cmd.items) + +// Run command asynchronously +B_Proc b_cmd_run_async(B_Cmd cmd); + +// Run command synchronously +bool b_cmd_run_sync(B_Cmd cmd); + +#ifndef B_TEMP_CAPACITY +#define B_TEMP_CAPACITY (8*1024*1024) +#endif // B_TEMP_CAPACITY +char *b_temp_strdup(const char *cstr); +void *b_temp_alloc(size_t size); +char *b_temp_sprintf(const char *format, ...); +void b_temp_reset(void); +size_t b_temp_save(void); +void b_temp_rewind(size_t checkpoint); + +int is_path1_modified_after_path2(const char *path1, const char *path2); +bool b_rename(const char *old_path, const char *new_path); +int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); +int b_needs_rebuild1(const char *output_path, const char *input_path); +int b_file_exists(const char *file_path); + +// TODO: add MinGW support for Go Rebuild Urself™ Technology +#ifndef B_REBUILD_URSELF +# if _WIN32 +# if defined(__GNUC__) +# define B_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path +# elif defined(__clang__) +# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path +# elif defined(_MSC_VER) +# define B_REBUILD_URSELF(binary_path, source_path) "cl.exe", b_temp_sprintf("/Fe:%s", (binary_path)), source_path +# endif +# else +# if defined(__clang__) +# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path +# else +# define B_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path +# endif +# endif +#endif + +// Go Rebuild Urself™ Technology +// +// How to use it: +// int main(int argc, char** argv) { +// GO_REBUILD_URSELF(argc, argv); +// // actual work +// return 0; +// } +// +// After your added this macro every time you run ./build it will detect +// that you modified its original source code and will try to rebuild itself +// before doing any actual work. So you only need to bootstrap your build system +// once. +// +// The modification is detected by comparing the last modified times of the executable +// and its source code. The same way the make utility usually does it. +// +// The rebuilding is done by using the REBUILD_URSELF macro which you can redefine +// if you need a special way of bootstraping your build system. (which I personally +// do not recommend since the whole idea of build is to keep the process of bootstrapping +// as simple as possible and doing all of the actual work inside of the build) +// +#define B_GO_REBUILD_URSELF(argc, argv) \ + do { \ + const char *source_path = __FILE__; \ + assert(argc >= 1); \ + const char *binary_path = argv[0]; \ + \ + int rebuild_is_needed = b_needs_rebuild(binary_path, &source_path, 1); \ + if (rebuild_is_needed < 0) exit(1); \ + if (rebuild_is_needed) { \ + B_String_Builder sb = {0}; \ + b_sb_append_cstr(&sb, binary_path); \ + b_sb_append_cstr(&sb, ".old"); \ + b_sb_append_null(&sb); \ + \ + if (!b_rename(binary_path, sb.items)) exit(1); \ + B_Cmd rebuild = {0}; \ + b_cmd_append(&rebuild, B_REBUILD_URSELF(binary_path, source_path)); \ + bool rebuild_succeeded = b_cmd_run_sync(rebuild); \ + b_cmd_free(rebuild); \ + if (!rebuild_succeeded) { \ + b_rename(sb.items, binary_path); \ + exit(1); \ + } \ + \ + B_Cmd cmd = {0}; \ + b_da_append_many(&cmd, argv, argc); \ + if (!b_cmd_run_sync(cmd)) exit(1); \ + exit(0); \ + } \ + } while(0) +// The implementation idea is stolen from https://github.com/zhiayang/nabs + +typedef struct { + size_t count; + const char *data; +} B_String_View; + +const char *b_temp_sv_to_cstr(B_String_View sv); + +B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim); +B_String_View b_sv_trim(B_String_View sv); +bool b_sv_eq(B_String_View a, B_String_View b); +B_String_View b_sv_from_cstr(const char *cstr); +B_String_View b_sv_from_parts(const char *data, size_t count); + +// printf macros for String_View +#ifndef SV_Fmt +#define SV_Fmt "%.*s" +#endif // SV_Fmt +#ifndef SV_Arg +#define SV_Arg(sv) (int) (sv).count, (sv).data +#endif // SV_Arg +// USAGE: +// String_View name = ...; +// printf("Name: "SV_Fmt"\n", SV_Arg(name)); + +#endif // B_H_ + +#ifdef B_IMPLEMENTATION + +static size_t b_temp_size = 0; +static char b_temp[B_TEMP_CAPACITY] = {0}; + +bool b_mkdir_if_not_exists(const char *path) +{ + int result = mkdir(path, 0755); + if (result < 0) { + if (errno == EEXIST) { + b_log(B_INFO, "directory `%s` already exists", path); + return true; + } + b_log(B_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); + return false; + } + + b_log(B_INFO, "created directory `%s`", path); + return true; +} + +bool b_copy_file(const char *src_path, const char *dst_path) +{ + b_log(B_INFO, "copying %s -> %s", src_path, dst_path); + + int src_fd = -1; + int dst_fd = -1; + size_t buf_size = 32*1024; + char *buf = B_REALLOC(NULL, buf_size); + B_ASSERT(buf != NULL && "Buy more RAM lol!!"); + bool result = true; + + src_fd = open(src_path, O_RDONLY); + if (src_fd < 0) { + b_log(B_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + + struct stat src_stat; + if (fstat(src_fd, &src_stat) < 0) { + b_log(B_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + + dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); + if (dst_fd < 0) { + b_log(B_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); + b_return_defer(false); + } + + for (;;) { + ssize_t n = read(src_fd, buf, buf_size); + if (n == 0) break; + if (n < 0) { + b_log(B_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + char *buf2 = buf; + while (n > 0) { + ssize_t m = write(dst_fd, buf2, n); + if (m < 0) { + b_log(B_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); + b_return_defer(false); + } + n -= m; + buf2 += m; + } + } + +defer: + free(buf); + close(src_fd); + close(dst_fd); + return result; +} + +void b_cmd_render(B_Cmd cmd, B_String_Builder *render) +{ + for (size_t i = 0; i < cmd.count; ++i) { + const char *arg = cmd.items[i]; + if (arg == NULL) break; + if (i > 0) b_sb_append_cstr(render, " "); + if (!strchr(arg, ' ')) { + b_sb_append_cstr(render, arg); + } else { + b_da_append(render, '\''); + b_sb_append_cstr(render, arg); + b_da_append(render, '\''); + } + } +} + +B_Proc b_cmd_run_async(B_Cmd cmd) +{ + if (cmd.count < 1) { + b_log(B_ERROR, "Could not run empty command"); + return B_INVALID_PROC; + } + + B_String_Builder sb = {0}; + b_cmd_render(cmd, &sb); + b_sb_append_null(&sb); + b_log(B_INFO, "CMD: %s", sb.items); + b_sb_free(sb); + memset(&sb, 0, sizeof(sb)); + + pid_t cpid = fork(); + if (cpid < 0) { + b_log(B_ERROR, "Could not fork child process: %s", strerror(errno)); + return B_INVALID_PROC; + } + + if (cpid == 0) { + // NOTE: This leaks a bit of memory in the child process. + // But do we actually care? It's a one off leak anyway... + B_Cmd cmd_null = {0}; + b_da_append_many(&cmd_null, cmd.items, cmd.count); + b_cmd_append(&cmd_null, NULL); + + if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { + b_log(B_ERROR, "Could not exec child process: %s", strerror(errno)); + exit(1); + } + B_ASSERT(0 && "unreachable"); + } + + return cpid; +} + +bool b_procs_wait(B_Procs procs) +{ + bool success = true; + for (size_t i = 0; i < procs.count; ++i) { + success = b_proc_wait(procs.items[i]) && success; + } + return success; +} + +bool b_proc_wait(B_Proc proc) +{ + if (proc == B_INVALID_PROC) return false; + + for (;;) { + int wstatus = 0; + if (waitpid(proc, &wstatus, 0) < 0) { + b_log(B_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); + return false; + } + + if (WIFEXITED(wstatus)) { + int exit_status = WEXITSTATUS(wstatus); + if (exit_status != 0) { + b_log(B_ERROR, "command exited with exit code %d", exit_status); + return false; + } + + break; + } + + if (WIFSIGNALED(wstatus)) { + b_log(B_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus))); + return false; + } + } + + return true; +} + +bool b_cmd_run_sync(B_Cmd cmd) +{ + B_Proc p = b_cmd_run_async(cmd); + if (p == B_INVALID_PROC) return false; + return b_proc_wait(p); +} + +char *b_shift_args(int *argc, char ***argv) +{ + B_ASSERT(*argc > 0); + char *result = **argv; + (*argv) += 1; + (*argc) -= 1; + return result; +} + +void b_log(B_Log_Level level, const char *fmt, ...) +{ + switch (level) { + case B_INFO: + fprintf(stderr, "[INFO] "); + break; + case B_WARNING: + fprintf(stderr, "[WARNING] "); + break; + case B_ERROR: + fprintf(stderr, "[ERROR] "); + break; + default: + B_ASSERT(0 && "unreachable"); + } + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +bool b_read_entire_dir(const char *parent, B_File_Paths *children) +{ + bool result = true; + DIR *dir = NULL; + + dir = opendir(parent); + if (dir == NULL) { + b_log(B_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); + b_return_defer(false); + } + + errno = 0; + struct dirent *ent = readdir(dir); + while (ent != NULL) { + b_da_append(children, b_temp_strdup(ent->d_name)); + ent = readdir(dir); + } + + if (errno != 0) { + b_log(B_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); + b_return_defer(false); + } + +defer: + if (dir) closedir(dir); + return result; +} + +bool b_write_entire_file(const char *path, const void *data, size_t size) +{ + bool result = true; + + FILE *f = fopen(path, "wb"); + if (f == NULL) { + b_log(B_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); + b_return_defer(false); + } + + // len + // v + // aaaaaaaaaa + // ^ + // data + + const char *buf = data; + while (size > 0) { + size_t n = fwrite(buf, 1, size, f); + if (ferror(f)) { + b_log(B_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); + b_return_defer(false); + } + size -= n; + buf += n; + } + +defer: + if (f) fclose(f); + return result; +} + +B_File_Type b_get_file_type(const char *path) +{ + struct stat statbuf; + if (stat(path, &statbuf) < 0) { + b_log(B_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); + return -1; + } + + switch (statbuf.st_mode & S_IFMT) { + case S_IFDIR: return B_FILE_DIRECTORY; + case S_IFREG: return B_FILE_REGULAR; + case S_IFLNK: return B_FILE_SYMLINK; + default: return B_FILE_OTHER; + } +} + +bool b_copy_directory_recursively(const char *src_path, const char *dst_path) +{ + bool result = true; + B_File_Paths children = {0}; + B_String_Builder src_sb = {0}; + B_String_Builder dst_sb = {0}; + size_t temp_checkpoint = b_temp_save(); + + B_File_Type type = b_get_file_type(src_path); + if (type < 0) return false; + + switch (type) { + case B_FILE_DIRECTORY: { + if (!b_mkdir_if_not_exists(dst_path)) b_return_defer(false); + if (!b_read_entire_dir(src_path, &children)) b_return_defer(false); + + for (size_t i = 0; i < children.count; ++i) { + if (strcmp(children.items[i], ".") == 0) continue; + if (strcmp(children.items[i], "..") == 0) continue; + + src_sb.count = 0; + b_sb_append_cstr(&src_sb, src_path); + b_sb_append_cstr(&src_sb, "/"); + b_sb_append_cstr(&src_sb, children.items[i]); + b_sb_append_null(&src_sb); + + dst_sb.count = 0; + b_sb_append_cstr(&dst_sb, dst_path); + b_sb_append_cstr(&dst_sb, "/"); + b_sb_append_cstr(&dst_sb, children.items[i]); + b_sb_append_null(&dst_sb); + + if (!b_copy_directory_recursively(src_sb.items, dst_sb.items)) { + b_return_defer(false); + } + } + } break; + + case B_FILE_REGULAR: { + if (!b_copy_file(src_path, dst_path)) { + b_return_defer(false); + } + } break; + + case B_FILE_SYMLINK: { + b_log(B_WARNING, "TODO: Copying symlinks is not supported yet"); + } break; + + case B_FILE_OTHER: { + b_log(B_ERROR, "Unsupported type of file %s", src_path); + b_return_defer(false); + } break; + + default: B_ASSERT(0 && "unreachable"); + } + +defer: + b_temp_rewind(temp_checkpoint); + b_da_free(src_sb); + b_da_free(dst_sb); + b_da_free(children); + return result; +} + +char *b_temp_strdup(const char *cstr) +{ + size_t n = strlen(cstr); + char *result = b_temp_alloc(n + 1); + B_ASSERT(result != NULL && "Increase B_TEMP_CAPACITY"); + memcpy(result, cstr, n); + result[n] = '\0'; + return result; +} + +void *b_temp_alloc(size_t size) +{ + if (b_temp_size + size > B_TEMP_CAPACITY) return NULL; + void *result = &b_temp[b_temp_size]; + b_temp_size += size; + return result; +} + +char *b_temp_sprintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + int n = vsnprintf(NULL, 0, format, args); + va_end(args); + + B_ASSERT(n >= 0); + char *result = b_temp_alloc(n + 1); + B_ASSERT(result != NULL && "Extend the size of the temporary allocator"); + // TODO: use proper arenas for the temporary allocator; + va_start(args, format); + vsnprintf(result, n + 1, format, args); + va_end(args); + + return result; +} + +void b_temp_reset(void) +{ + b_temp_size = 0; +} + +size_t b_temp_save(void) +{ + return b_temp_size; +} + +void b_temp_rewind(size_t checkpoint) +{ + b_temp_size = checkpoint; +} + +const char *b_temp_sv_to_cstr(B_String_View sv) +{ + char *result = b_temp_alloc(sv.count + 1); + B_ASSERT(result != NULL && "Extend the size of the temporary allocator"); + memcpy(result, sv.data, sv.count); + result[sv.count] = '\0'; + return result; +} + +int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) +{ + struct stat statbuf = {0}; + + if (stat(output_path, &statbuf) < 0) { + // NOTE: if output does not exist it 100% must be rebuilt + if (errno == ENOENT) return 1; + b_log(B_ERROR, "could not stat %s: %s", output_path, strerror(errno)); + return -1; + } + int output_path_time = statbuf.st_mtime; + + for (size_t i = 0; i < input_paths_count; ++i) { + const char *input_path = input_paths[i]; + if (stat(input_path, &statbuf) < 0) { + // NOTE: non-existing input is an error cause it is needed for building in the first place + b_log(B_ERROR, "could not stat %s: %s", input_path, strerror(errno)); + return -1; + } + int input_path_time = statbuf.st_mtime; + // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild + if (input_path_time > output_path_time) return 1; + } + + return 0; +} + +int b_needs_rebuild1(const char *output_path, const char *input_path) +{ + return b_needs_rebuild(output_path, &input_path, 1); +} + +bool b_rename(const char *old_path, const char *new_path) +{ + b_log(B_INFO, "renaming %s -> %s", old_path, new_path); + if (rename(old_path, new_path) < 0) { + b_log(B_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); + return false; + } + return true; +} + +bool b_read_entire_file(const char *path, B_String_Builder *sb) +{ + bool result = true; + + FILE *f = fopen(path, "rb"); + if (f == NULL) b_return_defer(false); + if (fseek(f, 0, SEEK_END) < 0) b_return_defer(false); + long m = ftell(f); + if (m < 0) b_return_defer(false); + if (fseek(f, 0, SEEK_SET) < 0) b_return_defer(false); + + size_t new_count = sb->count + m; + if (new_count > sb->capacity) { + sb->items = realloc(sb->items, new_count); + B_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); + sb->capacity = new_count; + } + + fread(sb->items + sb->count, m, 1, f); + if (ferror(f)) { + // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. + b_return_defer(false); + } + sb->count = new_count; + +defer: + if (!result) b_log(B_ERROR, "Could not read file %s: %s", path, strerror(errno)); + if (f) fclose(f); + return result; +} + +B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim) +{ + size_t i = 0; + while (i < sv->count && sv->data[i] != delim) { + i += 1; + } + + B_String_View result = b_sv_from_parts(sv->data, i); + + if (i < sv->count) { + sv->count -= i + 1; + sv->data += i + 1; + } else { + sv->count -= i; + sv->data += i; + } + + return result; +} + +B_String_View b_sv_from_parts(const char *data, size_t count) +{ + B_String_View sv; + sv.count = count; + sv.data = data; + return sv; +} + +B_String_View b_sv_trim_left(B_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[i])) { + i += 1; + } + + return b_sv_from_parts(sv.data + i, sv.count - i); +} + +B_String_View b_sv_trim_right(B_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { + i += 1; + } + + return b_sv_from_parts(sv.data, sv.count - i); +} + +B_String_View b_sv_trim(B_String_View sv) +{ + return b_sv_trim_right(b_sv_trim_left(sv)); +} + +B_String_View b_sv_from_cstr(const char *cstr) +{ + return b_sv_from_parts(cstr, strlen(cstr)); +} + +bool b_sv_eq(B_String_View a, B_String_View b) +{ + if (a.count != b.count) { + return false; + } else { + return memcmp(a.data, b.data, a.count) == 0; + } +} + +// RETURNS: +// 0 - file does not exists +// 1 - file exists +// -1 - error while checking if file exists. The error is logged +int b_file_exists(const char *file_path) +{ + struct stat statbuf; + if (stat(file_path, &statbuf) < 0) { + if (errno == ENOENT) return 0; + b_log(B_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); + return -1; + } + return 1; +} + +#endif |