Add an error interface for arbitrary length messages.
parent
2501754886
commit
ea71a0d661
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#ifndef _BOOTSTRAP_ERROR_H
|
||||
#define _BOOTSTRAP_ERROR_H
|
||||
|
||||
enum ErrorCode {
|
||||
SUCCESS = 0,
|
||||
#include <stdlib.h>
|
||||
|
||||
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 */
|
||||
|
|
|
@ -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.
|
||||
|
|
25
main.c
25
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cp -r template/* "$OUT"
|
|
@ -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
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) { return EXIT_SUCCESS; }
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
use flake
|
||||
echo "hello world"
|
62
src/config.c
62
src/config.c
|
@ -1,5 +1,6 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#include "error.h"
|
||||
|
||||
void error_free(struct Error *error) {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
free((void *)error->message);
|
||||
free(error);
|
||||
error = 0;
|
||||
}
|
|
@ -5,14 +5,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue