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 "cJSON.h"
#include "dyn_array.h" #include "dyn_array.h"
enum PromptType { enum FieldType {
PT_STRING = 1, FT_STRING = 1,
}; };
struct Prompt { struct Field {
enum FieldType type;
const char *key; const char *key;
enum PromptType type; const char *prompt;
}; };
enum SpecValidationError { enum SpecValidationError {
SVE_NOT_TOPLEVEL_OBJECT = 1, // The top-level JSON value of a `spec.json` file must be a JSON object.
// The value of a top-level key does not correspond to one of the following: SVE_TOPLEVEL_NOT_OBJECT = 1,
// * "$STRING" // The field is not a JSON object.
SVE_INVALID_VALUE, SVE_FIELD_NOT_OBJECT,
SVE_FIELD_TYPE_INVALID,
SVE_FIELD_TYPE_UNKNOWN,
SVE_FIELD_PROMPT_INVALID,
}; };
enum SpecValidationError 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 */ #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; struct DynArray *prompts = 0;
switch (validate_spec_json(parsed, &prompts)) { 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); fprintf(stderr, "`%s/spec.json` is not a JSON object.\n", target);
goto cleanup_parsed; goto cleanup_parsed;
case SVE_INVALID_VALUE: case SVE_FIELD_NOT_OBJECT:
fprintf(stderr, "unknown value type found in `%s/spec.json`.\n", target); 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; goto cleanup_parsed;
} }
// TODO: Load in the curses interface.
// TODO: Run `run.sh`. // TODO: Run `run.sh`.
retval = EXIT_SUCCESS; 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 "parser.h"
#include "path.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) { static int find_spec_json(const struct Config *const config, FILE **handle) {
const char *segments[] = {config->root_dir, config->target, "spec.json"}; const char *segments[] = {config->root_dir, config->target, "spec.json"};
char *filepath = char *filepath =

View File

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

View File

@ -23,13 +23,17 @@ int main(int argc, char *argv[]) {
sput_run_test(test_join_path_multiple_segments); sput_run_test(test_join_path_multiple_segments);
sput_enter_suite("parser"); sput_enter_suite("parser");
sput_run_test(test_parse_spec_json_missing); sput_run_test(test_parser_missing);
sput_run_test(test_parse_spec_json_minimal); sput_run_test(test_parser_minimal);
sput_run_test(test_parse_spec_json_invalid); sput_run_test(test_parser_invalid);
sput_enter_suite("validator"); sput_enter_suite("validator");
sput_run_test(test_validate_spec_json_not_toplevel_object); sput_run_test(test_validator_toplevel_not_object);
sput_run_test(test_validate_spec_json_invalid_value_type); 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(); 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 A missing `spec.json` file is not an error. Our parsed @cJSON instance should
be set to NULL in this case. 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"); struct TestParserFixture *fixture = test_parser_setup("no_spec_json");
cJSON *parsed = 0; cJSON *parsed = 0;
@ -56,7 +56,7 @@ static void test_parse_spec_json_missing() {
test_parser_teardown(fixture); 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"); struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json");
cJSON *parsed = 0; cJSON *parsed = 0;
@ -67,7 +67,7 @@ static void test_parse_spec_json_minimal() {
test_parser_teardown(fixture); 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"); struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json");
cJSON *parsed = 0; cJSON *parsed = 0;

View File

@ -27,30 +27,80 @@ static void test_validator_teardown(struct TestValidatorFixture *fixture) {
free(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("[]"); struct TestValidatorFixture *fixture = test_validator_setup("[]");
enum SpecValidationError retval = enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts); 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); test_validator_teardown(fixture);
} }
static void test_validate_spec_json_invalid_value_type() { static void test_validator_field_not_object() {
struct TestValidatorFixture *fixture = struct TestValidatorFixture *fixture =
test_validator_setup("{\"key\": \"$UNKNOWN\"}"); test_validator_setup("{\"key\": \"$UNKNOWN\"}");
enum SpecValidationError retval = enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts); 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); 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 = 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 = enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts); validate_spec_json(fixture->parsed, &fixture->prompts);