Add an error interface for arbitrary length messages.
parent
2501754886
commit
ea71a0d661
|
@ -1,5 +1,11 @@
|
||||||
# bootstrap
|
# 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.
|
CLI utility for initializing projects in reproducible ways.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef _BOOTSTRAP_CONFIG_H
|
#ifndef _BOOTSTRAP_CONFIG_H
|
||||||
#define _BOOTSTRAP_CONFIG_H
|
#define _BOOTSTRAP_CONFIG_H
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
// The directory the `bootstrap` command was run from.
|
// The directory the `bootstrap` command was run from.
|
||||||
// OWNERSHIP: Does not own this pointer.
|
// OWNERSHIP: Does not own this pointer.
|
||||||
|
@ -13,20 +15,7 @@ struct Config {
|
||||||
const char *target;
|
const char *target;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigError {
|
struct Error *config_load(
|
||||||
// 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(
|
|
||||||
const char *cwd,
|
const char *cwd,
|
||||||
const char *root_dir,
|
const char *root_dir,
|
||||||
const char *target,
|
const char *target,
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
#ifndef _BOOTSTRAP_ERROR_H
|
#ifndef _BOOTSTRAP_ERROR_H
|
||||||
#define _BOOTSTRAP_ERROR_H
|
#define _BOOTSTRAP_ERROR_H
|
||||||
|
|
||||||
enum ErrorCode {
|
#include <stdlib.h>
|
||||||
SUCCESS = 0,
|
|
||||||
|
|
||||||
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_ENV_ROOT_DIR_INVALID,
|
||||||
ERROR_CONFIG_TARGET_NOT_FOUND,
|
ERROR_CONFIG_TARGET_NOT_FOUND,
|
||||||
|
ERROR_CONFIG_TARGET_INVALID,
|
||||||
ERROR_CONFIG_TARGET_NOT_DIR,
|
ERROR_CONFIG_TARGET_NOT_DIR,
|
||||||
|
|
||||||
ERROR_PARSER_SPEC_JSON_CANNOT_OPEN,
|
ERROR_PARSER_SPEC_JSON_CANNOT_OPEN,
|
||||||
|
@ -27,4 +30,55 @@ struct Error {
|
||||||
const char *message;
|
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 */
|
#endif /* _BOOTSTRAP_ERROR_H */
|
||||||
|
|
|
@ -9,7 +9,13 @@
|
||||||
* A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates
|
* A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates
|
||||||
* additional space as needed.
|
* 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.
|
* Create a new `StringBuf` instance.
|
||||||
|
|
25
main.c
25
main.c
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "error.h"
|
||||||
#include "evaluator.h"
|
#include "evaluator.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "validator.h"
|
#include "validator.h"
|
||||||
|
@ -16,26 +17,11 @@ static int run(const char *root_dir, const char *target) {
|
||||||
root_dir = getenv("BOOTSTRAP_ROOT_DIR");
|
root_dir = getenv("BOOTSTRAP_ROOT_DIR");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Error *error = 0;
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
switch (config_load(cwd, root_dir, target, &config)) {
|
|
||||||
case CE_ENV_CWD_INVALID:
|
if ((error = config_load(cwd, root_dir, target, &config))) {
|
||||||
fprintf(stderr, "Could not retrieve $CWD.\n");
|
fprintf(stderr, "%s", error->message);
|
||||||
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);
|
|
||||||
goto cleanup_cwd;
|
goto cleanup_cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +86,7 @@ cleanup_config:
|
||||||
|
|
||||||
cleanup_cwd:
|
cleanup_cwd:
|
||||||
free(cwd);
|
free(cwd);
|
||||||
|
error_free(error);
|
||||||
return retval;
|
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
|
#!/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 "config.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -7,41 +8,48 @@
|
||||||
|
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
|
|
||||||
enum ConfigError config_load(
|
struct Error *config_load(
|
||||||
const char *cwd,
|
const char *cwd,
|
||||||
const char *root_dir,
|
const char *root_dir,
|
||||||
const char *target,
|
const char *target,
|
||||||
struct Config **config
|
struct Config **config
|
||||||
) {
|
) {
|
||||||
if (cwd == 0) {
|
if (cwd == 0) {
|
||||||
return CE_ENV_CWD_INVALID;
|
return ERROR_NEW(ERROR_CONFIG_ENV_CWD_INVALID, "Could not retrieve $CWD.");
|
||||||
}
|
}
|
||||||
if (root_dir == 0) {
|
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) {
|
assert(target);
|
||||||
return CE_TARGET_INVALID;
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
if (!S_ISDIR(sb.st_mode)) {
|
||||||
{ // Check if the specified directory exists.
|
error = ERROR_NEW(
|
||||||
struct stat sb;
|
ERROR_CONFIG_TARGET_NOT_DIR, "Spec ", filepath, " is not a directory."
|
||||||
|
);
|
||||||
const char *segments[] = {root_dir, target};
|
goto cleanup;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*config = malloc(sizeof(struct Config));
|
*config = malloc(sizeof(struct Config));
|
||||||
|
@ -49,7 +57,9 @@ enum ConfigError config_load(
|
||||||
(*config)->root_dir = root_dir;
|
(*config)->root_dir = root_dir;
|
||||||
(*config)->target = target;
|
(*config)->target = target;
|
||||||
|
|
||||||
return 0;
|
cleanup:
|
||||||
|
free(filepath);
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_free(struct Config *config) {
|
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 <stdlib.h>
|
||||||
#include <string.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 *string_buf_new(size_t capacity) {
|
||||||
struct StringBuf *sb = malloc(sizeof(struct StringBuf));
|
struct StringBuf *sb = malloc(sizeof(struct StringBuf));
|
||||||
sb->buf = calloc(capacity, sizeof(char));
|
sb->buf = calloc(capacity, sizeof(char));
|
||||||
|
@ -27,12 +19,6 @@ size_t string_buf_size(struct StringBuf *sb) {
|
||||||
return sb->size;
|
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) {
|
void string_buf_cappend(struct StringBuf *sb, char c) {
|
||||||
assert(sb);
|
assert(sb);
|
||||||
|
|
||||||
|
|
|
@ -36,15 +36,17 @@ static void test_config_teardown(struct TestConfigFixture *fixture) {
|
||||||
static void test_config_load_invalid_args() {
|
static void test_config_load_invalid_args() {
|
||||||
struct TestConfigFixture *fixture = test_config_setup();
|
struct TestConfigFixture *fixture = test_config_setup();
|
||||||
|
|
||||||
|
struct Error *error = 0;
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval = 0;
|
|
||||||
|
|
||||||
retval = config_load(0, fixture->root_dir, fixture->target, &config);
|
error = config_load(0, fixture->root_dir, fixture->target, &config);
|
||||||
sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0");
|
sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0");
|
||||||
retval = config_load(fixture->cwd, 0, fixture->target, &config);
|
error_free(error);
|
||||||
sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0");
|
error = config_load(fixture->cwd, 0, fixture->target, &config);
|
||||||
retval = config_load(fixture->cwd, fixture->root_dir, 0, &config);
|
sput_fail_unless(
|
||||||
sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0");
|
error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0"
|
||||||
|
);
|
||||||
|
error_free(error);
|
||||||
|
|
||||||
test_config_teardown(fixture);
|
test_config_teardown(fixture);
|
||||||
}
|
}
|
||||||
|
@ -52,10 +54,14 @@ static void test_config_load_invalid_args() {
|
||||||
static void test_config_load_spec_not_found() {
|
static void test_config_load_spec_not_found() {
|
||||||
struct TestConfigFixture *fixture = test_config_setup();
|
struct TestConfigFixture *fixture = test_config_setup();
|
||||||
|
|
||||||
|
struct Error *error = 0;
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval =
|
|
||||||
config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
|
error = config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
|
||||||
sput_fail_unless(retval == CE_TARGET_NOT_FOUND, "target not found");
|
sput_fail_unless(
|
||||||
|
error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found"
|
||||||
|
);
|
||||||
|
error_free(error);
|
||||||
|
|
||||||
test_config_teardown(fixture);
|
test_config_teardown(fixture);
|
||||||
}
|
}
|
||||||
|
@ -63,10 +69,14 @@ static void test_config_load_spec_not_found() {
|
||||||
static void test_config_load_spec_not_dir() {
|
static void test_config_load_spec_not_dir() {
|
||||||
struct TestConfigFixture *fixture = test_config_setup();
|
struct TestConfigFixture *fixture = test_config_setup();
|
||||||
|
|
||||||
|
struct Error *error = 0;
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval =
|
|
||||||
config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
|
error = config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
|
||||||
sput_fail_unless(retval == CE_TARGET_NOT_DIR, "target not dir");
|
sput_fail_unless(
|
||||||
|
error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir"
|
||||||
|
);
|
||||||
|
error_free(error);
|
||||||
|
|
||||||
test_config_teardown(fixture);
|
test_config_teardown(fixture);
|
||||||
}
|
}
|
||||||
|
@ -74,11 +84,12 @@ static void test_config_load_spec_not_dir() {
|
||||||
static void test_config_load_success() {
|
static void test_config_load_success() {
|
||||||
struct TestConfigFixture *fixture = test_config_setup();
|
struct TestConfigFixture *fixture = test_config_setup();
|
||||||
|
|
||||||
|
struct Error *error = 0;
|
||||||
struct Config *config = 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->cwd, fixture->cwd) == 0, "config_load() cwd");
|
||||||
sput_fail_unless(
|
sput_fail_unless(
|
||||||
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"
|
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"
|
||||||
|
|
Loading…
Reference in New Issue