diff --git a/README.md b/README.md index 4b1a13d..bef57de 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # bootstrap +TODO: +- [ ] Make free-ing data consistent with null pointers. +- [ ] Make error handling consistent. +- [ ] Add documentation throughout (ownership, docstrings, etc.). +- [ ] Have main.c return status code of run.sh. + CLI utility for initializing projects in reproducible ways. ## Overview diff --git a/include/config.h b/include/config.h index 7e6b21b..d02fca5 100644 --- a/include/config.h +++ b/include/config.h @@ -1,6 +1,8 @@ #ifndef _BOOTSTRAP_CONFIG_H #define _BOOTSTRAP_CONFIG_H +#include "error.h" + struct Config { // The directory the `bootstrap` command was run from. // OWNERSHIP: Does not own this pointer. @@ -13,20 +15,7 @@ struct Config { const char *target; }; -enum ConfigError { - // The $CWD could not be retrieved. - CE_ENV_CWD_INVALID = 1, - // The $BOOTSTRAP_ROOT_DIR environment variable is empty. - CE_ENV_ROOT_DIR_INVALID, - // The target argument is invalid. - CE_TARGET_INVALID, - // No spec with the given name was found. - CE_TARGET_NOT_FOUND, - // The spec is not a directory. - CE_TARGET_NOT_DIR, -}; - -enum ConfigError config_load( +struct Error *config_load( const char *cwd, const char *root_dir, const char *target, diff --git a/include/error.h b/include/error.h index 2b4fdac..620d695 100644 --- a/include/error.h +++ b/include/error.h @@ -1,12 +1,15 @@ #ifndef _BOOTSTRAP_ERROR_H #define _BOOTSTRAP_ERROR_H -enum ErrorCode { - SUCCESS = 0, +#include - ERROR_CONFIG_ENV_CWD_INVALID, +#include "string_buf.h" + +enum ErrorCode { + ERROR_CONFIG_ENV_CWD_INVALID = 1, ERROR_CONFIG_ENV_ROOT_DIR_INVALID, ERROR_CONFIG_TARGET_NOT_FOUND, + ERROR_CONFIG_TARGET_INVALID, ERROR_CONFIG_TARGET_NOT_DIR, ERROR_PARSER_SPEC_JSON_CANNOT_OPEN, @@ -27,4 +30,55 @@ struct Error { const char *message; }; +static inline struct Error *priv_error_new( + enum ErrorCode code, size_t n, const char *messages[static n] +) { + struct Error *e = malloc(sizeof(struct Error)); + e->code = code; + struct StringBuf *sb = string_buf_new(1024); + for (int i = 0; i < n; ++i) { + string_buf_sappend(sb, messages[i]); + } + e->message = string_buf_convert(sb); + return e; +} + +// clang-format off + +/** +Return the number of elements of `__VA_ARGS__`. + +Take the `__VA_ARGS__` list and append a list of decreasing numbers +31, 30, ..., 0. Then, by using ALEN0, return the 31st element of that list. +*/ +#define ALEN(...) \ + ALEN0( \ + __VA_ARGS__, \ + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, \ + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, \ + 0x0E, 0x0F, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, \ + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00) + +#define ALEN0( \ + _00, _01, _02, _03, _04, _05, _06, _07, \ + _08, _09, _0A, _0B, _0C, _0D, _0F, _0E, \ + _10, _11, _12, _13, _14, _15, _16, _17, \ + _18, _19, _1A, _1B, _1C, _1D, _1E, _1F, \ + ...) _1F + +/** +Create a new `struct Error` instance. +*/ +#define ERROR_NEW(code, ...) \ + ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__) + +#define ERROR_NEW0(code, nargs, ...) \ + priv_error_new( \ + (code), (nargs + 1), (const char * [nargs + 1]){__VA_ARGS__, "\n"} \ + ) + +// clang-format on + +void error_free(struct Error *error); + #endif /* _BOOTSTRAP_ERROR_H */ diff --git a/include/string_buf.h b/include/string_buf.h index 224270b..7f9336f 100644 --- a/include/string_buf.h +++ b/include/string_buf.h @@ -9,7 +9,13 @@ * A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates * additional space as needed. */ -struct StringBuf; +struct StringBuf { + char *buf; + // The length of @buf excluding `NUL`. + size_t size; + // The allocated size of @buf including `NUL`. + size_t _capacity; +}; /** * Create a new `StringBuf` instance. diff --git a/main.c b/main.c index eb50289..d2ffed9 100644 --- a/main.c +++ b/main.c @@ -5,6 +5,7 @@ #include "cJSON.h" #include "config.h" +#include "error.h" #include "evaluator.h" #include "parser.h" #include "validator.h" @@ -16,26 +17,11 @@ static int run(const char *root_dir, const char *target) { root_dir = getenv("BOOTSTRAP_ROOT_DIR"); } + struct Error *error = 0; struct Config *config = 0; - switch (config_load(cwd, root_dir, target, &config)) { - case CE_ENV_CWD_INVALID: - fprintf(stderr, "Could not retrieve $CWD.\n"); - goto cleanup_cwd; - case CE_ENV_ROOT_DIR_INVALID: - fprintf( - stderr, - "Either supply a value to `-d` or specify the $BOOTSTRAP_ROOT_DIR " - "environment variable.\n" - ); - goto cleanup_cwd; - case CE_TARGET_INVALID: - fprintf(stderr, "Spec `%s` is invalid.\n", target); - goto cleanup_cwd; - case CE_TARGET_NOT_FOUND: - fprintf(stderr, "Spec `%s` not found.\n", target); - goto cleanup_cwd; - case CE_TARGET_NOT_DIR: - fprintf(stderr, "Spec `%s` is not a directory.\n", target); + + if ((error = config_load(cwd, root_dir, target, &config))) { + fprintf(stderr, "%s", error->message); goto cleanup_cwd; } @@ -100,6 +86,7 @@ cleanup_config: cleanup_cwd: free(cwd); + error_free(error); return retval; } diff --git a/specs/clang/run.sh b/specs/clang/run.sh deleted file mode 100755 index ea3761b..0000000 --- a/specs/clang/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -cp -r template/* "$OUT" diff --git a/specs/clang/template/flake.nix b/specs/clang/template/flake.nix deleted file mode 100644 index 8d05f24..0000000 --- a/specs/clang/template/flake.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ - description = '' - An opinionated clang flake. - ''; - - inputs = { - flake-utils.url = "github:numtide/flake-utils"; - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - }; - - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - - codelldb = pkgs.writeShellScriptBin "codelldb" '' - exec ${pkgs.vscode-extensions.vadimcn.vscode-lldb}/share/vscode/extensions/vadimcn.vscode-lldb/adapter/codelldb "$@" - ''; - in - { - devShells.default = pkgs.mkShell.override { - # https://nixos.wiki/wiki/Using_Clang_instead_of_GCC - stdenv = pkgs.clangStdenv; - } { - packages = with pkgs; [ - bear - clang-tools - codelldb - ]; - buildInputs = with pkgs; [ - ncurses - ]; - }; - }); -} diff --git a/specs/clang/template/main.c b/specs/clang/template/main.c deleted file mode 100644 index 7cbc9bc..0000000 --- a/specs/clang/template/main.c +++ /dev/null @@ -1,3 +0,0 @@ -#include - -int main(int argc, char **argv) { return EXIT_SUCCESS; } diff --git a/specs/clang/template/.envrc b/specs/test/run.sh old mode 100644 new mode 100755 similarity index 52% rename from specs/clang/template/.envrc rename to specs/test/run.sh index b9238c3..c914ca6 --- a/specs/clang/template/.envrc +++ b/specs/test/run.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -use flake +echo "hello world" diff --git a/src/config.c b/src/config.c index 34b177b..1d4df64 100644 --- a/src/config.c +++ b/src/config.c @@ -1,5 +1,6 @@ #include "config.h" +#include #include #include #include @@ -7,41 +8,48 @@ #include "path.h" -enum ConfigError config_load( +struct Error *config_load( const char *cwd, const char *root_dir, const char *target, struct Config **config ) { if (cwd == 0) { - return CE_ENV_CWD_INVALID; + return ERROR_NEW(ERROR_CONFIG_ENV_CWD_INVALID, "Could not retrieve $CWD."); } if (root_dir == 0) { - return CE_ENV_ROOT_DIR_INVALID; + return ERROR_NEW( + ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "No specified root directory." + ); } - if (target == 0) { - return CE_TARGET_INVALID; + assert(target); + + // Check if the specified directory exists. + const char *segments[] = {root_dir, target}; + char *filepath = + join_path_segments(sizeof(segments) / sizeof(char *), segments); + + struct stat sb; + int stat_res = stat(filepath, &sb); + struct Error *error = 0; + + if (stat_res == -1) { + if (errno == ENOENT) { + error = ERROR_NEW( + ERROR_CONFIG_TARGET_NOT_FOUND, "Spec ", filepath, " not found." + ); + } else { + error = ERROR_NEW( + ERROR_CONFIG_TARGET_INVALID, "Spec ", filepath, " is invalid." + ); + } + goto cleanup; } - - { // Check if the specified directory exists. - struct stat sb; - - const char *segments[] = {root_dir, target}; - char *filepath = - join_path_segments(sizeof(segments) / sizeof(char *), segments); - - int stat_res = stat(filepath, &sb); - free(filepath); - - if (stat_res == -1) { - if (errno == ENOENT) { - return CE_TARGET_NOT_FOUND; - } - return CE_TARGET_INVALID; - } - if (!S_ISDIR(sb.st_mode)) { - return CE_TARGET_NOT_DIR; - } + if (!S_ISDIR(sb.st_mode)) { + error = ERROR_NEW( + ERROR_CONFIG_TARGET_NOT_DIR, "Spec ", filepath, " is not a directory." + ); + goto cleanup; } *config = malloc(sizeof(struct Config)); @@ -49,7 +57,9 @@ enum ConfigError config_load( (*config)->root_dir = root_dir; (*config)->target = target; - return 0; +cleanup: + free(filepath); + return error; } void config_free(struct Config *config) { diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..7ed0cb0 --- /dev/null +++ b/src/error.c @@ -0,0 +1,10 @@ +#include "error.h" + +void error_free(struct Error *error) { + if (!error) { + return; + } + free((void *)error->message); + free(error); + error = 0; +} diff --git a/src/string_buf.c b/src/string_buf.c index fdb98e7..cc2c7ce 100644 --- a/src/string_buf.c +++ b/src/string_buf.c @@ -5,14 +5,6 @@ #include #include -struct StringBuf { - char *buf; - // The length of @buf excluding `NUL`. - size_t size; - // The allocated size of @buf including `NUL`. - size_t _capacity; -}; - struct StringBuf *string_buf_new(size_t capacity) { struct StringBuf *sb = malloc(sizeof(struct StringBuf)); sb->buf = calloc(capacity, sizeof(char)); @@ -27,12 +19,6 @@ size_t string_buf_size(struct StringBuf *sb) { return sb->size; } -const char *string_buf_value(struct StringBuf *sb) { - assert(sb); - - return sb->buf; -} - void string_buf_cappend(struct StringBuf *sb, char c) { assert(sb); diff --git a/test/test_config.h b/test/test_config.h index 6512a72..722fa71 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -36,15 +36,17 @@ static void test_config_teardown(struct TestConfigFixture *fixture) { static void test_config_load_invalid_args() { struct TestConfigFixture *fixture = test_config_setup(); + struct Error *error = 0; struct Config *config = 0; - enum ConfigError retval = 0; - retval = config_load(0, fixture->root_dir, fixture->target, &config); - sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0"); - retval = config_load(fixture->cwd, 0, fixture->target, &config); - sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0"); - retval = config_load(fixture->cwd, fixture->root_dir, 0, &config); - sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0"); + error = config_load(0, fixture->root_dir, fixture->target, &config); + sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0"); + error_free(error); + error = config_load(fixture->cwd, 0, fixture->target, &config); + sput_fail_unless( + error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0" + ); + error_free(error); test_config_teardown(fixture); } @@ -52,10 +54,14 @@ static void test_config_load_invalid_args() { static void test_config_load_spec_not_found() { struct TestConfigFixture *fixture = test_config_setup(); + struct Error *error = 0; struct Config *config = 0; - enum ConfigError retval = - config_load(fixture->cwd, fixture->root_dir, "not_found", &config); - sput_fail_unless(retval == CE_TARGET_NOT_FOUND, "target not found"); + + error = config_load(fixture->cwd, fixture->root_dir, "not_found", &config); + sput_fail_unless( + error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found" + ); + error_free(error); test_config_teardown(fixture); } @@ -63,10 +69,14 @@ static void test_config_load_spec_not_found() { static void test_config_load_spec_not_dir() { struct TestConfigFixture *fixture = test_config_setup(); + struct Error *error = 0; struct Config *config = 0; - enum ConfigError retval = - config_load(fixture->cwd, fixture->root_dir, "not_dir", &config); - sput_fail_unless(retval == CE_TARGET_NOT_DIR, "target not dir"); + + error = config_load(fixture->cwd, fixture->root_dir, "not_dir", &config); + sput_fail_unless( + error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir" + ); + error_free(error); test_config_teardown(fixture); } @@ -74,11 +84,12 @@ static void test_config_load_spec_not_dir() { static void test_config_load_success() { struct TestConfigFixture *fixture = test_config_setup(); + struct Error *error = 0; struct Config *config = 0; - enum ConfigError retval = - config_load(fixture->cwd, fixture->root_dir, fixture->target, &config); - sput_fail_unless(retval == 0, "config_load() success"); + error = + config_load(fixture->cwd, fixture->root_dir, fixture->target, &config); + sput_fail_unless(error == 0, "config_load() success"); sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd"); sput_fail_unless( strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"