Convert fields into objects with properties.
parent
52efac43bd
commit
07f9853481
|
@ -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 */
|
|
@ -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
22
main.c
|
@ -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;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"test": "$STRING"
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 =
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue