diff --git a/include/evaluator.h b/include/evaluator.h new file mode 100644 index 0000000..a8811e8 --- /dev/null +++ b/include/evaluator.h @@ -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 */ diff --git a/include/validator.h b/include/validator.h index 030fbdf..50736e6 100644 --- a/include/validator.h +++ b/include/validator.h @@ -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 */ diff --git a/main.c b/main.c index 1146a9d..16c946f 100644 --- a/main.c +++ b/main.c @@ -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; diff --git a/specs/clang/spec.json b/specs/clang/spec.json deleted file mode 100644 index bde20d8..0000000 --- a/specs/clang/spec.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test": "$STRING" -} diff --git a/src/evaluator.c b/src/evaluator.c new file mode 100644 index 0000000..5cbe954 --- /dev/null +++ b/src/evaluator.c @@ -0,0 +1,36 @@ +#include +#include + +#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; +} diff --git a/src/parser.c b/src/parser.c index 106e83e..6a6364c 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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 = diff --git a/src/validator.c b/src/validator.c index 87914f7..57eda71 100644 --- a/src/validator.c +++ b/src/validator.c @@ -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; } diff --git a/test/runner.c b/test/runner.c index 9488cae..6933d42 100644 --- a/test/runner.c +++ b/test/runner.c @@ -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(); diff --git a/test/test_parser.h b/test/test_parser.h index ed32429..676b32f 100644 --- a/test/test_parser.h +++ b/test/test_parser.h @@ -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; diff --git a/test/test_validator.h b/test/test_validator.h index a9a04a7..aae5a7d 100644 --- a/test/test_validator.h +++ b/test/test_validator.h @@ -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);