Convert fields into objects with properties.

pull/9/head
Joshua Potter 2023-11-24 19:29:14 -07:00
parent 52efac43bd
commit 07f9853481
10 changed files with 194 additions and 54 deletions

16
include/evaluator.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _BOOTSTRAP_EVALUATOR_H
#define _BOOTSTRAP_EVALUATOR_H
#include "config.h"
#include "validator.h"
enum SpecEvaluationError {
// The provided input does not match the expected prompt response type.
SEE_INVALID_PROMPT_RESPONSE = 1
};
enum SpecEvaluationError evaluate_spec_json(
const struct Config *const config, const struct DynArray *const prompts
);
#endif /* _BOOTSTRAP_EVALUATOR_H */

View File

@ -4,23 +4,27 @@
#include "cJSON.h"
#include "dyn_array.h"
enum PromptType {
PT_STRING = 1,
enum FieldType {
FT_STRING = 1,
};
struct Prompt {
struct Field {
enum FieldType type;
const char *key;
enum PromptType type;
const char *prompt;
};
enum SpecValidationError {
SVE_NOT_TOPLEVEL_OBJECT = 1,
// The value of a top-level key does not correspond to one of the following:
// * "$STRING"
SVE_INVALID_VALUE,
// The top-level JSON value of a `spec.json` file must be a JSON object.
SVE_TOPLEVEL_NOT_OBJECT = 1,
// The field is not a JSON object.
SVE_FIELD_NOT_OBJECT,
SVE_FIELD_TYPE_INVALID,
SVE_FIELD_TYPE_UNKNOWN,
SVE_FIELD_PROMPT_INVALID,
};
enum SpecValidationError
validate_spec_json(const cJSON *const parsed, struct DynArray **prompts);
validate_spec_json(const cJSON *const parsed, struct DynArray **fields);
#endif /* _BOOTSTRAP_VALIDATOR_H */

22
main.c
View File

@ -50,15 +50,29 @@ static int run(const char *root_dir, const char *target) {
struct DynArray *prompts = 0;
switch (validate_spec_json(parsed, &prompts)) {
case SVE_NOT_TOPLEVEL_OBJECT:
case SVE_TOPLEVEL_NOT_OBJECT:
fprintf(stderr, "`%s/spec.json` is not a JSON object.\n", target);
goto cleanup_parsed;
case SVE_INVALID_VALUE:
fprintf(stderr, "unknown value type found in `%s/spec.json`.\n", target);
case SVE_FIELD_NOT_OBJECT:
fprintf(
stderr,
"Encountered child in `%s/spec.json` that is not a JSON object.\n",
target
);
goto cleanup_parsed;
case SVE_FIELD_TYPE_INVALID:
fprintf(stderr, "Types must be string values.\n");
goto cleanup_parsed;
case SVE_FIELD_TYPE_UNKNOWN:
fprintf(
stderr, "Encountered an unknown `type` in `%s/spec.json`.\n", target
);
goto cleanup_parsed;
case SVE_FIELD_PROMPT_INVALID:
fprintf(stderr, "Prompts must be string values.\n");
goto cleanup_parsed;
}
// TODO: Load in the curses interface.
// TODO: Run `run.sh`.
retval = EXIT_SUCCESS;

View File

@ -1,3 +0,0 @@
{
"test": "$STRING"
}

36
src/evaluator.c Normal file
View File

@ -0,0 +1,36 @@
#include <errno.h>
#include <stdio.h>
#include "evaluator.h"
#include "path.h"
static int find_run_sh(const struct Config *const config, FILE **handle) {
const char *segments[] = {config->root_dir, config->target, "run.sh"};
char *filepath =
join_path_segments(sizeof(segments) / sizeof(char *), segments);
int retval = 0;
// It is ok if the file does not exist. It is not ok if we couldn't open the
// file for any other reason.
*handle = fopen(filepath, "r");
if (!*handle && errno != ENOENT) {
retval = errno;
}
free(filepath);
return retval;
}
enum SpecEvaluationError evaluate_spec_json(
const struct Config *const config, const struct DynArray *const prompts
) {
if (prompts) {
for (int i = 0; i < prompts->size; ++i) {
}
// TODO: Display prompts and collect answers.
}
// TODO: Run `run.sh`.
return 0;
}

View File

@ -6,10 +6,6 @@
#include "parser.h"
#include "path.h"
/*
Returns a file pointer to the `spec.json` file if it exists. Returns 0
otherwise.
*/
static int find_spec_json(const struct Config *const config, FILE **handle) {
const char *segments[] = {config->root_dir, config->target, "spec.json"};
char *filepath =

View File

@ -2,24 +2,47 @@
#include "validator.h"
static struct Prompt *create_prompt(const cJSON *const kv) {
if (!kv->string) {
return 0;
static enum SpecValidationError
read_field(const cJSON *const field, struct Field **out) {
if (!cJSON_IsObject(field)) {
return SVE_FIELD_NOT_OBJECT;
}
if (cJSON_IsString(kv) && strcmp(kv->valuestring, "$STRING") == 0) {
struct Prompt *prompt = malloc(sizeof(struct Prompt));
prompt->key = kv->string;
prompt->type = PT_STRING;
return prompt;
enum SpecValidationError retval = 0;
*out = malloc(sizeof(struct Field));
const cJSON *type = cJSON_GetObjectItemCaseSensitive(field, "type");
if (!cJSON_IsString(type)) {
retval = SVE_FIELD_TYPE_INVALID;
goto cleanup;
}
return 0;
if (strcmp(type->valuestring, "STRING") == 0) {
(*out)->type = FT_STRING;
} else {
retval = SVE_FIELD_TYPE_UNKNOWN;
goto cleanup;
}
const cJSON *prompt = cJSON_GetObjectItemCaseSensitive(field, "prompt");
if (cJSON_IsString(prompt)) {
(*out)->prompt = prompt->valuestring;
} else {
retval = SVE_FIELD_PROMPT_INVALID;
goto cleanup;
}
return retval;
cleanup:
free(*out);
return retval;
}
enum SpecValidationError
validate_spec_json(const cJSON *const parsed, struct DynArray **prompts) {
*prompts = 0;
validate_spec_json(const cJSON *const parsed, struct DynArray **fields) {
*fields = 0;
if (!parsed) {
// Indicates no `spec.json` file was found.
@ -27,31 +50,31 @@ validate_spec_json(const cJSON *const parsed, struct DynArray **prompts) {
}
if (!cJSON_IsObject(parsed)) {
return SVE_NOT_TOPLEVEL_OBJECT;
return SVE_TOPLEVEL_NOT_OBJECT;
}
enum SpecValidationError retval = 0;
// `cJSON_GetArraySize` works because internally JSON objects are stored as
// arrays.
*prompts = dyn_array_new(cJSON_GetArraySize(parsed));
*fields = dyn_array_new(cJSON_GetArraySize(parsed));
cJSON *child = parsed->child;
while (child) {
struct Prompt *prompt = create_prompt(child);
if (!prompt) {
retval = SVE_INVALID_VALUE;
struct Field *field = 0;
retval = read_field(child, &field);
if (retval) {
goto cleanup;
}
dyn_array_push(*prompts, prompt);
dyn_array_push(*fields, field);
child = child->next;
}
return 0;
cleanup:
if (*prompts) {
dyn_array_free(*prompts);
*prompts = 0;
if (*fields) {
dyn_array_free(*fields);
*fields = 0;
}
return retval;
}

View File

@ -23,13 +23,17 @@ int main(int argc, char *argv[]) {
sput_run_test(test_join_path_multiple_segments);
sput_enter_suite("parser");
sput_run_test(test_parse_spec_json_missing);
sput_run_test(test_parse_spec_json_minimal);
sput_run_test(test_parse_spec_json_invalid);
sput_run_test(test_parser_missing);
sput_run_test(test_parser_minimal);
sput_run_test(test_parser_invalid);
sput_enter_suite("validator");
sput_run_test(test_validate_spec_json_not_toplevel_object);
sput_run_test(test_validate_spec_json_invalid_value_type);
sput_run_test(test_validator_toplevel_not_object);
sput_run_test(test_validator_field_not_object);
sput_run_test(test_validator_field_type_invalid);
sput_run_test(test_validator_field_type_unknown);
sput_run_test(test_validator_field_prompt_invalid);
sput_run_test(test_validator_valid);
sput_finish_testing();

View File

@ -45,7 +45,7 @@ static void test_parser_teardown(struct TestParserFixture *fixture) {
A missing `spec.json` file is not an error. Our parsed @cJSON instance should
be set to NULL in this case.
*/
static void test_parse_spec_json_missing() {
static void test_parser_missing() {
struct TestParserFixture *fixture = test_parser_setup("no_spec_json");
cJSON *parsed = 0;
@ -56,7 +56,7 @@ static void test_parse_spec_json_missing() {
test_parser_teardown(fixture);
}
static void test_parse_spec_json_minimal() {
static void test_parser_minimal() {
struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json");
cJSON *parsed = 0;
@ -67,7 +67,7 @@ static void test_parse_spec_json_minimal() {
test_parser_teardown(fixture);
}
static void test_parse_spec_json_invalid() {
static void test_parser_invalid() {
struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json");
cJSON *parsed = 0;

View File

@ -27,30 +27,80 @@ static void test_validator_teardown(struct TestValidatorFixture *fixture) {
free(fixture);
}
static void test_validate_spec_json_not_toplevel_object() {
static void test_validator_toplevel_not_object() {
struct TestValidatorFixture *fixture = test_validator_setup("[]");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_NOT_TOPLEVEL_OBJECT, "not top-level object");
sput_fail_unless(retval == SVE_TOPLEVEL_NOT_OBJECT, "top-level not object");
test_validator_teardown(fixture);
}
static void test_validate_spec_json_invalid_value_type() {
static void test_validator_field_not_object() {
struct TestValidatorFixture *fixture =
test_validator_setup("{\"key\": \"$UNKNOWN\"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_INVALID_VALUE, "invalid value");
sput_fail_unless(retval == SVE_FIELD_NOT_OBJECT, "field not object");
test_validator_teardown(fixture);
}
static void test_validate_spec_json_valid() {
static void test_validator_field_type_invalid() {
struct TestValidatorFixture *fixture = test_validator_setup("{"
" \"key\": {"
" \"type\": 2"
" }"
"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_TYPE_INVALID, "field type invalid");
test_validator_teardown(fixture);
}
static void test_validator_field_type_unknown() {
struct TestValidatorFixture *fixture =
test_validator_setup("{\"key\": \"$STRING\"}");
test_validator_setup("{"
" \"key\": {"
" \"type\": \"UNKNOWN\""
" }"
"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_TYPE_UNKNOWN, "field type unknown");
test_validator_teardown(fixture);
}
static void test_validator_field_prompt_invalid() {
struct TestValidatorFixture *fixture =
test_validator_setup("{"
" \"key\": {"
" \"type\": \"STRING\","
" \"prompt\": 2"
" }"
"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_PROMPT_INVALID, "field prompt invalid");
test_validator_teardown(fixture);
}
static void test_validator_valid() {
struct TestValidatorFixture *fixture =
test_validator_setup("{"
" \"key\": {"
" \"type\": \"STRING\","
" \"prompt\": \"What value for key?\""
" }"
"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);