From 72e94e3fcd583ac5c2337817ffb0b6dd855da436 Mon Sep 17 00:00:00 2001 From: momoyonwork Date: Sat, 25 Jan 2025 14:13:33 +0500 Subject: [PATCH] Implement tests. - WIP: Use nob to compile and run tests... --- nob.c | 57 ++ nob.h | 1853 +++++++++++++++++++++++++++++++++++++++++++++ tests/arena.c | 10 + tests/arena.exe | Bin 0 -> 71339 bytes tests/logging.c | 10 + tests/logging.exe | Bin 0 -> 71851 bytes 6 files changed, 1930 insertions(+) create mode 100644 nob.c create mode 100644 nob.h create mode 100644 tests/arena.c create mode 100644 tests/arena.exe create mode 100644 tests/logging.c create mode 100644 tests/logging.exe diff --git a/nob.c b/nob.c new file mode 100644 index 0000000..c87d510 --- /dev/null +++ b/nob.c @@ -0,0 +1,57 @@ +#define NOB_IMPLEMENTATION +#include "nob.h" + +#define CC "gcc" +#define CFLAGS "-Wextra", "-Wall", "-Wno-char-subscripts" + +#define TESTS_DIR "./tests/" + +int main(int argc, char *argv[]) { + NOB_GO_REBUILD_URSELF(argc, argv); + + Nob_Cmd cmd = {0}; + + Nob_File_Paths files = {0}; + if (!nob_read_entire_dir(TESTS_DIR, &files)) return 1; + + for (int i = 0; i < files.count; ++i) { + const char *file = files.items[i]; + + // Ignore files starting with '.' + if (file[0] == '.') continue; + + cmd.count = 0; + + Nob_String_Builder file_without_suffix = {0}; + nob_sb_append_cstr(&file_without_suffix, file); + + // Ignore non *.c files + if (!nob_sv_end_with(nob_sb_to_sv(file_without_suffix), ".c")) { + nob_sb_free(file_without_suffix); + continue; + } + + file_without_suffix.count -= 2; + nob_sb_append_null(&file_without_suffix); + + Nob_String_Builder sb = {0}; + nob_sb_append_cstr(&sb, TESTS_DIR); + nob_sb_append_cstr(&sb, "/"); + nob_sb_append_cstr(&sb, file_without_suffix.items); + nob_sb_append_null(&sb); + + + Nob_String_Builder sb2 = {0}; + nob_sb_append_cstr(&sb2, TESTS_DIR); + nob_sb_append_cstr(&sb2, "/"); + nob_sb_append_cstr(&sb2, file); + nob_sb_append_null(&sb2); + + nob_cmd_append(&cmd, CC, CFLAGS, "-o", sb.items, sb2.items); + + if (!nob_cmd_run_sync(cmd)) return 1; + nob_sb_free(sb); + nob_sb_free(sb2); + } + return 0; +} diff --git a/nob.h b/nob.h new file mode 100644 index 0000000..022a98e --- /dev/null +++ b/nob.h @@ -0,0 +1,1853 @@ +/* nob - v1.9.0 - Public Domain - https://github.com/tsoding/nob + + This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea. + + # Quick Example + + ```c + // nob.c + #define NOB_IMPLEMENTATION + #include "nob.h" + + int main(int argc, char **argv) + { + NOB_GO_REBUILD_URSELF(argc, argv); + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); + if (!nob_cmd_run_sync(cmd)) return 1; + return 0; + } + ``` + + ```console + $ cc -o nob nob.c + $ ./nob + ``` + + The `nob` automatically rebuilds itself if `nob.c` is modified thanks to + the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below) + + # The Zoo of `nob_cmd_run_*` Functions + + `Nob_Cmd` is just a dynamic array of strings which represents a command and its arguments. + First you append the arguments + + ```c + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); + ``` + + Then you run it + + ```c + if (!nob_cmd_run_sync(cmd)) return 1; + ``` + + `*_sync` at the end indicates that the function blocks until the command finishes executing + and returns `true` on success and `false` on failure. You can run the command asynchronously + but you have to explitictly wait for it afterwards: + + ```c + Nob_Proc p = nob_cmd_run_async(cmd); + if (p == NOB_INVALID_PROC) return 1; + if (!nob_proc_wait(p)) return 1; + ``` + + One of the problems with running commands like that is that `Nob_Cmd` still contains the arguments + from the previously run command. If you want to reuse the same `Nob_Cmd` you have to not forget to reset + it + + ```c + Nob_Cmd cmd = {0}; + + nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); + if (!nob_cmd_run_sync(cmd)) return 1; + cmd.count = 0; + + nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); + if (!nob_cmd_run_sync(cmd)) return 1; + cmd.count = 0; + ``` + + Which is a bit error prone. To make it a bit easier we have `nob_cmd_run_sync_and_reset()` which + accepts `Nob_Cmd` by reference and resets it for you: + + ```c + Nob_Cmd cmd = {0}; + + nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); + if (!nob_cmd_run_sync_and_reset(&cmd)) return 1; + + nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); + if (!nob_cmd_run_sync_and_reset(&cmd)) return 1; + ``` + + There is of course also `nob_cmd_run_async_and_reset()` to maintain the pattern. + + The stdin, stdout and stderr of any command can be redirected by using `Nob_Cmd_Redirect` structure + along with `nob_cmd_run_sync_redirect()` or `nob_cmd_run_async_redirect()` + + ```c + // Opening all the necessary files + Nob_Fd fdin = nob_fd_open_for_read("input.txt"); + if (fdin == NOB_INVALID_FD) return 1; + Nob_Fd fdout = nob_fd_open_for_read("output.txt"); + if (fdout == NOB_INVALID_FD) return 1; + Nob_Fd fderr = nob_fd_open_for_read("error.txt"); + if (fderr == NOB_INVALID_FD) return 1; + + // Preparing the command + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); + + // Running the command synchronously redirecting the standard streams + bool ok = nob_cmd_run_sync_redirect(cmd, (Nob_Cmd_Redirect) { + .fdin = fdin, + .fdout = fdout, + .fderr = fderr, + }); + if (!ok) return 1; + + // Closing all the files + nob_fd_close(fdin); + nob_fd_close(fdout); + nob_fd_close(fderr); + + // Reseting the command + cmd.count = 0; + ``` + + And of course if you find closing the files and reseting the command annoying we have + `nob_cmd_run_sync_redirect_and_reset()` and `nob_cmd_run_async_redirect_and_reset()` + which do all of that for you automatically. + + All the Zoo of `nob_cmd_run_*` functions follows the same pattern: sync/async, + redirect/no redirect, and_reset/no and_reset. They always come in that order. + + # Stripping off `nob_` Prefixes + + Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any + potential conflicts with any other names in your code. But sometimes it is very annoying and makes + the code noisy. If you know that none of the names from nob.h conflict with anything in your code + you can enable NOB_STRIP_PREFIX macro and just drop all the prefixes: + + ```c + // nob.c + #define NOB_IMPLEMENTATION + #define NOB_STRIP_PREFIX + #include "nob.h" + + int main(int argc, char **argv) + { + NOB_GO_REBUILD_URSELF(argc, argv); + Cmd cmd = {0}; + cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); + if (!cmd_run_sync(cmd)) return 1; + return 0; + } + ``` + + Not all the names have strippable prefixes. All the redefinable names like `NOB_GO_REBUILD_URSELF` + for instance will retain their prefix even if NOB_STRIP_PREFIX is enabled. Notable exception is the + nob_log() function. Stripping away the prefix results in log() which was historically always referring + to the natural logarithmic function that is already defined in math.h. So there is no reason to strip + off the prefix for nob_log(). + + The prefixes are stripped off only on the level of preprocessor. The names of the functions in the + compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h + from other languages (for whatever reason). + + If only few specific names create conflicts for you, you can just #undef those names after the + `#include ` since they are macros anyway. +*/ + +#ifndef NOB_H_ +#define NOB_H_ + +#define NOB_ASSERT assert +#define NOB_REALLOC realloc +#define NOB_FREE free + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# define _WINUSER_ +# define _WINGDI_ +# define _IMM_ +# define _WINCON_ +# include +# include +# include +#else +# include +# include +# include +# include +# include +#endif + +#ifdef _WIN32 +# define NOB_LINE_END "\r\n" +#else +# define NOB_LINE_END "\n" +#endif + +#define NOB_UNUSED(value) (void)(value) +#define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) +#define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) + +#define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) +#define NOB_ARRAY_GET(array, index) \ + (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index]) + +typedef enum { + NOB_INFO, + NOB_WARNING, + NOB_ERROR, + NOB_NO_LOGS, +} Nob_Log_Level; + +// Any messages with the level below nob_minimal_log_level are going to be suppressed. +extern Nob_Log_Level nob_minimal_log_level; + +void nob_log(Nob_Log_Level level, const char *fmt, ...); + +// It is an equivalent of shift command from bash. It basically pops an element from +// the beginning of a sized array. +#define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++) +// NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with +// the command line arguments passed to the main() function. nob_shift() is more generic. +// So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently +// remove it. This alias does not hurt anybody. +#define nob_shift_args(argc, argv) nob_shift(*argv, *argc) + +typedef struct { + const char **items; + size_t count; + size_t capacity; +} Nob_File_Paths; + +typedef enum { + NOB_FILE_REGULAR = 0, + NOB_FILE_DIRECTORY, + NOB_FILE_SYMLINK, + NOB_FILE_OTHER, +} Nob_File_Type; + +bool nob_mkdir_if_not_exists(const char *path); +bool nob_copy_file(const char *src_path, const char *dst_path); +bool nob_copy_directory_recursively(const char *src_path, const char *dst_path); +bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children); +bool nob_write_entire_file(const char *path, const void *data, size_t size); +Nob_File_Type nob_get_file_type(const char *path); + +#define nob_return_defer(value) do { result = (value); goto defer; } while(0) + +// Initial capacity of a dynamic array +#ifndef NOB_DA_INIT_CAP +#define NOB_DA_INIT_CAP 256 +#endif + +// Append an item to a dynamic array +#define nob_da_append(da, item) \ + do { \ + if ((da)->count >= (da)->capacity) { \ + (da)->capacity = (da)->capacity == 0 ? NOB_DA_INIT_CAP : (da)->capacity*2; \ + (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ + } \ + \ + (da)->items[(da)->count++] = (item); \ + } while (0) + +#define nob_da_free(da) NOB_FREE((da).items) + +// Append several items to a dynamic array +#define nob_da_append_many(da, new_items, new_items_count) \ + do { \ + if ((da)->count + (new_items_count) > (da)->capacity) { \ + if ((da)->capacity == 0) { \ + (da)->capacity = NOB_DA_INIT_CAP; \ + } \ + while ((da)->count + (new_items_count) > (da)->capacity) { \ + (da)->capacity *= 2; \ + } \ + (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + NOB_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; +} Nob_String_Builder; + +bool nob_read_entire_file(const char *path, Nob_String_Builder *sb); + +// Append a sized buffer to a string builder +#define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size) + +// Append a NULL-terminated string to a string builder +#define nob_sb_append_cstr(sb, cstr) \ + do { \ + const char *s = (cstr); \ + size_t n = strlen(s); \ + nob_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 nob_sb_append_null(sb) nob_da_append_many(sb, "", 1) + +// Free the memory allocated by a string builder +#define nob_sb_free(sb) NOB_FREE((sb).items) + +// Process handle +#ifdef _WIN32 +typedef HANDLE Nob_Proc; +#define NOB_INVALID_PROC INVALID_HANDLE_VALUE +typedef HANDLE Nob_Fd; +#define NOB_INVALID_FD INVALID_HANDLE_VALUE +#else +typedef int Nob_Proc; +#define NOB_INVALID_PROC (-1) +typedef int Nob_Fd; +#define NOB_INVALID_FD (-1) +#endif // _WIN32 + +Nob_Fd nob_fd_open_for_read(const char *path); +Nob_Fd nob_fd_open_for_write(const char *path); +void nob_fd_close(Nob_Fd fd); + +typedef struct { + Nob_Proc *items; + size_t count; + size_t capacity; +} Nob_Procs; + +bool nob_procs_wait(Nob_Procs procs); +bool nob_procs_wait_and_reset(Nob_Procs *procs); + +// Wait until the process has finished +bool nob_proc_wait(Nob_Proc proc); + +// A command - the main workhorse of Nob. Nob is all about building commands an running them +typedef struct { + const char **items; + size_t count; + size_t capacity; +} Nob_Cmd; + +// Example: +// ```c +// Nob_Fd fdin = nob_fd_open_for_read("input.txt"); +// if (fdin == NOB_INVALID_FD) fail(); +// Nob_Fd fdout = nob_fd_open_for_write("output.txt"); +// if (fdout == NOB_INVALID_FD) fail(); +// Nob_Cmd cmd = {0}; +// nob_cmd_append(&cmd, "cat"); +// if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) { +// .fdin = &fdin, +// .fdout = &fdout +// })) fail(); +// ``` +typedef struct { + Nob_Fd *fdin; + Nob_Fd *fdout; + Nob_Fd *fderr; +} Nob_Cmd_Redirect; + +// 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 nob_sb_append_null if you plan to +// use it as a C string. +void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render); + +#define nob_cmd_append(cmd, ...) \ + nob_da_append_many(cmd, \ + ((const char*[]){__VA_ARGS__}), \ + (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) + +#define nob_cmd_extend(cmd, other_cmd) \ + nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count) + +// Free all the memory allocated by command arguments +#define nob_cmd_free(cmd) NOB_FREE(cmd.items) + +// Run command asynchronously +#define nob_cmd_run_async(cmd) nob_cmd_run_async_redirect(cmd, (Nob_Cmd_Redirect) {0}) +// NOTE: nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0 +// so the Nob_Cmd instance can be seamlessly used several times in a row +Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd); +// Run redirected command asynchronously +Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); +// Run redirected command asynchronously and set cmd.count to 0 and close all the opened files +Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); + +// Run command synchronously +bool nob_cmd_run_sync(Nob_Cmd cmd); +// NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0 +// so the Nob_Cmd instance can be seamlessly used several times in a row +bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd); +// Run redirected command synchronously +bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); +// Run redirected command synchronously and set cmd.count to 0 and close all the opened files +bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); + +#ifndef NOB_TEMP_CAPACITY +#define NOB_TEMP_CAPACITY (8*1024*1024) +#endif // NOB_TEMP_CAPACITY +char *nob_temp_strdup(const char *cstr); +void *nob_temp_alloc(size_t size); +char *nob_temp_sprintf(const char *format, ...); +void nob_temp_reset(void); +size_t nob_temp_save(void); +void nob_temp_rewind(size_t checkpoint); + +// Given any path returns the last part of that path. +// "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory" +const char *nob_path_name(const char *path); +bool nob_rename(const char *old_path, const char *new_path); +int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); +int nob_needs_rebuild1(const char *output_path, const char *input_path); +int nob_file_exists(const char *file_path); +const char *nob_get_current_dir_temp(void); +bool nob_set_current_dir(const char *path); + +// TODO: add MinGW support for Go Rebuild Urself™ Technology +#ifndef NOB_REBUILD_URSELF +# if _WIN32 +# if defined(__GNUC__) +# define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path +# elif defined(__clang__) +# define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path +# elif defined(_MSC_VER) +# define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path +# endif +# else +# define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path +# endif +#endif + +// Go Rebuild Urself™ Technology +// +// How to use it: +// int main(int argc, char** argv) { +// NOB_GO_REBUILD_URSELF(argc, argv); +// // actual work +// return 0; +// } +// +// After your added this macro every time you run ./nob 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 NOB_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 NoBuild is to keep the process of bootstrapping +// as simple as possible and doing all of the actual work inside of ./nob) +// +void nob__go_rebuild_urself(const char *source_path, int argc, char **argv); +#define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(__FILE__, argc, argv) + +typedef struct { + size_t count; + const char *data; +} Nob_String_View; + +const char *nob_temp_sv_to_cstr(Nob_String_View sv); + +Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); +Nob_String_View nob_sv_trim(Nob_String_View sv); +Nob_String_View nob_sv_trim_left(Nob_String_View sv); +Nob_String_View nob_sv_trim_right(Nob_String_View sv); +bool nob_sv_eq(Nob_String_View a, Nob_String_View b); +bool nob_sv_end_with(Nob_String_View sv, const char *cstr); +Nob_String_View nob_sv_from_cstr(const char *cstr); +Nob_String_View nob_sv_from_parts(const char *data, size_t count); +// nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View +#define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).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)); + + +// minirent.h HEADER BEGIN //////////////////////////////////////// +// Copyright 2021 Alexey Kutepov +// +// 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. +// +// ============================================================ +// +// minirent — 0.0.1 — A subset of dirent interface for Windows. +// +// https://github.com/tsoding/minirent +// +// ============================================================ +// +// ChangeLog (https://semver.org/ is implied) +// +// 0.0.2 Automatically include dirent.h on non-Windows +// platforms +// 0.0.1 First Official Release + +#ifndef _WIN32 +#include +#else // _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +struct dirent +{ + char d_name[MAX_PATH+1]; +}; + +typedef struct DIR DIR; + +static DIR *opendir(const char *dirpath); +static struct dirent *readdir(DIR *dirp); +static int closedir(DIR *dirp); + +#endif // _WIN32 +// minirent.h HEADER END //////////////////////////////////////// + +#ifdef _WIN32 + +char *nob_win32_error_message(DWORD err); + +#endif // _WIN32 + +#endif // NOB_H_ + +#ifdef NOB_IMPLEMENTATION + +// Any messages with the level below nob_minimal_log_level are going to be suppressed. +Nob_Log_Level nob_minimal_log_level = NOB_INFO; + +#ifdef _WIN32 + +// Base on https://stackoverflow.com/a/75644008 +// > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it. +// > +// > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265 +#ifndef NOB_WIN32_ERR_MSG_SIZE +#define NOB_WIN32_ERR_MSG_SIZE (4 * 1024) +#endif // NOB_WIN32_ERR_MSG_SIZE + +char *nob_win32_error_message(DWORD err) { + static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; + DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg, + NOB_WIN32_ERR_MSG_SIZE, NULL); + + if (errMsgSize == 0) { + if (GetLastError() != ERROR_MR_MID_NOT_FOUND) { + if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { + return (char *)&win32ErrMsg; + } else { + return NULL; + } + } else { + if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { + return (char *)&win32ErrMsg; + } else { + return NULL; + } + } + } + + while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) { + win32ErrMsg[--errMsgSize] = '\0'; + } + + return win32ErrMsg; +} + +#endif // _WIN32 + +// The implementation idea is stolen from https://github.com/zhiayang/nabs +void nob__go_rebuild_urself(const char *source_path, int argc, char **argv) +{ + const char *binary_path = nob_shift(argv, argc); +#ifdef _WIN32 + // On Windows executables almost always invoked without extension, so + // it's ./nob, not ./nob.exe. For renaming the extension is a must. + if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), ".exe")) { + binary_path = nob_temp_sprintf("%s.exe", binary_path); + } +#endif + + int rebuild_is_needed = nob_needs_rebuild1(binary_path, source_path); + if (rebuild_is_needed < 0) exit(1); // error + if (!rebuild_is_needed) return; // no rebuild is needed + + Nob_Cmd cmd = {0}; + + const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path); + + if (!nob_rename(binary_path, old_binary_path)) exit(1); + nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path)); + if (!nob_cmd_run_sync_and_reset(&cmd)) { + nob_rename(old_binary_path, binary_path); + exit(1); + } + + nob_cmd_append(&cmd, binary_path); + nob_da_append_many(&cmd, argv, argc); + if (!nob_cmd_run_sync_and_reset(&cmd)) exit(1); + exit(0); +} + +static size_t nob_temp_size = 0; +static char nob_temp[NOB_TEMP_CAPACITY] = {0}; + +bool nob_mkdir_if_not_exists(const char *path) +{ +#ifdef _WIN32 + int result = mkdir(path); +#else + int result = mkdir(path, 0755); +#endif + if (result < 0) { + if (errno == EEXIST) { + nob_log(NOB_INFO, "directory `%s` already exists", path); + return true; + } + nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); + return false; + } + + nob_log(NOB_INFO, "created directory `%s`", path); + return true; +} + +bool nob_copy_file(const char *src_path, const char *dst_path) +{ + nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); +#ifdef _WIN32 + if (!CopyFile(src_path, dst_path, FALSE)) { + nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError())); + return false; + } + return true; +#else + int src_fd = -1; + int dst_fd = -1; + size_t buf_size = 32*1024; + char *buf = NOB_REALLOC(NULL, buf_size); + NOB_ASSERT(buf != NULL && "Buy more RAM lol!!"); + bool result = true; + + src_fd = open(src_path, O_RDONLY); + if (src_fd < 0) { + nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); + nob_return_defer(false); + } + + struct stat src_stat; + if (fstat(src_fd, &src_stat) < 0) { + nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); + nob_return_defer(false); + } + + dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); + if (dst_fd < 0) { + nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); + nob_return_defer(false); + } + + for (;;) { + ssize_t n = read(src_fd, buf, buf_size); + if (n == 0) break; + if (n < 0) { + nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); + nob_return_defer(false); + } + char *buf2 = buf; + while (n > 0) { + ssize_t m = write(dst_fd, buf2, n); + if (m < 0) { + nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); + nob_return_defer(false); + } + n -= m; + buf2 += m; + } + } + +defer: + free(buf); + close(src_fd); + close(dst_fd); + return result; +#endif +} + +void nob_cmd_render(Nob_Cmd cmd, Nob_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) nob_sb_append_cstr(render, " "); + if (!strchr(arg, ' ')) { + nob_sb_append_cstr(render, arg); + } else { + nob_da_append(render, '\''); + nob_sb_append_cstr(render, arg); + nob_da_append(render, '\''); + } + } +} + +Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) +{ + if (cmd.count < 1) { + nob_log(NOB_ERROR, "Could not run empty command"); + return NOB_INVALID_PROC; + } + + Nob_String_Builder sb = {0}; + nob_cmd_render(cmd, &sb); + nob_sb_append_null(&sb); + nob_log(NOB_INFO, "CMD: %s", sb.items); + nob_sb_free(sb); + memset(&sb, 0, sizeof(sb)); + +#ifdef _WIN32 + // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output + + STARTUPINFO siStartInfo; + ZeroMemory(&siStartInfo, sizeof(siStartInfo)); + siStartInfo.cb = sizeof(STARTUPINFO); + // NOTE: theoretically setting NULL to std handles should not be a problem + // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior + // TODO: check for errors in GetStdHandle + siStartInfo.hStdError = redirect.fderr ? *redirect.fderr : GetStdHandle(STD_ERROR_HANDLE); + siStartInfo.hStdOutput = redirect.fdout ? *redirect.fdout : GetStdHandle(STD_OUTPUT_HANDLE); + siStartInfo.hStdInput = redirect.fdin ? *redirect.fdin : GetStdHandle(STD_INPUT_HANDLE); + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION piProcInfo; + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + + // TODO: use a more reliable rendering of the command instead of cmd_render + // cmd_render is for logging primarily + nob_cmd_render(cmd, &sb); + nob_sb_append_null(&sb); + BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); + nob_sb_free(sb); + + if (!bSuccess) { + nob_log(NOB_ERROR, "Could not create child process: %s", nob_win32_error_message(GetLastError())); + return NOB_INVALID_PROC; + } + + CloseHandle(piProcInfo.hThread); + + return piProcInfo.hProcess; +#else + pid_t cpid = fork(); + if (cpid < 0) { + nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); + return NOB_INVALID_PROC; + } + + if (cpid == 0) { + if (redirect.fdin) { + if (dup2(*redirect.fdin, STDIN_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno)); + exit(1); + } + } + + if (redirect.fdout) { + if (dup2(*redirect.fdout, STDOUT_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno)); + exit(1); + } + } + + if (redirect.fderr) { + if (dup2(*redirect.fderr, STDERR_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); + exit(1); + } + } + + // NOTE: This leaks a bit of memory in the child process. + // But do we actually care? It's a one off leak anyway... + Nob_Cmd cmd_null = {0}; + nob_da_append_many(&cmd_null, cmd.items, cmd.count); + nob_cmd_append(&cmd_null, NULL); + + if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { + nob_log(NOB_ERROR, "Could not exec child process: %s", strerror(errno)); + exit(1); + } + NOB_UNREACHABLE("nob_cmd_run_async_redirect"); + } + + return cpid; +#endif +} + +Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd) +{ + Nob_Proc proc = nob_cmd_run_async(*cmd); + cmd->count = 0; + return proc; +} + +Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) +{ + Nob_Proc proc = nob_cmd_run_async_redirect(*cmd, redirect); + cmd->count = 0; + if (redirect.fdin) { + nob_fd_close(*redirect.fdin); + *redirect.fdin = NOB_INVALID_FD; + } + if (redirect.fdout) { + nob_fd_close(*redirect.fdout); + *redirect.fdout = NOB_INVALID_FD; + } + if (redirect.fderr) { + nob_fd_close(*redirect.fderr); + *redirect.fderr = NOB_INVALID_FD; + } + return proc; +} + +Nob_Fd nob_fd_open_for_read(const char *path) +{ +#ifndef _WIN32 + Nob_Fd result = open(path, O_RDONLY); + if (result < 0) { + nob_log(NOB_ERROR, "Could not open file %s: %s", path, strerror(errno)); + return NOB_INVALID_FD; + } + return result; +#else + // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing + SECURITY_ATTRIBUTES saAttr = {0}; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + + Nob_Fd result = CreateFile( + path, + GENERIC_READ, + 0, + &saAttr, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + + if (result == INVALID_HANDLE_VALUE) { + nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); + return NOB_INVALID_FD; + } + + return result; +#endif // _WIN32 +} + +Nob_Fd nob_fd_open_for_write(const char *path) +{ +#ifndef _WIN32 + Nob_Fd result = open(path, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (result < 0) { + nob_log(NOB_ERROR, "could not open file %s: %s", path, strerror(errno)); + return NOB_INVALID_FD; + } + return result; +#else + SECURITY_ATTRIBUTES saAttr = {0}; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + + Nob_Fd result = CreateFile( + path, // name of the write + GENERIC_WRITE, // open for writing + 0, // do not share + &saAttr, // default security + OPEN_ALWAYS, // open always + FILE_ATTRIBUTE_NORMAL, // normal file + NULL // no attr. template + ); + + if (result == INVALID_HANDLE_VALUE) { + nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); + return NOB_INVALID_FD; + } + + return result; +#endif // _WIN32 +} + +void nob_fd_close(Nob_Fd fd) +{ +#ifdef _WIN32 + CloseHandle(fd); +#else + close(fd); +#endif // _WIN32 +} + +bool nob_procs_wait(Nob_Procs procs) +{ + bool success = true; + for (size_t i = 0; i < procs.count; ++i) { + success = nob_proc_wait(procs.items[i]) && success; + } + return success; +} + +bool nob_procs_wait_and_reset(Nob_Procs *procs) +{ + bool success = nob_procs_wait(*procs); + procs->count = 0; + return success; +} + +bool nob_proc_wait(Nob_Proc proc) +{ + if (proc == NOB_INVALID_PROC) return false; + +#ifdef _WIN32 + DWORD result = WaitForSingleObject( + proc, // HANDLE hHandle, + INFINITE // DWORD dwMilliseconds + ); + + if (result == WAIT_FAILED) { + nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError())); + return false; + } + + DWORD exit_status; + if (!GetExitCodeProcess(proc, &exit_status)) { + nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError())); + return false; + } + + if (exit_status != 0) { + nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); + return false; + } + + CloseHandle(proc); + + return true; +#else + for (;;) { + int wstatus = 0; + if (waitpid(proc, &wstatus, 0) < 0) { + nob_log(NOB_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) { + nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); + return false; + } + + break; + } + + if (WIFSIGNALED(wstatus)) { + nob_log(NOB_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus))); + return false; + } + } + + return true; +#endif +} + +bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) +{ + Nob_Proc p = nob_cmd_run_async_redirect(cmd, redirect); + if (p == NOB_INVALID_PROC) return false; + return nob_proc_wait(p); +} + +bool nob_cmd_run_sync(Nob_Cmd cmd) +{ + Nob_Proc p = nob_cmd_run_async(cmd); + if (p == NOB_INVALID_PROC) return false; + return nob_proc_wait(p); +} + +bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd) +{ + bool p = nob_cmd_run_sync(*cmd); + cmd->count = 0; + return p; +} + +bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) +{ + bool p = nob_cmd_run_sync_redirect(*cmd, redirect); + cmd->count = 0; + if (redirect.fdin) { + nob_fd_close(*redirect.fdin); + *redirect.fdin = NOB_INVALID_FD; + } + if (redirect.fdout) { + nob_fd_close(*redirect.fdout); + *redirect.fdout = NOB_INVALID_FD; + } + if (redirect.fderr) { + nob_fd_close(*redirect.fderr); + *redirect.fderr = NOB_INVALID_FD; + } + return p; +} + +void nob_log(Nob_Log_Level level, const char *fmt, ...) +{ + if (level < nob_minimal_log_level) return; + + switch (level) { + case NOB_INFO: + fprintf(stderr, "[INFO] "); + break; + case NOB_WARNING: + fprintf(stderr, "[WARNING] "); + break; + case NOB_ERROR: + fprintf(stderr, "[ERROR] "); + break; + case NOB_NO_LOGS: return; + default: + NOB_UNREACHABLE("nob_log"); + } + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) +{ + bool result = true; + DIR *dir = NULL; + + dir = opendir(parent); + if (dir == NULL) { + #ifdef _WIN32 + nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, nob_win32_error_message(GetLastError())); + #else + nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); + #endif // _WIN32 + nob_return_defer(false); + } + + errno = 0; + struct dirent *ent = readdir(dir); + while (ent != NULL) { + nob_da_append(children, nob_temp_strdup(ent->d_name)); + ent = readdir(dir); + } + + if (errno != 0) { + #ifdef _WIN32 + nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, nob_win32_error_message(GetLastError())); + #else + nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); + #endif // _WIN32 + nob_return_defer(false); + } + +defer: + if (dir) closedir(dir); + return result; +} + +bool nob_write_entire_file(const char *path, const void *data, size_t size) +{ + bool result = true; + + FILE *f = fopen(path, "wb"); + if (f == NULL) { + nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); + nob_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)) { + nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); + nob_return_defer(false); + } + size -= n; + buf += n; + } + +defer: + if (f) fclose(f); + return result; +} + +Nob_File_Type nob_get_file_type(const char *path) +{ +#ifdef _WIN32 + DWORD attr = GetFileAttributesA(path); + if (attr == INVALID_FILE_ATTRIBUTES) { + nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError())); + return -1; + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY; + // TODO: detect symlinks on Windows (whatever that means on Windows anyway) + return NOB_FILE_REGULAR; +#else // _WIN32 + struct stat statbuf; + if (stat(path, &statbuf) < 0) { + nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); + return -1; + } + + switch (statbuf.st_mode & S_IFMT) { + case S_IFDIR: return NOB_FILE_DIRECTORY; + case S_IFREG: return NOB_FILE_REGULAR; + case S_IFLNK: return NOB_FILE_SYMLINK; + default: return NOB_FILE_OTHER; + } +#endif // _WIN32 +} + +bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) +{ + bool result = true; + Nob_File_Paths children = {0}; + Nob_String_Builder src_sb = {0}; + Nob_String_Builder dst_sb = {0}; + size_t temp_checkpoint = nob_temp_save(); + + Nob_File_Type type = nob_get_file_type(src_path); + if (type < 0) return false; + + switch (type) { + case NOB_FILE_DIRECTORY: { + if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false); + if (!nob_read_entire_dir(src_path, &children)) nob_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; + nob_sb_append_cstr(&src_sb, src_path); + nob_sb_append_cstr(&src_sb, "/"); + nob_sb_append_cstr(&src_sb, children.items[i]); + nob_sb_append_null(&src_sb); + + dst_sb.count = 0; + nob_sb_append_cstr(&dst_sb, dst_path); + nob_sb_append_cstr(&dst_sb, "/"); + nob_sb_append_cstr(&dst_sb, children.items[i]); + nob_sb_append_null(&dst_sb); + + if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) { + nob_return_defer(false); + } + } + } break; + + case NOB_FILE_REGULAR: { + if (!nob_copy_file(src_path, dst_path)) { + nob_return_defer(false); + } + } break; + + case NOB_FILE_SYMLINK: { + nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet"); + } break; + + case NOB_FILE_OTHER: { + nob_log(NOB_ERROR, "Unsupported type of file %s", src_path); + nob_return_defer(false); + } break; + + default: NOB_UNREACHABLE("nob_copy_directory_recursively"); + } + +defer: + nob_temp_rewind(temp_checkpoint); + nob_da_free(src_sb); + nob_da_free(dst_sb); + nob_da_free(children); + return result; +} + +char *nob_temp_strdup(const char *cstr) +{ + size_t n = strlen(cstr); + char *result = nob_temp_alloc(n + 1); + NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY"); + memcpy(result, cstr, n); + result[n] = '\0'; + return result; +} + +void *nob_temp_alloc(size_t size) +{ + if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL; + void *result = &nob_temp[nob_temp_size]; + nob_temp_size += size; + return result; +} + +char *nob_temp_sprintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + int n = vsnprintf(NULL, 0, format, args); + va_end(args); + + NOB_ASSERT(n >= 0); + char *result = nob_temp_alloc(n + 1); + NOB_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 nob_temp_reset(void) +{ + nob_temp_size = 0; +} + +size_t nob_temp_save(void) +{ + return nob_temp_size; +} + +void nob_temp_rewind(size_t checkpoint) +{ + nob_temp_size = checkpoint; +} + +const char *nob_temp_sv_to_cstr(Nob_String_View sv) +{ + char *result = nob_temp_alloc(sv.count + 1); + NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); + memcpy(result, sv.data, sv.count); + result[sv.count] = '\0'; + return result; +} + +int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) +{ +#ifdef _WIN32 + BOOL bSuccess; + + HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + if (output_path_fd == INVALID_HANDLE_VALUE) { + // NOTE: if output does not exist it 100% must be rebuilt + if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; + nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError())); + return -1; + } + FILETIME output_path_time; + bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); + CloseHandle(output_path_fd); + if (!bSuccess) { + nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError())); + return -1; + } + + for (size_t i = 0; i < input_paths_count; ++i) { + const char *input_path = input_paths[i]; + HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + if (input_path_fd == INVALID_HANDLE_VALUE) { + // NOTE: non-existing input is an error cause it is needed for building in the first place + nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError())); + return -1; + } + FILETIME input_path_time; + bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); + CloseHandle(input_path_fd); + if (!bSuccess) { + nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError())); + return -1; + } + + // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild + if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1; + } + + return 0; +#else + 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; + nob_log(NOB_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 + nob_log(NOB_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; +#endif +} + +int nob_needs_rebuild1(const char *output_path, const char *input_path) +{ + return nob_needs_rebuild(output_path, &input_path, 1); +} + +const char *nob_path_name(const char *path) +{ +#ifdef _WIN32 + const char *p1 = strrchr(path, '/'); + const char *p2 = strrchr(path, '\\'); + const char *p = (p1 > p2)? p1 : p2; // NULL is ignored if the other search is successful + return p ? p + 1 : path; +#else + const char *p = strrchr(path, '/'); + return p ? p + 1 : path; +#endif // _WIN32 +} + +bool nob_rename(const char *old_path, const char *new_path) +{ + nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); +#ifdef _WIN32 + if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { + nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError())); + return false; + } +#else + if (rename(old_path, new_path) < 0) { + nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); + return false; + } +#endif // _WIN32 + return true; +} + +bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) +{ + bool result = true; + + FILE *f = fopen(path, "rb"); + if (f == NULL) nob_return_defer(false); + if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); + long m = ftell(f); + if (m < 0) nob_return_defer(false); + if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); + + size_t new_count = sb->count + m; + if (new_count > sb->capacity) { + sb->items = realloc(sb->items, new_count); + NOB_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. + nob_return_defer(false); + } + sb->count = new_count; + +defer: + if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); + if (f) fclose(f); + return result; +} + +Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) +{ + size_t i = 0; + while (i < sv->count && sv->data[i] != delim) { + i += 1; + } + + Nob_String_View result = nob_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; +} + +Nob_String_View nob_sv_from_parts(const char *data, size_t count) +{ + Nob_String_View sv; + sv.count = count; + sv.data = data; + return sv; +} + +Nob_String_View nob_sv_trim_left(Nob_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[i])) { + i += 1; + } + + return nob_sv_from_parts(sv.data + i, sv.count - i); +} + +Nob_String_View nob_sv_trim_right(Nob_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { + i += 1; + } + + return nob_sv_from_parts(sv.data, sv.count - i); +} + +Nob_String_View nob_sv_trim(Nob_String_View sv) +{ + return nob_sv_trim_right(nob_sv_trim_left(sv)); +} + +Nob_String_View nob_sv_from_cstr(const char *cstr) +{ + return nob_sv_from_parts(cstr, strlen(cstr)); +} + +bool nob_sv_eq(Nob_String_View a, Nob_String_View b) +{ + if (a.count != b.count) { + return false; + } else { + return memcmp(a.data, b.data, a.count) == 0; + } +} + +bool nob_sv_end_with(Nob_String_View sv, const char *cstr) +{ + size_t cstr_count = strlen(cstr); + if (sv.count >= cstr_count) { + size_t ending_start = sv.count - cstr_count; + Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count); + return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr)); + } + return false; +} + +// RETURNS: +// 0 - file does not exists +// 1 - file exists +// -1 - error while checking if file exists. The error is logged +int nob_file_exists(const char *file_path) +{ +#if _WIN32 + // TODO: distinguish between "does not exists" and other errors + DWORD dwAttrib = GetFileAttributesA(file_path); + return dwAttrib != INVALID_FILE_ATTRIBUTES; +#else + struct stat statbuf; + if (stat(file_path, &statbuf) < 0) { + if (errno == ENOENT) return 0; + nob_log(NOB_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); + return -1; + } + return 1; +#endif +} + +const char *nob_get_current_dir_temp() +{ +#ifdef _WIN32 + DWORD nBufferLength = GetCurrentDirectory(0, NULL); + if (nBufferLength == 0) { + nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); + return NULL; + } + + char *buffer = (char*) nob_temp_alloc(nBufferLength); + if (GetCurrentDirectory(nBufferLength, buffer) == 0) { + nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); + return NULL; + } + + return buffer; +#else + char *buffer = (char*) nob_temp_alloc(PATH_MAX); + if (getcwd(buffer, PATH_MAX) == NULL) { + nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno)); + return NULL; + } + + return buffer; +#endif // _WIN32 +} + +bool nob_set_current_dir(const char *path) +{ +#ifdef _WIN32 + if (!SetCurrentDirectory(path)) { + nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError())); + return false; + } + return true; +#else + if (chdir(path) < 0) { + nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno)); + return false; + } + return true; +#endif // _WIN32 +} + +// minirent.h SOURCE BEGIN //////////////////////////////////////// +#ifdef _WIN32 +struct DIR +{ + HANDLE hFind; + WIN32_FIND_DATA data; + struct dirent *dirent; +}; + +DIR *opendir(const char *dirpath) +{ + assert(dirpath); + + char buffer[MAX_PATH]; + snprintf(buffer, MAX_PATH, "%s\\*", dirpath); + + DIR *dir = (DIR*)calloc(1, sizeof(DIR)); + + dir->hFind = FindFirstFile(buffer, &dir->data); + if (dir->hFind == INVALID_HANDLE_VALUE) { + // TODO: opendir should set errno accordingly on FindFirstFile fail + // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + errno = ENOSYS; + goto fail; + } + + return dir; + +fail: + if (dir) { + free(dir); + } + + return NULL; +} + +struct dirent *readdir(DIR *dirp) +{ + assert(dirp); + + if (dirp->dirent == NULL) { + dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); + } else { + if(!FindNextFile(dirp->hFind, &dirp->data)) { + if (GetLastError() != ERROR_NO_MORE_FILES) { + // TODO: readdir should set errno accordingly on FindNextFile fail + // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + errno = ENOSYS; + } + + return NULL; + } + } + + memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); + + strncpy( + dirp->dirent->d_name, + dirp->data.cFileName, + sizeof(dirp->dirent->d_name) - 1); + + return dirp->dirent; +} + +int closedir(DIR *dirp) +{ + assert(dirp); + + if(!FindClose(dirp->hFind)) { + // TODO: closedir should set errno accordingly on FindClose fail + // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + errno = ENOSYS; + return -1; + } + + if (dirp->dirent) { + free(dirp->dirent); + } + free(dirp); + + return 0; +} +#endif // _WIN32 +// minirent.h SOURCE END //////////////////////////////////////// + +#endif // NOB_IMPLEMENTATION + +#ifndef NOB_STRIP_PREFIX_GUARD_ +#define NOB_STRIP_PREFIX_GUARD_ + // NOTE: The name stripping should be part of the header so it's not accidentally included + // several times. At the same time, it should be at the end of the file so to not create any + // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end + // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the + // solution is to split the header into two parts where the name stripping part is at the + // end of the file after the NOB_IMPLEMENTATION. + #ifdef NOB_STRIP_PREFIX + #define TODO NOB_TODO + #define UNREACHABLE NOB_UNREACHABLE + #define UNUSED NOB_UNUSED + #define ARRAY_LEN NOB_ARRAY_LEN + #define ARRAY_GET NOB_ARRAY_GET + #define INFO NOB_INFO + #define WARNING NOB_WARNING + #define ERROR NOB_ERROR + #define NO_LOGS NOB_NO_LOGS + #define Log_Level Nob_Log_Level + #define minimal_log_level nob_minimal_log_level + // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function. + // So there should be no reason to strip the `nob_` prefix in this specific case. + // #define log nob_log + #define shift nob_shift + #define shift_args nob_shift_args + #define File_Paths Nob_File_Paths + #define FILE_REGULAR NOB_FILE_REGULAR + #define FILE_DIRECTORY NOB_FILE_DIRECTORY + #define FILE_SYMLINK NOB_FILE_SYMLINK + #define FILE_OTHER NOB_FILE_OTHER + #define File_Type Nob_File_Type + #define mkdir_if_not_exists nob_mkdir_if_not_exists + #define copy_file nob_copy_file + #define copy_directory_recursively nob_copy_directory_recursively + #define read_entire_dir nob_read_entire_dir + #define write_entire_file nob_write_entire_file + #define get_file_type nob_get_file_type + #define return_defer nob_return_defer + #define da_append nob_da_append + #define da_free nob_da_free + #define da_append_many nob_da_append_many + #define String_Builder Nob_String_Builder + #define read_entire_file nob_read_entire_file + #define sb_append_buf nob_sb_append_buf + #define sb_append_cstr nob_sb_append_cstr + #define sb_append_null nob_sb_append_null + #define sb_free nob_sb_free + #define Proc Nob_Proc + #define INVALID_PROC NOB_INVALID_PROC + #define Fd Nob_Fd + #define INVALID_FD NOB_INVALID_FD + #define fd_open_for_read nob_fd_open_for_read + #define fd_open_for_write nob_fd_open_for_write + #define fd_close nob_fd_close + #define Procs Nob_Procs + #define procs_wait nob_procs_wait + #define procs_wait_and_reset nob_procs_wait_and_reset + #define proc_wait nob_proc_wait + #define Cmd Nob_Cmd + #define Cmd_Redirect Nob_Cmd_Redirect + #define cmd_render nob_cmd_render + #define cmd_append nob_cmd_append + #define cmd_extend nob_cmd_extend + #define cmd_free nob_cmd_free + #define cmd_run_async nob_cmd_run_async + #define cmd_run_async_and_reset nob_cmd_run_async_and_reset + #define cmd_run_async_redirect nob_cmd_run_async_redirect + #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset + #define cmd_run_sync nob_cmd_run_sync + #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset + #define cmd_run_sync_redirect nob_cmd_run_sync_redirect + #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset + #define temp_strdup nob_temp_strdup + #define temp_alloc nob_temp_alloc + #define temp_sprintf nob_temp_sprintf + #define temp_reset nob_temp_reset + #define temp_save nob_temp_save + #define temp_rewind nob_temp_rewind + #define path_name nob_path_name + #define rename nob_rename + #define needs_rebuild nob_needs_rebuild + #define needs_rebuild1 nob_needs_rebuild1 + #define file_exists nob_file_exists + #define get_current_dir_temp nob_get_current_dir_temp + #define set_current_dir nob_set_current_dir + #define String_View Nob_String_View + #define temp_sv_to_cstr nob_temp_sv_to_cstr + #define sv_chop_by_delim nob_sv_chop_by_delim + #define sv_trim nob_sv_trim + #define sv_trim_left nob_sv_trim_left + #define sv_trim_right nob_sv_trim_right + #define sv_eq nob_sv_eq + #define sv_end_with nob_sv_end_with + #define sv_from_cstr nob_sv_from_cstr + #define sv_from_parts nob_sv_from_parts + #define sb_to_sv nob_sb_to_sv + #define win32_error_message nob_win32_error_message + #endif // NOB_STRIP_PREFIX +#endif // NOB_STRIP_PREFIX_GUARD_ + +/* + Revision history: + + 1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim) + Add nob_path_name() (By @0dminnimda) + 1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda) + 1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr) + 1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset() + Add nob_sb_to_sv() + Add nob_procs_wait_and_reset() + 1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin) + 1.5.0 (2024-10-23) Add nob_get_current_dir_temp() + Add nob_set_current_dir() + 1.4.0 (2024-10-21) Fix UX issues with NOB_GO_REBUILD_URSELF on Windows when you call nob without the .exe extension (By @pgalkin) + Add nob_sv_end_with (By @pgalkin) + 1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS + 1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr) + 1.3.0 (2024-10-17) Add NOB_UNREACHABLE + 1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr) + 1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX. + 1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable + Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names + Add NOB_UNUSED macro + Add NOB_TODO macro + Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part + 1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2 + 1.1.0 (2024-10-15) nob_minimal_log_level + nob_cmd_run_sync_and_reset + 1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h +*/ + +/* + Version Conventions: + + We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH: + - Modifying comments does not update the version. + - PATCH is incremented in case of a bug fix or refactoring without touching the API. + - MINOR is incremented when new functions and/or types are added in a way that does + not break any existing user code. We want to do this in the majority of the situation. + If we want to delete a certain function or type in favor of another one we should + just add the new function/type and deprecate the old one in a backward compatible way + and let them co-exist for a while. + - MAJOR update should be just a periodic cleanup of the deprecated functions and types + without really modifying any existing functionality. + + Naming Conventions: + + - All the user facing names should be prefixed with `nob_` or `NOB_` depending on the case. + - The prefixes of non-redefinable names should be strippable with NOB_STRIP_PREFIX (unless + explicitly stated otherwise like in case of nob_log). + - Internal functions should be prefixed with `nob__` (double underscore). +*/ + +/* + ------------------------------------------------------------------------------ + This software is available under 2 licenses -- choose whichever you prefer. + ------------------------------------------------------------------------------ + ALTERNATIVE A - MIT License + Copyright (c) 2024 Alexey Kutepov + 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. + ------------------------------------------------------------------------------ + 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. + ------------------------------------------------------------------------------ +*/ diff --git a/tests/arena.c b/tests/arena.c new file mode 100644 index 0000000..9ea9bb7 --- /dev/null +++ b/tests/arena.c @@ -0,0 +1,10 @@ +#define COMMONLIB_IMPLEMENTATION +#include "../commonlib.h" + +int main(void) { + c_Arena a = c_Arena_make(0); + + c_Arena_free(&a); + + return 0; +} diff --git a/tests/arena.exe b/tests/arena.exe new file mode 100644 index 0000000000000000000000000000000000000000..5c86fd3b77aa5d2b0b9c0f7276480e7f398b57d5 GIT binary patch literal 71339 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 literal 0 HcmV?d00001 diff --git a/tests/logging.c b/tests/logging.c new file mode 100644 index 0000000..1194b0d --- /dev/null +++ b/tests/logging.c @@ -0,0 +1,10 @@ +#define COMMONLIB_IMPLEMENTATION +#include "../commonlib.h" + +int main(void) { + c_log_info("This is an info log"); + c_log_error("This one is an error log"); + c_log_warning("And this is a warning log"); + + return 0; +} diff --git a/tests/logging.exe b/tests/logging.exe new file mode 100644 index 0000000000000000000000000000000000000000..9093b18b4e2c4214d45d79cf302300048318976d GIT binary patch literal 71851 zcmeEv3w&Hvo&UM>yqU>k@@mu4(rIaFN}DuklQxAGk|t>rNZXL451!L$GI>Z|CX+r8 zrCMC+)~;1t7grYQb5YUlDt|ykH-HM_t0E%0s9i+~0$LE)`sDxpo^$TZy>rthxVY+m zxSzTAp7T4u^EApV z^>+or|M7SCh~q-EgPD`!ToLwomWtUxs=@8S9nm*9GvJhdz{xi+zeIxLC_)25%9-*CJ}VwEIh~?^NxJjubtqgyepoJ0|>Ve6}ZV;6u0fF6=J-6L?K3*2LL3k1$f)8 zT7;DE8td!t>g%c|9C?!TO6f^owZt1%^rS3yB8+s;!)?22iMLnby~Ml@<0gOQxNTQ0 z@pg@l5;RV{{dBpn{me(7q9@Db2yiG9=G%7F5^rR5q!Tb%W=G*5-YVR-tCs1GB>IPd zD9i8|0?FT68xowgLabk2rCGL?^`7(^{AEU%90Rw1p%%Fo{9GzDHQ3bC0_Mv>Aso#wE~a)-6=t-M=kMI zD`6cVbSLnLZ&h-_aMcp8M)9{L0>Civ3h=%cx9zGW-Wru|l5~#fbiZmtf|Gbo$ZNj8 zRtYp6c&@bRaL#S{s5(arg=nlCuY9!f%M*9n-1per_odxU2fk4H4E;xJSIc4lN9PGq z`6!4|m7e)5D0g?4=gqr8ir_&TE}dJO4*mo@R5Tsj6q)!p;5HqqEonMjeCu*J4|XgB zbH(r1?r7a-o&pghv4N1o#oJ}T{Sz~xte=4{iiz)of|KSIX_^$x zdNs`l5l_)PC~3BX%N-fIriEb;6f00Z)IFXnJNGvOjTOmNELBau*mkH!y z;$`F^V9{Tv>H8=>vIMPT!RQ4^F%v z04=8po1AVblWCl#(>TkX#`h?9DJPadN=}P`137J&xX?;tOsBCforb+EYE&9O^XZJN7+H|t%(AEhgbgKE_mzxhBzn8j4#9N8Drg0*je*f~zH;j)hlm5fSJ9Uy@CSz7XB=aN@V1EGMXN@DESCJw|-3O_yCEaGCD7O129#P~^z<)y455C_zKDKdUJO`Mnx<5`S z=<5Dm?M8L4gJ4lLwl78K;o@GtK(Q1QRn`2tqyUyklSOh6s&%OeFVwh>;0X9{1^?Sv ze`{HPYaR9XkWjo-rFrSI@p4_7myL>-uVByvUY_I&yxf9#ikDrI0-A(FwGa^sg3ZIB zRx(lAWUz#yO$Vjd0^=>3mOKD?o>a)SX43_YC-V+`M5!@JnN5f12o*YPPW(eQQkC>& zv+)9C^CKmjr?hOc(_A2Gk&1e-l_CK->n&N2OH{T&9u-PfPr6v0DmT=)+AFTsIzs)R z)DNi_GJGVZJxIw4l??1hZm3^TV6b?abn$$p>5#YSP`e2!o`7&BQdRHViNj$EfKuFa z0_mJ;G9J#mOXPii97XYF3gfGWjrI0!jD)OeEZi^Z2D2H8@?YaSRa1~KOTq^!M4BcI)(kdW`j zlTs6;f`E?*eNsKZ0HXn>NI^3smn<|FK?IS*NQ%P>i^CRjD9JFfQ_r4jc@DQyGU-&P zTdglEkW>t$4A@)8le2UbC0b-7dhq_2A4R~ODnPR-nwBCLzkb4EAKFa7!TW&)fe{O@ zCQF*>W@HjxkuaGpQVE=%wNeKMz&kKupmlU{dUqz?;cRNT$|74;DP6FqxKlNR5>0c7 zK&#BhZ~Pz2lPl*j4H@@xkWc3F}`mLeF|Wz7FIvP7JSW@JKShv>C%)xpOU{`oC^P5PlWG!#Dr^+!&h)<-8v zOUeMo#^dN2pJ+PF_x%&2-^s~>irP0g&d}q@^TP_sWhrsnAaDT9I9JzQHxxoAWiBJ5|$_2t?=MHpq z<%iW+yY3#0ERpE_6W>8%H#Yv-O1lQ$yu08(@P%IX^?yqKDpP4Ik#%^5crowt@n7cU zr{(w!0DqYmaZ2))=JKIG=v+S3IKc+Xojo%{wmXolmTfA3rIc+{$~N3|=nC|n7!V!2 zYJ$yBsWmWpcAlkcs*9{?EFBBp!^sLdTE|G~J90?~0xY!z*yJ9Jp^#8m_f%A_SXouQ zYIW1SHESy3yE?lPC?!>}_awUO*4H;QZUE$mUut}rLnJv=dbS>%jLS^OcI3v!rx7Ic z|Lhz{0Q!J})=kXQV?M++z#;l}%s^-C{yy9%IeK{*ZnMFi=YdWu~8POoStVu=4kliCvqF|>cJl&$319|r z0_!c9k@%x{p>l*!z7q)*T!P^Kvt{rfh=t&18GA!Iwh?v2{whK&T|mQovrRN*gX;QuVc|Dz25_cHvCX80e;@IR2@zc<4_ zRrsee@IRK}|3HTSpB;WEduipP6I)SCFBjiEU>6OS(~e}Wuf0;goG16@ElDnn<}JBy zVMRmUl7@w~c(!2S>#+Y{&<%7q(1-{4el4a8}j-e0#1G_`Mc?6LJk+VNMAeJl0zj(ry(!@k|sRrqj~vn*qfLC=^Kgm*73X>K1au) z{UxpE-QCL9%*DI|>;7H&mMY)Jl`lv6 zKCFDdQ1Nb4zHchudzJ6g%6F6U{Udzcc|&tI!JC(l<(Z*HZPK|j0(aRq>F$jZ<#OrX zQBnD2i0ltpXboy04i?XsB(URRHSM3KiW#^&7D0)pYQP zF8;%>lQb$2@S;QyJyfd{<{{*lVaIf_H?i1jQS5tB?8j2Y{w1j&586uptpR|Z`6M2B z^J>8dn*QQ1L5H6S0dxeDd1znp^8{SMxQAMyTgBg|f1UD|!v8t?=P7>_{twe{D1Qn3 zhw1+@bIrw%gT-U?e^L3-_ZMG9|G&cDa>(DjgPM2=o=t~xTJU;?dCQx(UuF=U<$>RN zl6&@Sg`VE0oTM=;A}c@ z3NrOS0e{{kutRV(J>(w*44l3E>_4-8lk{zkn$5{z^jDAe+8agmSA9XA zccoB=l}LsIh=bp3I{4zmuYOCdd9mr>caekAE3STqjf$dwZ3g|2q-W*tncw{srJlaX zIj`0wdfys!cqu4%++Vw1lfYk20_Dq-iP7Zf^5x~_%R7e#28IUv`*xN0ibbR6FX}qK zB6>w#dwXMBNAs4=(G7LYEsYIVMG=HsY#rEjcsjP z+OCSKJS`d>?H?N%j&=9-C(b{=duTW@Sh`@*=z?X@1tYr_EQyv~2wI^K(sSUACKA__ zE*f1HtymUqZ*06I*0{MLjSSKm_{l_nKk(Bi&hUYFKX&~~7hOA+#+Z~pSGw(u9gZkY zy4@p*cvtC2VsxxOxhx9u%c7ObqTT5jd&DN0bhJAT0d!p?fD4n0?rxPjHp)kaqQmjg z(P*3|GJVOucz@ruiLPjTBrzDDFXCkvO6)6|H*eTNnJyabjMXtL3Q7{Qdt_*UNk@}I zd{LCr39} z#774E27449nn-j7pTid(-&=NYB>39(e@FrmOmcD4R<9Lr z8jhH7599rM+}UvN$NMqd5xDs-g%QXcxcPnp_CKX?^S!u8h$^`Gej0Zz+5KacyXaPwV<+2Lbw^ZhQ|Pr}Xj&u~8lH{Yx02=N@;e18P@OK|gD zfa$Gxy?U>MI~#7k--){fZoYqsdm-F>Z^!Ce1>Agp3-?;M`CiB6IJo)#H0}<#`7VmW zOb>3p@5kK_H{Y%Eh1d%>-yg$$1KfPiTYz~9+{ey=jBwuq_i?BkHH{UqGuc;Aov1l$6% zw`I7Wg1Z&(t+-DT{@V37Apyf@2;X`&!#BXOVS4>$o6l>sX1K$?tzmEA1D*<_)rIpe zji2pr%=QLHycM3EGQQ8_XNbgKp>e{#M$manijD8aa*t;yB`@K_#4eR%H z`u%qO{*Znj)9?HA`>Xo>n0`O0-%siHN&WWSq|z+X?>YKis^1m*y;i>)^t(mBJM{ZX z{qEK8q<-J1-?!-ZhxGfHe&46x59{}1`fW+#M27!0d03KB?}&)SI`_t7aeUv+0{1 zb&ez}L{h}C1Ti*ZAwDbO$sx?FpXa+zh+pvC-!J?IXiBSpA+9$%_x0@Q8;tc~*gKMs7*}>&iVw!teELOe8lAb&d5W zn&N|9{fRmtu+IO5ZsxpfMR`TJ%C;y1AD1+?ZEkE?xx#`m53gN+2?-biLu=mqov9nL z->H}cPP``EJB|wR4j+B$T6@vnf&S?3#K>sh(BQfSmE{!+qKU!Ip)RZ?ty{3QV?$ZZ zg6L=xDjDy`lGM5d`x2uIF1j$Z7HeUNfnELkqKH2@x^BVP$l&>-oxP|Qqh$kqog+h| zL*2i%c?nHmIpU-s*;-k%jyN9kx zj4W7gMOwT3v=rAax2L{#c`6f(yA~NAjwkze_4R|jsk7xkr?eCUYvaSi{e7JvG&GoE zX?|H*M{gq9+<;H^`iJ&lIkJl=39JI9v7@77!^1-($!Pb`NOU_;hW3m`xAmbUqh)0& zQW`1Ig)jb^yVfmuLt;fmWo1?M>axn}s#RrGD?6*p;?>=WvWkkDu0$fSvTJ2`#T#wn z`O|q>eMUZ3R#&d9*|jEKR^7D*e01;XDyvDX=qih^sH&=1v7)+r&C2+z!$-{-`KXGo z=w4BiSXoxRa@U%&s;a8WvR&~ton>9C)^t_HtKzE?D{EdQKFTZ4$Vp}Qs@1DHSFb9o zsYq0oRjpXLrfg01u3cp{E4wPXS9Mi%#aByCqOxW{1ON1*sjN67CpC$R?$zop0y4i(pr zRX<8%Cbn^-^_8pjPHwbppp(Zn^sgJ`JQm*M7)I(niNVB3Unjirk-jgW9oV6#}yAHzvfJOiS3C;zQ zqktqw;+=^RfwgmeaKI!jQo!_M*3g3?YEt;Q4IWDh&*L+{i6{XxD1*iZM=_1YPC9V{ zp5SONN3Bk%rjjBRi1?(tYiMj&e?kO|Kxc1!M3l@y?F#t(l9Dgzpy6voSWYV*o+Ye< zv&?{DrZ8l5AEWylwr^=`5Z=RcX$}|g`MUdu;z{9SB&4HxmG}XVUok6l7voCB$`#0< zZ&L}e{ju0s3bbVw$@w0^BR{oxg8XVBxFz9hBZ;joTQ+ZuwRW@#e`WdOgh=)j2ClxLT?y^uj7NPdpMWwP+R;w&@pfvo%B_hQ{>XpCj|p`es30K4P; zV~$+5*iG!P!zW{#eVF`b%OR!pzQNA%STu1YLs& z!G&l25D~It>-%~r=xDIo>_MaMjScc7j^OG-`ry4r1ZyM*rWdPFd-rsW0uUxk+1?n- z4c;PpBi@(_Jd3^}Z)~p!t_k;pN(q?7EBFm0cpV%vuS`3Z91shG>&z<5oRPoeKn!y~ z5xgkN$}u1VSojLoN{CP4^bU=Uh+v%rfhk5C=!)+X!SxaZwj^i>@#-Z6oDs4w0c8$0 zND$Z~XpfB7C_yC}OM*5?&>RKo8|@l}NH$7XRGoWbdV;Oy+a=%KgYl6bNafNH{UQ?U z8Xb!D%GD;(mL*|EB-TC1HN8>M5uzA`Clc!$<gc1QP*@3zFy_1Q$J7&jT}pJvKO~mpAf`2~u z7WkWx2QEwoUncv}%&u4@Kg%z6U;9p2d3E)o2dOnfIi$jyJt+zi8m?EG3Xv6pC& zQ}DO>-$$(LP#DttyZq+5n6uvGRnPEN(^WhDr23;qif4Oc^#+7!w#WD61WV;M#S+dW0^ z1fjh~taEpgtHZ%dvojQ^2|;JR!DzvFF+(^_kco)U-(!d!WW}psEM*TJXBE+6ZAZQh{CrGArj@E zoG8+(P!@FZ=_tfU1-~V8r5G2(C5?eu~g+ z5#*~2m^#^F)40O(c^J*vv;fuMYEMZ8_#t^N-Cq;ykF)%CdI~5isAzn&Gubzgh{+<0 zdm406Hne0=R8L^)Dz(8DfNpOi^rzeksftE2tx?Ah=EMtF9HA5DI3D4{T zFuIe~3c3PL(4C54w`T^c7W8UhS8`}irv>j(aOl>a&g9U*E(_kP;QPVcs;anRxX%;$ zIv9qIv6GDVE9mPyksknh1l%T)Xwwp`sv>xe;`MeW$SnfXY>l zkfaJZfsmCe93f*WWJ2eov&Kqzw+c9E1yruGLiVVTQz42ZIRuJU$X*qKd3umTOG;U4 z+AQU#e5F6A{6Dw+GevQ)ah+!&DymHrSBl`9JZBMThgON;^`4R{Ku?MwJ2pBc)wv=_ zwPk~${quW`;AI{!8^js9DZbW2{ijd2j@4qnhm0tecvORoTq1JV>dA^Ld<6SA?DEwj zg@Ea^2I3xHYy-#3m8&q6_r=gx!j;|Byt%!jZgYKOEGB&3U*l40lz)R)F zh%bdj*e{vFXYueiZQ9bX6?NbHcroMoa&j<^FTq2$R>C(I&M+IUWrV3liC)eEaLoQ-krWilR>eP&AwviQx>qPE2;t@Q2PZPUJzf+PF71IxIqq4B7k&V<6ts*BRT@ zH`+Od&C$>jBfAc%nQ}jJApSaR@`cV*uxthE8zgM0f<=t<_NUw{4V4*%OsoWi<$htP z(g-t8QA6%fhE^CM6W&t8+E5IwG{V0@j0&ThQ$0nc+Ayz2MruF>eP(F2ku@iRL4|=T zKQbypHAZ+o{7pue@~=_;R)a$n>gWivvew9=w00<90>iY>ItAKk;OI9C?Lr0Xl`x5Q zk%A0cd$yrEBYZxR+iMJuU=xT8)f=IU0NHQgTtVy5CGvLE&>9eGGI9oi zcDvEJ7yGJOAeR^m-UHwXqZj$brl<&QGQy{z024-hxDRMj=v$1eUT}X>!IaQj73?KV ze}xFO8#(U-ty7_lt=rI6BkKzYC^16AqlvMuAyfl2+E$*os~ko%LS^oEsIXEa)Cb)_ z)6bD;=rRSZFha>*j`N^`iIHwhlSSxi6;#6rsjcwPPD73pQHy*W4@o^{F{_?fDf3U@ zmp$i9uqt~~k96@NeC&dJe*}t;<7G*Jr64PUt_|nHAKZcmZ&{nY!q)+JP9GjNiu4L! z8ww{FM6dED3q2t6ZFL26fmAX1h$E3dZXv_Wg%z4|^`xq#=7dPOdS72_$x1O~oFpzI zsf+#B$uioE ziY((&K6V%tBCk{KOGd`Zl99QN574BMIkk)4Vui~=LGJd9g% zx&bdH1*n)j$20e{=hO=L@x>%&9iZnk+Poh$+I-Y(k@tWO{WehqYKv6C=ZW`MrGohw z_-w?Txkd6DaP4=tZWY1^8JT#-Eq?%}fwh=U5h|AMHu`ppa0nKG!njm;l&X2N;PvL> z;n6yn5=%WtUw>6%xd{D_2Y2R9PzwvvDV9o#B@^#e5zBl3>csL(h-3-otS8S=x({i{ z>o&JoR!}UNc(00Be(_f)mZu<+*;s+d+}C^2EtZuOOD5j`NGuPZquQ26lnuX$i`Fk) z`aj)HaqO#Ddn+T!WFNLlu*;Iz+nE@q84tHk;K$^r42!B|m|4T#Qo;UbJ28re8hDPz zVY9?|b5FDpO>363d&6jCVEC{P0{92vG?^RNf!osYeau^p zkfkOHOYS@s@_5;RHNo{SE8L7Yfz$-oUr~fp)fGU=%->dg3=lu#`N#3dGCaG6V1??> zL(34EjfYn<7LYqmB2Y3{3Tgdi9gQMzXcn0ZsGYIEVHuQVjc@#n=idej%lud3ImfoW zb=(Nv{VZS}ny2*)jr3t#A^4f_Lm0IRk6M`$!F$8Mga3p_tze1Zec^Y*KjGhlhA;A(#FQY{|KK#E>7k5V@Gng2#yyNV+te;_+v;*1iw@)FzQ7#OP}0BEWgau z*5=I}SjRROmXlGHeYw46Rw_t(!_EAU#k0_0WY;&!(bi^wQy z3AWMV)K&i_GPJe69b0%=R~a+=;D5+mut$*G`wBHmP>IHpq&7&<90gK) zU!jc>78PZ!V9xY}TFnd5@_Df3B=^2Tmxk!aCai7mE7X=Hfku96^DER5q98CBwsyZl z+a<(@)oB*eW>AqLng@+njX3s$LY?oGsmZO$P*(wcNSwQ0p+tZJgDklF73#^l6=9Kb zvKi_qt_h9^8J!g6f<_eLc3KeEVtC| zSLg>O&mr+}_2q{J8Xe11CoGlOBSQZr-6$5d`xW|8)>3ebLXo>)p&v_c0TS&Np{Mfi z1Oq4vYxgVk6B)bIqjtYSPZx}1rKZAT?S6%R7NX2*JXm1C?pNr!xj#mDEp$=teuaKM zH-JTlCa5ZRzd|qMEP=Nb(dF(}=*6t{@bB<&_bc=(nfOk4kemOOxfzBB+4;3(VlUCK z`xW|a{zk;Q4s}I(f0w@<-s{P<2>nqK*`MD13Ssw)#e?;3DFCb*VfTxW^zIi1=h#zTTvQvd0h4qB81&9HtWa4EWP9!^@nOarW8=G5xU&t zZ@_ZvT+_JH^Bl|{Z418O0b37jhXJ*3J%q6Jz~;|%Zasvs_3(9Y6Up3q2x04iRob=n z5W?02^Vn+NdI(|bfo-AQdI(|b;W|*=$(8T)ihl@O56ssv!L|ke5Vjr&&)j+lVe5hL z{kE-#5VjtE0Ma))wjM&*df<7{C<^=5LkL?B@ zJut|%^$^0=0|O@6dI(|bfkC#dhY+?N$dYU8A%v|5;<~pULfCp>$O+fhLkL?B44JTY z7_6;_5Vjr|aMB7$Z9Rmr^}vu*VU&Sm>mh`#2Zjhk%Zi!l}l^jhT_HMUBUz=}bJ-Yp~Tmk`;z$pqkvxc<+g1I+qU%UZQBsGZEHXu+qQhMp|g_ZIP3?OADe6Rvjvgi z*j%IkkCuNXY!=W&Q_W>Kw#nGUQqwFy@yjiLiHJ~VWZsc&IQ>)ye+Vjk+V!{v+yJ_vKz%dHMWKl!y>0lf=y4)$k5m@z8MlZxJmNb4SU8|7mUo3qatUONhN5-2ZJ`?Y6-m5GaSPv zHuf(%6J40lVD1&zBAZG#>$*{|;ur$3|8F0A_$jDor47x%GIq9vJVOLsV zX?pcC;40N{{5^C@H5}5FYJR-$K!EUHjk@UfV)Evv(qYu9W^kjh^7?m^6Gaa{E)9M< z?^4tn;gKd5cZN=(a76y-YTK>z2{RQI>~Iq>&B!eYeUDYq4kYd@2`))d zjG=Dj&JJG*qR})kCc$S(aH)d?(dUF-5A+HLT*a6xLtqjUlOu7plZj;WvW$Exv<1f= zrh?Ab(9~A10x!3LQ@9suV137ywpdGZdk1ktMj@vvAlzUFhZWd=3W?5(UDAG8dq?A@ zSV!}wMv)!4%on-85bb=+(4JOqLPxIfWpN2Y7)^aWy_FrXr4f-UeP%04%_m_i6znPm zGex$8V&rO{cMt?;#V*;3L(pRFn4mW{#2V^4>cl7Qbf9a=DUD= z!V`8-Uf&moM)&waA!O;8SI9nqh?QLJc?ex6JB1GbAT{zE$e=4HkzSwoJILhRvufX{989mOI!sph_SJ;qob*HTVq>0 z4yTjp1PCL+*tW{prY&1Hcf>mC*0(ga!@v+BGUu}JOnO@z+obVVmZlV|zieYeTl2QY zSZiD3hUOiJ7SchIi`a&(EiEEUQ1j;Ij#%T4`o>lsjMpx*T_hwwIRqmUq`bX(Gg#XQ z?QXj)CNrFCi{uW?lW{xR>R{c{v1Q{%SgJ&9pt|}Fa4PZ%($rg<8-N)DDY!%dLmGFG zKu4@$%ceS{U$?nI3PBXgXqy^Znm0F!BI#*rgqif_j`qfSQA_{|Cx(OkHgAb-Xxp-h zROPXLVulsix~08&2Xa@xwXM11vRGY5M_cpytsRYGriIbow5er%ohY&VTfv|t)(-w= zSW9{!F}n*YW0;df$z719bdh+WcnnK(y7 zq$I>b`Wknj>|5Km)Hk-bi$zupNE#op*R`MoE@|8>&Lx~8q=ga*EhYda08gE|yv zDM9tyI%1mu-qPC9E|w6|g7z;3Igb#ijFSCQD+V&JiyAJmjNrN!kc}z*-Bi~q$_RwR z1}Z0LLtA5`4Yr&x@YJ}uLsZZWrs=Mv8)q)ky+Xq`x8o?tR7J=Kbdr2?;kfBFiY94QMJT6LA3pRGpy6(sf0wWyq$w9d_6{5ty`5%YT>u$7uX! z+4^(L3-ix0%r2Y>W!P-H@?qP>&MDKjD<8I9-v-=n+m#R7F2d}#UHP!>BFtsml@Hr4 zCYG}8%7<+i2jtqeD<8I992ul-yYgY%^+v?7+jix{wyOnQg0}6-hi%vUfMVHp<-@k? zDEyXfS3YdJ=y%w5<-@j%Lw%cVS3YdJ2(sID<-@j%u(WMgK5V-PN!fPg!?x=)AfLAF z%7<;&=K*orcICsi>x+n(vhB);ZP)WabJ%v}!?x=W0H$rb@?qQc2xMZn?aGI3S2ZMH zx9!S@Z5Lr_+pc`rc6sm#(y3~GN01MLuBGsm7~x3_y7FPr#cV}0LTS*I4}&g-l^S82 zL03Kux(KZ>!kGqL`Bxf|F{Futuo-ma!=Q_Ez*;Qj@-1gxEL$}_SJSUP_~o3MCF*wn9XTZw&>42CUc&Ltru%5Ctgs?A0ki5 zNqrIYqDKwk@`e?ZbEaVoTf|GAocln*Oa*lI;?HfxuM{|v0uvbn-v3tck_F8;cvYP8 z#4g`lS!rXbitxtW;)h`MC%B8qo>?*)NW}2CM*PMVwQ{u!3xrA$zZ|zB zv`V4GaahcVqEql&kD19tr3(#&jKLHY;jaK|t2_5_RScUnqt}SSIg!6d=?CZY*qdE( zd_EC*+7qN?@;Y~kpLs%51Cb{;6Q1=HNs$TvI}n~W|0&ALqZ-wKs3=}xgwMmwV9nKt zX3V-WR0PlY^vsIog5UP!8vN07@5s=eShBu~?BxKVh*7YS^+V9j)|;xuRfgPT#d-pu zQWH7t@y;Tue>t8Kw%vkx=@L<<=dZ^j!U>Afc^Tr5!(WU^$-coj4hKPY_rW|q-i2kK zmD#KT=ftF4t5_qq6SKTxf2{zRaoR9G#JnNa+TOUeVGH^dcA$FtaX}8}BxgiI=ZJaA zS_?5u+lN9M9^dlOLxQ5D4n5UD6xV92cAMsP-*E6~Rv&0Lqu*5`9XVbh^% zWPxQ9JhNH`KgnR?so;guf_P?G&Mz#uD9}c=GZ)DS=6nlxhSMyEwwTKqm~N9~o-)vA zi@9xTG}>amacVT$Vt!~6v;xhWYELq@)P=bOJrKt!|-i!oD`zpp^%}}+Vq!)3{ zO~=_YS)6w|bST&sUrU8b9;2yH@F>XD&#e@MWa#WP!XAZ? zcaCOTtaBRy8k-C%L8irsam%7^v1#u8(BShaO5BUa#HKQ;$=|_q9kY&4mBvJy?2tYI z|GyG8wkJM1$b+uhO%FQohX8vJ_u@}3RMm?ba1;#9hNWwbSZtoFz^qqyYjB0Q@XZ>$ z5@2FhfJ70N!uK*SkY3Zsvf76 zi)ALL?FT&>rmA_SC)2(sNMlt~nRa*%&*gV29?P@?)a<6K9=j=!#kAY5BGS5T3#J&} zu#vxv)Kbgg9@}y_-D){Jbn%w;u`TN_Zp8d|GiG9Kn0CbKw`}fc+tL!-&{DUtU63j7 zKNJ?c3u{U7Z-Pw` z;A#dOk|$}o@X}m|8EQ5?TQ^^_dCT_AB8Q%a#`@+>buA*7o@T7fNenphm1Bh{ zP>w2Cz7nNI6e&QBD3$<_ouMFb&7@1JUkP1UwQ7f4#4LLF75!{F+Uqtn*0r_OT_(<= zw_dsD(9NYM{3{w3Q5wWt0$N&`&$E?tJKgi>R+C$7O~TF90@7YY56y|B?_B!0ZY9?| zYw26xvSq!fql+sAb(_R`<=)a(x3N*wrvoteRuw`tSQu$gBS9o(;cOtFeuLO3-z}Wp zH_@qA+z?YI*X(0VcoZiu=h43n^=?ZWT6%hvB(NGo#LKX{hM5fA5MyK8HtN-WdYfBX z8aJZQUVho;+f>(n2{07Zj>}pb34m9a{0>5XeQM+xpMn+!@5ZBuwct1$vO0LBiw}I( zi-4?A46G7e>Lf(LdXVL<*k3D$ggUG(5gPy?~OrOvQcY#Ffa!SGE7Og{DmLu4b% z8?oZ^fJs>EB=(AT%779bFhd%<+fIT+)Eh85_YzHn8~XcckPnp<;==@!WiNU2k`ED< z(c{U~L6VI2Wg>oqh^(7prs{>s0VX{o`-JB1hjbGI{{OU%FFz(N#)?Dc zxkML07}f_a7)rxE$+0UILTXqs#TPJ>j!iPZi}9PTFJnCG*(Dsv>p)?8)R^P&+}FdC z`dY@*qDBwz&3`k&&H>1Dznw|jUrzZMO{x>tT{VqyVWAA`>@If8WE8BXu*7limB28~ zMR;aewI2G*DwZjF;}&mkGtSEqksOjmz0#F5KkFzncPaeJszwxq?jmFd3_6e=)plJ7 z{=cf>?2FK@6|kbnJjv>IXc$ZjPd7U@f<|Tx;}&ro8?ek~FS7a9b)1obDPJw8eM@R( zce9Kdfa~|j=3m)yM%k@U8uj_~U+y@gh|S*nRXENlSJnRjW)I9=?rBdq3TB*c6wEx` zC}^E-6#TIF9jJdecS)UYRGRY8K%YVCz=_-7(x1nkWd=Wx^%eMGTddYHgZgwM0F>o^ z@T30M0l|5?QSf74Qc0a|WM-Uhl$Ck9k-6}!pCdvzW#MgBo4YZz*B0L9>O%V9y+)Y& zbfc7ox2aDz(iYyPKHW%Jc$+oqbR$}Ln`^>v02Q2WL~H-x50Ky?IAmU#HjJ#rLUWy2 z?~@tU7T%^l-3U3>7T%^l-AF;Cg}13sH_}Kn%Qp4tMjC`MnyF7WQV?n3ZR*pF6i8Zl zoBDJkjU`Fx(~T5JS$LbN(~ZU;7!ym(=KCby+QQqsG(^A1vs-w#Wl5Nkmoo6~2vH39 zdeXA*-Yx+&@21{;93-TC9H$$Zo#wA)T5?I&>?)uSg+u#pQ=e|6?7PjLtluLna<*i$ z_iTz5_T4_x4Q6Aw$6F`h%ZGa&ib2_Tn@O_{0BzrG>eG!_$guA=t<#N&Zk=vq{=oc} zYJ`QUPd9?4wJ`PRMoeG!Z`)*U8ZlvwIO?|qNwC^^b$}a!|C29L^Q=e|6?YqsV z3wC0)C2ikr>eG$1eYg4C+ye-&wd}jipU?d`{FZ&U`9jXu;Z50hn=fWP4ZpJQHh(1( z-zn|8O?|qNwC^_c=|1rI{YPNjPE%ZE-)+Lan+?S=98zPQZe-SYC?Cs0 z*33NJ$b^OL93DH9vXBL&h>)~}EFfnSqAg_2$xb&iVIjL8y|U9n)^we2WKMdzkvYlf zMkXv|-v^3m3t1BuvTTBO+AL&E=jlc!EM$pdw~#eqA$t^r(iXC&>vSX2dAgAa3)yx= zIg(|!kTqc;%UX8Iuz^<#6Be?pOTv>y%Q$5rYr;bI!=S*^jaVT{DEy7?_itZe%)7 zH!|^w!F(_Z9kYL8VB!-4RxRk2;}ZiDpBNAh-Ey36Wa1M8!uQ)gF);Co!S}&1tgyM1 zsZTdD@rl9nfF5yvVql);bR!d=7?7~*69W^U7%*UxPYg_aV!$BRCk7@yF<`(XpBR|< z#DGDzPYle{oo-~}69eKt0fTer=|(0#F<{6E*Cz%hJ~3d(1O>|P1*}gDOnhR%fRk20 z>JtMKpBOOYRF;mCI^D>`Ck70`AX%PnL@7&6)2ACzzS6HxH~NF+pD7A?y3tBhRGY>A zN@3!Y1On|)G{N{Jp&n3ux)Gg{sy^L_4Tkp5?=?)EZo~%BI%w`%5A~lu+t&+ZM6rCt zid-VH+3I=OOb8#rJ~n8+T2$1jPB#jtCWo9t;^?B2h;Es0pXPKU52jJRM}TKP-H7pg za}bif7!TQ62_H{TktYXUK-dFdPx#o5rA{{rWu9&nQl}dgI89)}=NP|$k*(bVCVZOH zjc}Gpb}Le|Tfl^20YjMG0wxR#7{Xi@Fkx80FtL;cOc)k0%#*f&3Bv+rCcJ42m@q70 z%n0o)(x)4Rou?axVF9xgD3%3G7={-W@LLuzVOYS>@34RgyH7U?Pjb3Z*mb&5IP-L) zFf3r2K|XB(6NUxMWl#qcALBf{16g zfC*1>x)BcektZIdEnvd1fcY3`oyy8M-6#xWm~SDV#K@Y&7$yv37%EaUBb3H4VHm?O ztklR#ZU5*5OkU1MSNS$tk?@#485w*z2nkDs^#jJW_rOYouQDo1_Q;KA7 z>Xj}&^yx-9aQZl2mIPP|vLfi(Pz`_ZYCL$$rU0iKb;6y!7Y`dndWA0zAw>Xz{;&Ags z-gCx;Q8Jg3$nw_iuN6p3EIez8a*(@YxO3z>@TED%rJf{qd6((nXFx{E;PJmI1Da8lF@S*Mn`VBanUUB zH$Mah=jSY*wN`@jnv6FZ*UOY5lJ5Km`)?%^0K=UcHh8<6Z8Bmz$zGbR@ znfLi7Q!6es!v6C?3uy%jm|+nI0MxOhm~SjH3&Y-dXU)qu3W5>*?Lf)7QeLwl9^`lS zOv9d$vp~_7uQ^CE7fGZtIyc3G&iUC$F;|7F+{`N$i;i3*CePd%#K8yZzvUD zZawJ?;$tDL;68VT{FA56l6Z|O?ENwu5e-n467>SdTC@K6N`W{Bet$~XnX2M$LefPP zD%R}_J|DHUTB!8d&bpqHsxG=-<-!0V)y!Q4t;0f|q%pt1YWh~pM}HM5HdvxsPh^1aCl^HtX{CwGhsTw#_0&~xosPhX3(Ofar5(l_&5Us5W zn3ZZKbnTyQ)xEP+CVW<-#umoZEsSc^(8RD&lMQGVQ(+ThwWzXDo0k%nrXG!0HDSI- zwaiLNjBPbE5;ET$G$FRNv$|wkd`?FGS-~8Qjmpg;XDw9q^;}0>U5OW`D{-m2PA*9| zu7k4uKF`_sE>%tPvU5>K%aBJ$%Zu-foek~sVxUzxTh&V0jiLBgR7>?;Y1g7TSP81~ z85f4bkfLg`6&ux>wCs1-Bdtm)4?vJum;!^%uBIj{2`fd-8c19AN3u;{BYC!UALncR zkc~W*rgPRIBTP~kq(Wq(O3rK9sIE(;Et5$%nineBvi-6urM)q<*H>i0ZNEbjM`l7MhXH_9u2w`ag#$0V=7sZv;rTu(IjweK%_?ezL{4`B*=aq< zp;LBG)W%%5R^+Ktk2o8I{FILC2BARK0x3x~?kSW^*$R{NVNGInT&i;G2p7$wZd-L* zce+aIs&2CwkrE?YA}75Pdy#7y&a%3v+2yMEGvdJlff$o@$uf`gJyzvMBg>j?>)s~o z$&`EonF7JUK3#4c2%1D>qn&j+6~ehluHlfUT*wid%)XU%Dw!s)Dh0@6t-_g})kUN< z=@pGL9ps^~=qjv|3uk6E+ZtZxSn8YW)E3<}m`bp$!Vzl_qIw2vyoruM61R^c3sr+_ zrbdxPlDGL1AO%`%DSEc0BT9Af{l6-|`Gp+JYW?`z8FQwKTlECGvdC^AU4s{;YB1E| zwd*e|fqzE&eqo9sybZ(nU2YCvoVnwMV_xtYxNpb(4cx!Q&0|CBaVK$mjCr2C@V^rE zlYIO?!ak3Om;XrQ`%k)lf`|X-eEc^a0UBNTc?@*Gk5kk<*3re@Lc{NEdEm33{jA>z zjQPFIm~R-ycBDt{R^mM=cb>lkr3Z0KPQ2XnHh#|skI^t>3dhc=l`9c22n-;NK+9fF z5X81<(&eWmeFI3`k6V)VJV%1RPEkE>Q4ND?pwMGXi{u!#mOh1Bk^DYf2LED1F=`!L z3Jd2~K(ZUH(+6=Yl3$0*_%0tZ?=6u!XfsoHj1G{m)|h z`srku-2rR8cjCSe_Y1h^V6|Wq?l|tlxW{qxk2=o5x_UQmne^;`gv+>tj|T~R3=i-3 z@$mhWkCS-#e~(ANgtd>qfREXD_!i(1T!M#L$;bJ4WNqZ5m5&{G1mkqQj*lcB#vAYm zy$uild+;!CVDY(_#;a_cD24yn4{i~%`T{goTI#TQ}3gr32 z-n^v0*jsq9KjIxrdIysJqG6zJE-ds%0{Py8q<8RQ`1ch0X9i~Y^SwDq|4i>_60WR^ z{kh)I=Ng!SB_ZR!UMKcO<70)R2=LGTzrGvWsiJAUl*wq_>BmQi@ll~}P zo+JKX;7R{tIK7YigN6PjDc5-^*RqsrGFVwEbh&mF`De)_J&@N)-jn72T%a1Smsvwz^C5~^<}rW%uz%LO{rPvbdar#PVVes_Lq$ z1jpWgyWX?dDD-=0+T@sjY0@7VBYlVw;v@x#u@GW}tRS$6Hv!RI3r{hm-U7@%#PIH0 z3(T>?BJ$zQH~hud*+hyoW}IH65T+FBS}jxN3KHcj@E$&Y!^aD!eRv{h3x24Zmkp<@ z65c2VL093vk%J+&qk&uCGH&PNPQu3dc$klG^C4ByvSr2L&1fTxK(ad-xEJq>(PX2v zpW-8MuYBS}Qy&(>_>^JYa2bIe@_9#~`&Kyofm};9t5)kfa2e0>A=9u-1!wuag_0)QYx%ErXtws9E(1Xx z=5a6XpWu#QM`0oE%W(faZkbNb=ioBF$p`;W!Si!GyuZc67v=(cF&}gB@N?h6UyetB z8w-B^e?_1P58bFPsa1{Y3?+h|B7X?7KxGQ`QIW4!-ku%)(7YYs91Ooo7rttTz5UBC zLmqph*+KKDri~A;)D=;jHE3@`Zp9mN(NLc$UJY=qUbi3}Pr6PQkMLpKR=;Xx@l5zf zfp2x1)4@Lue5*&C4!$fB-Q7)vPx({;$?7Y{(IxV#%Z=RNsmm=n;BCm=Z*2Rg?3+XH zHUBx_O?pD#`|cCp$t|gSFgH^7gNu`5<@X5aTMYn~o8+|&Pr^?wR^?<_^Ec$y zdbWi~wjIw+;buHHB8fB6KM6WkWki23uQTCCG0?Iq7NVXJpM16gKXX^fE}z<1S{#bn zYtKKo1Y$TFjVFc2!9(fMLq+cV)@wU$nyTaUCVnqJ2SbdTv@)dyka^2)Uki8E1^)e zXZfC$EAaPEb{3tfKx;`Acc(>Q`9Qq0w{H-HPA9_2B0);0kKmA3Dji1A^4BjM%FvbQ zF7FjM9~*I$8%G>=$ML^}>KrZv^1q{!qg`;NK+=*F0RE>Y;UI*Mci>XR4996RzzOji?68PG?j8Ee*8UnE{!PS0n)aS5tor6+`CU#;?t3EAjtPopx3jx4P&gD__NC zWQrt(ElHcxmfD%hbI-XQ-^$L2(SKP6Y_Mg(FN~7;H^rX(##7g0eed#1JIK9A}^d&d^ z^Jk6|&|ObkV#_<1F3lWu?BcnXG41dRRV-#FbShXyi)@TWz(r4@jf}&;9wFcOPAuPukOAc zxY0SwbDKsm3WHZ2ECdI#f*?3hoc7}TbGD+(X<8{im46jm!71zVBvr@Ghuv*2L#wp# zA5SboCB8lKC{{@A6?An>fF~-vnhn^Wvy>(szuPlmciScQ3S^?}o6F>pTxDHj0|WbB zMUD8g3TINzCk4I&ap>`K`nZX|6=_Vr`JGg&PAj5G`k4RSS)n>!&QTzf6;i50u-Ah# z%onXjCDngq^abheB-PC8+0z+T8eR+fYm~rjFvYD~>+*>Z{x~&Mfd8axyG>X-Bvz>X z?GxL8xozAqI(*~C=RDrgapC)%=O(|5>buGRF+V~+hBHUK!wC6g;J$!Fxi2stRMaIk zxtZa^VSShS`hVs%+7a<4zkGc%V6-CIc+hCq>H2Op?(_h76Nt*N`vNk|BYgh~4m=gN zA?UtcVq69jw$77YsUN#ia8?lPWK z06Qt^_C+rO_ZN}grMLOs@D2mMcPM)B-)wxCfDbEr0DRK;6ak-7^e(+^5_(<64#nRN z&;x%vEdJn^{Oz##13>b(!{TqpZGy|}ynG9HLlnL@+->l6xA6$J(R_~}Su zYV#G%lKIpSe0|}|Aw)BVzYs+7K|(;f4p^CD_2CGuHecbnD715z!nJf+njVW}+a-6+sLQKJHq+2vAZuxK; z2L>|0!49<0+jJHZ-tW?G8EOn-0~5Q*8P~1a&k(P{e=~PqcW4mz^}O;(NjKVOH4e6y zfkUdWx6HU6(td0f1BaOI>roBD_f^2*f0?_l9|2U05a!rxSo^Y0yyvwS8`!|%KCY9L zr7aZ*(b}@F2ZM^9GHA`NM30Ux?ZjH)Ig_^Jp{$23oRqexSWJCB!ZK1%7oZH8w3J_g zwopf=(iUog(iRx}W@s+}f>Pai2B}&S>jIvVSpQH@4gNys+%oco6Asq+g zVL+m&q-i>j0`h?W zzjQ!&?8kOg>U3HWl-{=-kh1|%}_{T=~J0Mp9(uyWR(cxsD zD)X@v#A>nk1ByaI9lQ-;tfT46YOz6B*THm{<(gKDJ(8}>U<+xLLlVxbT({~>3u*?O zx05Z$Wwq)H0af+%L%^YFlJoizy!(-YO3U5)S@pCRc&eUu$FE5^X;|`!BD#_fxpJ~( z(dnNCor-DDq4cNXr*;~2*b`4hr*#^1_D+M&&S}uOZW?rkr$J}`H0bP~2AvzGLFdRc z=-fCBI!C8L=k3#=b8H%Pj!c8j_%!Iedm40(PlL|Q)1Y%=8gy=%2Az}BpmTH@bT}xR zs=j|{8g%dx$&_?%p9Y=iH0azh4LTLmpmS^*bZVzT=gw)+X`Ke0d!|8W=QQZhPHw99 zaCjPY#-~AN|1{`)VH$LfOoPtD)1Y&78gzb|qN5iSGp2>FLq~c{(wTfdX!Vr}hkZ9H zqZ~2drLR|QETP8<9J8u%f@vj@dRyZJj!>tX{c+BR!5(-%fDr=*lo=Q2X`J(6AP1ap zWP~bjk^>D~%r$0+(w+HW9j(AU)eMp8ao*=OuE3KybY73b%Q>H#{Ta>yiAMbq-baCR zy73Rup2uB-d#c$V=l`ni=skB@z}PN+ICFd&x}k8GFE`7twr+4#sC2_;<5W2<-FOT* zQ|ZPFMV4;N)D)fMSXR%cbYq({Q2SW!J8@4{?!N(!D))A{$eHtEx#vUHs@z|225KM6 zeS8|_&T*zHcWNXe*e+{Kdp2_HD0kZO*sgbjmlM+{cZKu6Q|`AcTA()xe_r|eV>Q3L{wjJ`f`8oG|GJ(@|vpLZ<|KB|8g4T&PEN{<#Y}cJk1nr?*PXKP_WsLz8&i_ui9|Ip#wezQdGgY~-Dw(?6e}Vi@ zRo_oSC{vaDi{NLf`u-Fco~ph_!4He%Oy$lA$W-ON7dWciS)z}Vq~rPmAdzX5yTTzq z-o#Qt5I?^P4w;uc537%EC&jB6hW%chkg!#1PUar@pxX#E7 zV-?*=MEl-__a=ONquPY8o+HG37aqp|jbK&j#^*`Sb#Z*iQ6X!n*SOaeP5{RVQRHhX z{b>ZxN`DGLb+L=qLx3=xIr17ku5bc4miRUVsC4W*lk)r{;M5|7foIaO3h=CSY$q_m zob9s468wK^rDJx>rZnR7NaQ%$UbZch?Ftc-Ut>q{#`3b{3D}T&sRNP=NQDD(79b}z zWyRZ4Kv3N^9jnz+sWYcfBFw1`miF#M2x&~lp{2cyDLFLAne(z%IFCabN_(kS&dbtX zj__5hMP7;Ryr`M8@XvKhGfPmF<~tGKqh|7;Hl>;TWZ!*JGoJuhY3BAxaj2QC2$@PV zcYtA~nLkDh_e_df$G)QZaDKqG)p)5*8@}aXqAnYZrlxQ zH{a9^{;7@9jkjwqoG+I9e(*C@x%1y)RJjiz26O1VSnm918CC8@py<9>?)#@v?k5p@ zsvhkL;G}!BEiOS;12R61a#uJkcQ?zgcDbKG5>u6X)ila|Vxv{=PeY=UmplIzMwRKcaz6%srYd*-XNxNL4#c2FIxm(x|L;YWJ9mKH7t8(VG|D}Syq~t*we7)iS*C0| z%KNzAv~YDcN*WDd8d6Ptc0j6z5$=Gjx({h-h_;tttyCIy`hB8Q>avw^X4YzvSjtn; zIbr4E40N8F2Az}BpyNkQ(={dOswrKBVEl8s7ILhdy)DKGxMjhN?SIwv5YaGi#C4soKnMG+A}$F{rF_3_!AMGx=9Ts_x7LMfb)2 zbU8v)e~LzUl8g0=nlM%U;-3Yn`o$3=PporZ55YTzo1C7>R)l6Gst#_Rl;znLTVIwW~n)g)f$i0S~$X`Ey5aW9Ys{Fb-KRqchz_D z?5N#UwTR__tU7_bYlyDzT<<+yE_(6bxC!u`rqiA59UHtxeetg8=oYEf%7rtNQf97v zO@^cGTT(fl3`gaC1*T{!7Y1D!HBRBU^ImO}kIp-LnbYOnDxLqud{wdDOQ}v~<)!jf zy~8Ty)8nkZ!O{&JPM3N8`SKObOiKEHgeSF*r8L>x%BoEdrOE>PGM42P4_DXMjmVQK zizw(=(DeJ&&eu57kTq_Sw3zBGDSntQwzqD?j{%3HIMrgqGs*QCfLoCFGquOmRE;@T%bZGnKNNaVGi_Q@jo&7F4kGbfSxai#MqI08*&XX=Wb6j+e zy67Bn(Rs>6C+ebeyNk}vE;=V&bQZek9COh*>Z0Sz)ZW#lE;{$P=-lq2Q{yvI;UK8CR}u`bkS*X(c$3NM9kDo zv+AUaPOpnjtBa25qI1echcgOCTB|!;bRsS~A~UC}ZgkPv;i6OGqGP(~+~T5hrHf9~ zMJM8-!+k_&zIM9kaHY$EQ&r-kbIe7j%SETcMJMW_bDxV&uZs@<*w4vNsf*6TE;{`# zI<+o36)rlDWzwmt$;7E{%A~V;IGxrAj`@K$@xnqMbTuYF`4wU(CZ}>nFT=i#7;O2G z9Mu}vOYq&G=_p8q12J6mDabrP_Uh4>f|L`YIrQLD?vcdE$k52JwNotPUkIF|DI9_> z(Uj>{bS?*^)xqHaAZpGS1x_pm!h=Ji!hy37IL=%gCguG+@6Mw9VL~DnBzF1W9{*d9PEeO7eTU!L13ze`hQ zxWc&&5T~3U0OWQ@nLYuC)gq}_&y!9{@^Ir&V9bCVu_A!%a6r!HR6}1V_shHbTX$Q2 z@MA222N5!^3dFKj@Kow`PCEP}CH%`8uXG7ss&Ueg-3|!!IXTpi)At-wJp`O14xZn` zSgD+L>asZMko+CM*$LKEdEo@MZt^nhz`2KXQaB9%A|PqWcMp%?ET?X9!V&Aoz&YmN z=U0GOt*DZQ56SLS#F0SvXd;273m$n6Sw?j!=L4tLLFYU&iz_Xkt4PPu0$c-#)q|+C zb^~IqODM<@K-AifmDc+K8An<=!${*3fLJ}FiuE8M)}kJKnCR;X!J8?21W%fT zLmYEx!drpE@O0|$*J-68pV1J;_85!sbf%6rvyWl>9hAS0SWl#Kih9vc72ogBpYJ)+ z0v)O0M;vtIraGvn`8gLG3$mg9!{lB?m7WcyD(9iVU5N~EXKZLR)|0@AiG2eJY!4=c zgmBfHXQDD+ow3pWv611JTnX=tC5Og3d!;AV*B$E`N{kLB6)G_6MiPT@45;zhnB`IZ zi{<0r#U@i>veZC-cT9HNF`O@h0}AQy9vK>lbu!n|+dn)soOZ>=29tgLh?D5*>r^}= z$_Q{K4`FN_(%Bo=ByH$vVE;&BU}!g)CqcVMhq%coBp2L`AB&^wuo8F0vWNC3NBRad z4{4{(HDnV_atOzCQKaA)?lE}q_v&_UO{_QZ{|mde<+NcS>Mvs2P%_iUOkmnflaK-O zyrW`KTnwhzB+#$#*^BT^qz}nhLYKXqU9V(&b-L87kYuWEz7G;c8mbC#EvXhpR&qz$ zK`H(qICvxf=jd{|US})In9F&cB%fq}AtG%x3Hxp$ydod6O+32bX(7?-i?};JA{qSd9F~PnR(OD;+oJWT7xh0GMekEI$ zLPeSV^;E2Q@a2Y(!fT5}%*;vq2Rmk3e%ZR;#evG>PvEZdTY3XJGvsjQUQ9im@&>J1 zlSOJY+A@s7Y#=e08|ND3f$TGc2Pc!~XB^A!QlBO;3YRJ}Z|D;H1oI5d8X9CO0y z!`;H7U!~Jl`5y_1)y?C!vBx!#zHGCgfnZ+g4+7|cx69c3cJ^aleZ3^^j=ga8|2GDq!uvv$}Vk_g)};7E131d>Z;MjHDURwLm6di-yH-oKmykt zh4Jd^{~D7;TDuv@j`+Zqn0Py^)2TB%8bkpS{FfMH(|xH9t83iA-?S%;kACrf!3@IQ zM+8Ldyqh?)#VG5PJ*gf<-ZWRuLrJUL{Sq(D%d9q05M2`~7#%g?Gs)8lBg?h|rVw`! zch>q_Z+h9%>P^RR(Y=6<0(b)~BBO3AfF#oD0%(M_;>fSxUYkx?Hwt=AIH