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 "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
22
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;
|
||||
|
|
|
@ -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 "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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue