Add consistent error output.
parent
df65c8bcac
commit
80b1f4ed49
|
@ -2,10 +2,6 @@
|
|||
|
||||
CLI utility for defining custom project initialization scripts.
|
||||
|
||||
TODO:
|
||||
- [ ] Add evaluator tests.
|
||||
- [ ] Color output to console.
|
||||
|
||||
## Overview
|
||||
|
||||
`bootstrap` is a tool for quickly defining your own init-like scripts. If you
|
||||
|
|
|
@ -95,7 +95,7 @@ Take the `__VA_ARGS__` list and append a list of decreasing numbers
|
|||
__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, \
|
||||
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, \
|
||||
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00)
|
||||
|
||||
#define ALEN0( \
|
||||
|
@ -121,13 +121,20 @@ It is the responsibility of the caller to free the @ref 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"} \
|
||||
)
|
||||
#define ERROR_NEW0(code, nargs, ...) \
|
||||
priv_error_new((code), (nargs), (const char *[nargs]){__VA_ARGS__})
|
||||
|
||||
// clang-format on
|
||||
|
||||
#define ANSI_BLACK(...) "\e[0;30m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_RED(...) "\e[0;31m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_GREEN(...) "\e[0;32m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_YELLOW(...) "\e[0;33m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_BLUE(...) "\e[0;34m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_PURPLE(...) "\e[0;35m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_CYAN(...) "\e[0;36m", __VA_ARGS__, "\e[0m"
|
||||
#define ANSI_WHITE(...) "\e[0;37m", __VA_ARGS__, "\e[0m"
|
||||
|
||||
/**
|
||||
@brief Deallocates a previously allocated @ref Error isntance.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#define _BOOTSTRAP_VALIDATOR_H
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "config.h"
|
||||
#include "dyn_array.h"
|
||||
#include "error.h"
|
||||
|
||||
|
@ -52,6 +53,8 @@ struct Field {
|
|||
/**
|
||||
@brief Verify the `spec.json` file is formatted correctly.
|
||||
|
||||
@param config
|
||||
A reference to the parameters describing the desired spec.
|
||||
@param parsed
|
||||
A possible null pointer to the parsed `spec.json` file. If null, this method
|
||||
simply sets *fields to a null pointer.
|
||||
|
@ -62,7 +65,9 @@ struct Field {
|
|||
A null pointer if no error occurs. Otherwise an @ref Error pointer.
|
||||
*/
|
||||
struct Error *validate_spec_json(
|
||||
const cJSON *const parsed, struct DynArray **fields
|
||||
const struct Config *const config,
|
||||
const cJSON *const parsed,
|
||||
struct DynArray **fields
|
||||
);
|
||||
|
||||
#endif /* _BOOTSTRAP_VALIDATOR_H */
|
||||
|
|
2
main.c
2
main.c
|
@ -33,7 +33,7 @@ static int run(const char *root_dir, const char *target) {
|
|||
}
|
||||
|
||||
struct DynArray *prompts = 0;
|
||||
if ((error = validate_spec_json(parsed, &prompts))) {
|
||||
if ((error = validate_spec_json(config, parsed, &prompts))) {
|
||||
fprintf(stderr, "%s", error->message);
|
||||
goto cleanup_parsed;
|
||||
}
|
||||
|
|
30
src/config.c
30
src/config.c
|
@ -17,11 +17,19 @@ struct Error *config_new(
|
|||
assert(target);
|
||||
|
||||
if (cwd == 0) {
|
||||
return ERROR_NEW(ERROR_CONFIG_ENV_CWD_INVALID, "Could not retrieve $CWD.");
|
||||
return ERROR_NEW(
|
||||
ERROR_CONFIG_ENV_CWD_INVALID,
|
||||
ANSI_RED("ERROR"),
|
||||
": Could not retrieve ",
|
||||
ANSI_CYAN("CWD"),
|
||||
"."
|
||||
);
|
||||
}
|
||||
if (root_dir == 0) {
|
||||
return ERROR_NEW(
|
||||
ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "No specified root directory."
|
||||
ERROR_CONFIG_ENV_ROOT_DIR_INVALID,
|
||||
ANSI_RED("ERROR"),
|
||||
": Could not find root directory."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -35,18 +43,30 @@ struct Error *config_new(
|
|||
if (stat_res == -1) {
|
||||
if (errno == ENOENT) {
|
||||
error = ERROR_NEW(
|
||||
ERROR_CONFIG_TARGET_NOT_FOUND, "Spec ", filepath, " not found."
|
||||
ERROR_CONFIG_TARGET_NOT_FOUND,
|
||||
ANSI_RED("ERROR"),
|
||||
": Could not find ",
|
||||
ANSI_BLUE(target),
|
||||
" spec."
|
||||
);
|
||||
} else {
|
||||
error = ERROR_NEW(
|
||||
ERROR_CONFIG_TARGET_INVALID, "Spec ", filepath, " is invalid."
|
||||
ERROR_CONFIG_TARGET_INVALID,
|
||||
ANSI_RED("ERROR"),
|
||||
": ",
|
||||
ANSI_BLUE(target),
|
||||
" is invalid."
|
||||
);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
if (!S_ISDIR(sb.st_mode)) {
|
||||
error = ERROR_NEW(
|
||||
ERROR_CONFIG_TARGET_NOT_DIR, "Spec ", filepath, " is not a directory."
|
||||
ERROR_CONFIG_TARGET_NOT_DIR,
|
||||
ANSI_RED("ERROR"),
|
||||
": ",
|
||||
ANSI_CYAN(filepath),
|
||||
" is not a directory."
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
|
|
@ -23,17 +23,20 @@ static struct Error *find_run_exec(const struct Config *const config) {
|
|||
if (stat_res == -1 && errno == ENOENT) {
|
||||
return ERROR_NEW(
|
||||
ERROR_EVALUATOR_RUNNER_NOT_FOUND,
|
||||
"Could not find ",
|
||||
config->target,
|
||||
"/runner"
|
||||
ANSI_RED("NOT_FOUND"),
|
||||
": Could not find ",
|
||||
ANSI_BLUE(config->target, "/runner"),
|
||||
"."
|
||||
);
|
||||
}
|
||||
|
||||
if (!(sb.st_mode & S_IXUSR)) {
|
||||
return ERROR_NEW(
|
||||
ERROR_EVALUATOR_RUNNER_NOT_EXEC,
|
||||
config->target,
|
||||
"/runner is not executable."
|
||||
ANSI_RED("ERROR"),
|
||||
": ",
|
||||
ANSI_BLUE(config->target, "/runner"),
|
||||
" is not executable."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -42,20 +45,21 @@ static struct Error *find_run_exec(const struct Config *const config) {
|
|||
|
||||
static const char *prompt_field(struct Field *field) {
|
||||
assert(field);
|
||||
printf("%s", field->prompt);
|
||||
|
||||
char *response = calloc(1, 1024);
|
||||
|
||||
switch (field->type) {
|
||||
case FT_TEXT:
|
||||
printf("%s", field->prompt);
|
||||
// TODO: Probably want this buffer size to be a bit more dynamic.
|
||||
char *input = calloc(1, 1024);
|
||||
if (fgets(input, 1024, stdin)) {
|
||||
size_t len = strlen(input);
|
||||
if (len > 0 && input[len - 1] == '\n') {
|
||||
input[len - 1] = '\0';
|
||||
if (fgets(response, 1024, stdin)) {
|
||||
size_t len = strlen(response);
|
||||
if (len > 0 && response[len - 1] == '\n') {
|
||||
response[len - 1] = '\0';
|
||||
}
|
||||
return input;
|
||||
return response;
|
||||
} else {
|
||||
free(input);
|
||||
free(response);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +96,9 @@ int evaluate_runner(
|
|||
const char *response = prompt_field(field);
|
||||
if (!response) {
|
||||
*error = ERROR_NEW(
|
||||
ERROR_EVALUATOR_RESPONSE_INVALID, "Could not read in response."
|
||||
ERROR_EVALUATOR_RESPONSE_INVALID,
|
||||
ANSI_RED("ERROR"),
|
||||
": Could not read response."
|
||||
);
|
||||
string_buf_free(env_buf);
|
||||
return EXIT_FAILURE;
|
||||
|
|
11
src/parser.c
11
src/parser.c
|
@ -19,7 +19,11 @@ static struct Error *find_spec_json(
|
|||
*handle = fopen(filepath, "r");
|
||||
if (!*handle && errno != ENOENT) {
|
||||
error = ERROR_NEW(
|
||||
ERROR_PARSER_SPEC_JSON_INVALID, config->target, "/spec.json is invalid."
|
||||
ERROR_PARSER_SPEC_JSON_INVALID,
|
||||
ANSI_RED("ERROR"),
|
||||
": ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" is invalid."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -60,7 +64,10 @@ struct Error *parse_spec_json(
|
|||
if (!*parsed) {
|
||||
return ERROR_NEW(
|
||||
ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
|
||||
"The spec.json file contains invalid JSON."
|
||||
ANSI_RED("ERROR"),
|
||||
": ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" contains invalid JSON."
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,27 +4,44 @@
|
|||
|
||||
#include "string_utils.h"
|
||||
|
||||
static struct Error *read_field(const cJSON *const field, struct Field **out) {
|
||||
static struct Error *read_field(
|
||||
const struct Config *const config,
|
||||
const cJSON *const field,
|
||||
struct Field **out
|
||||
) {
|
||||
if (!cJSON_IsObject(field)) {
|
||||
return ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_NOT_OBJECT,
|
||||
"Field \"",
|
||||
field->string,
|
||||
"\" is not a JSON object."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" is not a JSON object."
|
||||
);
|
||||
}
|
||||
|
||||
if (isdigit(field->string[0])) {
|
||||
return ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_NAME_INVALID,
|
||||
"Field names may not begin with a digit."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" may not begin with a digit."
|
||||
);
|
||||
} else {
|
||||
for (const char *c = field->string; *c; ++c) {
|
||||
if (*c != '_' && !isalnum(*c)) {
|
||||
return ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_NAME_INVALID,
|
||||
"Field names must consist of alphanumeric characters or underscores."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" must consist of only alphanumeric characters and underscores."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -38,9 +55,14 @@ static struct Error *read_field(const cJSON *const field, struct Field **out) {
|
|||
if (!cJSON_IsString(type)) {
|
||||
error = ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_TYPE_INVALID,
|
||||
"Field \"",
|
||||
field->string,
|
||||
"\" has non-string \"type\"."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" has non-string ",
|
||||
ANSI_PURPLE("type"),
|
||||
"."
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -50,9 +72,14 @@ static struct Error *read_field(const cJSON *const field, struct Field **out) {
|
|||
} else {
|
||||
error = ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
|
||||
"Field \"",
|
||||
field->string,
|
||||
"\" has unknown \"type\"."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" has unknown ",
|
||||
ANSI_PURPLE("type"),
|
||||
"."
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -63,9 +90,14 @@ static struct Error *read_field(const cJSON *const field, struct Field **out) {
|
|||
} else {
|
||||
error = ERROR_NEW(
|
||||
ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
|
||||
"Field \"",
|
||||
field->string,
|
||||
"\" has non-string \"prompt\"."
|
||||
ANSI_RED("ERROR"),
|
||||
": Field ",
|
||||
ANSI_PURPLE(field->string),
|
||||
" in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" has non-string ",
|
||||
ANSI_PURPLE("prompt"),
|
||||
"."
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -78,7 +110,9 @@ cleanup:
|
|||
}
|
||||
|
||||
struct Error *validate_spec_json(
|
||||
const cJSON *const parsed, struct DynArray **fields
|
||||
const struct Config *const config,
|
||||
const cJSON *const parsed,
|
||||
struct DynArray **fields
|
||||
) {
|
||||
*fields = 0;
|
||||
|
||||
|
@ -90,7 +124,10 @@ struct Error *validate_spec_json(
|
|||
if (!cJSON_IsObject(parsed)) {
|
||||
return ERROR_NEW(
|
||||
ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT,
|
||||
"Top-level JSON value in spec.json is not an object."
|
||||
ANSI_RED("ERROR"),
|
||||
": Top-level JSON value in ",
|
||||
ANSI_BLUE(config->target, "/spec.json"),
|
||||
" is not an object."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -102,7 +139,7 @@ struct Error *validate_spec_json(
|
|||
cJSON *child = parsed->child;
|
||||
while (child) {
|
||||
struct Field *field = 0;
|
||||
error = read_field(child, &field);
|
||||
error = read_field(config, child, &field);
|
||||
if (error) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
#ifndef _BOOTSTRAP_TEST_VALIDATOR
|
||||
#define _BOOTSTRAP_TEST_VALIDATOR
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dyn_array.h"
|
||||
#include "sput.h"
|
||||
#include "string_utils.h"
|
||||
#include "validator.h"
|
||||
|
||||
struct TestValidatorFixture {
|
||||
const char *json;
|
||||
struct DynArray *prompts;
|
||||
cJSON *parsed;
|
||||
struct Config config;
|
||||
};
|
||||
|
||||
static struct TestValidatorFixture *test_validator_setup(const char *json) {
|
||||
|
@ -17,6 +21,15 @@ static struct TestValidatorFixture *test_validator_setup(const char *json) {
|
|||
fixture->json = json;
|
||||
fixture->prompts = 0;
|
||||
fixture->parsed = cJSON_Parse(json);
|
||||
|
||||
char *cwd = getcwd(0, 0);
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/');
|
||||
|
||||
fixture->config.cwd = cwd;
|
||||
fixture->config.root_dir = root_dir;
|
||||
fixture->config.target = "minimal_spec_json";
|
||||
|
||||
return fixture;
|
||||
}
|
||||
|
||||
|
@ -24,13 +37,16 @@ static void test_validator_teardown(struct TestValidatorFixture *fixture) {
|
|||
if (fixture->parsed) {
|
||||
cJSON_Delete(fixture->parsed);
|
||||
}
|
||||
free((void *)fixture->config.cwd);
|
||||
free((void *)fixture->config.root_dir);
|
||||
free(fixture);
|
||||
}
|
||||
|
||||
static void test_validator_toplevel_not_object() {
|
||||
struct TestValidatorFixture *fixture = test_validator_setup("[]");
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT, "top-level not object"
|
||||
);
|
||||
|
@ -43,7 +59,8 @@ static void test_validator_field_not_object() {
|
|||
struct TestValidatorFixture *fixture =
|
||||
test_validator_setup("{\"key\": \"$UNKNOWN\"}");
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_NOT_OBJECT, "field not object"
|
||||
);
|
||||
|
@ -61,7 +78,8 @@ static void test_validator_field_name_leading_digit() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_NAME_INVALID,
|
||||
"field name leading digit"
|
||||
|
@ -80,7 +98,8 @@ static void test_validator_field_name_non_alnum() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_NAME_INVALID, "field name non alnum"
|
||||
);
|
||||
|
@ -98,7 +117,8 @@ static void test_validator_field_type_invalid() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_TYPE_INVALID, "field type invalid"
|
||||
);
|
||||
|
@ -116,7 +136,8 @@ static void test_validator_field_type_unknown() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN, "field type unknown"
|
||||
);
|
||||
|
@ -135,7 +156,8 @@ static void test_validator_valid_type_ci() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(error == 0, "valid");
|
||||
|
||||
test_validator_teardown(fixture);
|
||||
|
@ -151,7 +173,8 @@ static void test_validator_field_prompt_invalid() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_VALIDATOR_FIELD_PROMPT_INVALID, "field prompt invalid"
|
||||
);
|
||||
|
@ -170,7 +193,8 @@ static void test_validator_valid() {
|
|||
"}"
|
||||
);
|
||||
|
||||
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
|
||||
struct Error *error =
|
||||
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||
sput_fail_unless(error == 0, "valid");
|
||||
|
||||
test_validator_teardown(fixture);
|
||||
|
|
Loading…
Reference in New Issue