Add command line parsing and test spec directory existence.
parent
aba0439858
commit
795307f2a1
|
@ -20,6 +20,10 @@ enum ConfigError {
|
|||
CE_ENV_ROOT_DIR_INVALID,
|
||||
// The target argument is invalid.
|
||||
CE_TARGET_INVALID,
|
||||
// No spec with the given name was found.
|
||||
CE_TARGET_NOT_FOUND,
|
||||
// The spec is not a directory.
|
||||
CE_TARGET_NOT_DIR,
|
||||
};
|
||||
|
||||
enum ConfigError config_load(
|
||||
|
|
106
main.c
106
main.c
|
@ -1,3 +1,4 @@
|
|||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
@ -7,76 +8,99 @@
|
|||
#include "parser.h"
|
||||
#include "validator.h"
|
||||
|
||||
const char *ENV_BOOTSTRAP_ROOT_DIR = "BOOTSTRAP_ROOT_DIR";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: bootstrap <spec>\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int retval = EXIT_SUCCESS;
|
||||
static int run(const char *root_dir, const char *target) {
|
||||
int retval = EXIT_FAILURE;
|
||||
char *cwd = getcwd(0, 0);
|
||||
const char *root_dir = getenv(ENV_BOOTSTRAP_ROOT_DIR);
|
||||
const char *target = argv[1];
|
||||
|
||||
// `cwd` must be free'd.
|
||||
if (!root_dir) {
|
||||
root_dir = getenv("BOOTSTRAP_ROOT_DIR");
|
||||
}
|
||||
|
||||
struct Config *config = 0;
|
||||
switch (config_load(cwd, root_dir, target, &config)) {
|
||||
case CE_ENV_CWD_INVALID:
|
||||
fprintf(stderr, "Could not retrieve the $CWD value.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto cwd_cleanup;
|
||||
fprintf(stderr, "Could not retrieve $CWD.\n");
|
||||
goto cleanup_cwd;
|
||||
case CE_ENV_ROOT_DIR_INVALID:
|
||||
fprintf(stderr, "Must specify $BOOTSTRAP_ROOT_DIR environment variable.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto cwd_cleanup;
|
||||
fprintf(
|
||||
stderr,
|
||||
"Either supply a value to `-d` or specify the $BOOTSTRAP_ROOT_DIR "
|
||||
"environment variable.\n"
|
||||
);
|
||||
goto cleanup_cwd;
|
||||
case CE_TARGET_INVALID:
|
||||
fprintf(stderr, "Target spec `%s` is invalid.", argv[1]);
|
||||
retval = EXIT_FAILURE;
|
||||
goto cwd_cleanup;
|
||||
fprintf(stderr, "Spec `%s` is invalid.\n", target);
|
||||
goto cleanup_cwd;
|
||||
case CE_TARGET_NOT_FOUND:
|
||||
fprintf(stderr, "Spec `%s` not found.\n", target);
|
||||
goto cleanup_cwd;
|
||||
case CE_TARGET_NOT_DIR:
|
||||
fprintf(stderr, "Spec `%s` is not a directory.\n", target);
|
||||
goto cleanup_cwd;
|
||||
}
|
||||
|
||||
// `config` must be free'd.
|
||||
|
||||
cJSON *parsed = 0;
|
||||
switch (parse_spec_json(config, &parsed)) {
|
||||
case SPE_CANNOT_OPEN:
|
||||
fprintf(stderr, "Found `spec.json` but could not open.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto config_cleanup;
|
||||
fprintf(stderr, "Cannot open `%s/spec.json`.\n", target);
|
||||
goto cleanup_config;
|
||||
case SPE_INVALID_SYNTAX:
|
||||
fprintf(stderr, "`spec.json` does not conform to bootstrap format.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto config_cleanup;
|
||||
fprintf(stderr, "`%s/spec.json` is not valid JSON.\n", target);
|
||||
goto cleanup_config;
|
||||
}
|
||||
|
||||
// `parsed` must be free'd.
|
||||
|
||||
struct DynArray *prompts = 0;
|
||||
switch (validate_spec_json(parsed, &prompts)) {
|
||||
case SVE_NOT_TOPLEVEL_OBJECT:
|
||||
fprintf(stderr, "`spec.json` top-level JSON value must be object.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto parsed_cleanup;
|
||||
fprintf(stderr, "`%s/spec.json` is not a JSON object.\n", target);
|
||||
goto cleanup_parsed;
|
||||
case SVE_INVALID_VALUE:
|
||||
fprintf(stderr, "Encountered unknown `spec.json` value type.");
|
||||
retval = EXIT_FAILURE;
|
||||
goto parsed_cleanup;
|
||||
fprintf(stderr, "unknown value type found in `%s/spec.json`.\n", target);
|
||||
goto cleanup_parsed;
|
||||
}
|
||||
|
||||
// TODO: Extract the prompts out of the `spec.json` file.
|
||||
// TODO: Load in the curses interface.
|
||||
// TODO: Run `run.sh`.
|
||||
|
||||
parsed_cleanup:
|
||||
retval = EXIT_SUCCESS;
|
||||
|
||||
cleanup_parsed:
|
||||
cJSON_Delete(parsed);
|
||||
|
||||
config_cleanup:
|
||||
cleanup_config:
|
||||
config_free(config);
|
||||
|
||||
cwd_cleanup:
|
||||
cleanup_cwd:
|
||||
free(cwd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char *root_dir = 0;
|
||||
char *target = 0;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "d:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
root_dir = optarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = optind; index < argc; index++) {
|
||||
if (target == 0) {
|
||||
target = argv[index];
|
||||
} else {
|
||||
fprintf(stderr, "Usage: bootstrap [-d <ROOT_DIR>] <spec>\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
fprintf(stderr, "Usage: bootstrap [-d <ROOT_DIR>] <spec>\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return run(root_dir, target);
|
||||
}
|
||||
|
|
24
src/config.c
24
src/config.c
|
@ -1,7 +1,10 @@
|
|||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "path.h"
|
||||
|
||||
enum ConfigError config_load(
|
||||
const char *cwd,
|
||||
|
@ -19,6 +22,27 @@ enum ConfigError config_load(
|
|||
return CE_TARGET_INVALID;
|
||||
}
|
||||
|
||||
{ // Check if the specified directory exists.
|
||||
struct stat sb;
|
||||
|
||||
const char *segments[] = {root_dir, target};
|
||||
char *filepath =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
int stat_res = stat(filepath, &sb);
|
||||
free(filepath);
|
||||
|
||||
if (stat_res == -1) {
|
||||
if (errno == ENOENT) {
|
||||
return CE_TARGET_NOT_FOUND;
|
||||
}
|
||||
return CE_TARGET_INVALID;
|
||||
}
|
||||
if (!S_ISDIR(sb.st_mode)) {
|
||||
return CE_TARGET_NOT_DIR;
|
||||
}
|
||||
}
|
||||
|
||||
*config = malloc(sizeof(struct Config));
|
||||
(*config)->cwd = cwd;
|
||||
(*config)->root_dir = root_dir;
|
||||
|
|
|
@ -9,12 +9,13 @@ int main(int argc, char *argv[]) {
|
|||
sput_start_testing();
|
||||
|
||||
sput_enter_suite("config");
|
||||
sput_run_test(test_config_load_root_dir_invalid);
|
||||
sput_run_test(test_config_load_target_invalid);
|
||||
sput_run_test(test_config_load_invalid_args);
|
||||
sput_run_test(test_config_load_spec_not_found);
|
||||
sput_run_test(test_config_load_spec_not_dir);
|
||||
sput_run_test(test_config_load_success);
|
||||
|
||||
sput_enter_suite("dyn_array");
|
||||
sput_run_test(test_dyn_array_empty);
|
||||
sput_run_test(test_dyn_array_zero_capacity);
|
||||
sput_run_test(test_dyn_array_nonzero_capacity);
|
||||
|
||||
sput_enter_suite("path");
|
||||
|
|
|
@ -1,58 +1,94 @@
|
|||
#ifndef _BOOTSTRAP_TEST_CONFIG
|
||||
#define _BOOTSTRAP_TEST_CONFIG
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "path.h"
|
||||
#include "sput.h"
|
||||
|
||||
static const char *SAMPLE_CWD = "/home/jrpotter/Documents/bootstrap";
|
||||
static const char *SAMPLE_ROOT_DIR = "/usr/local/share/specs";
|
||||
static const char *SAMPLE_TARGET = "example-target";
|
||||
struct TestConfigFixture {
|
||||
char *cwd;
|
||||
char *root_dir;
|
||||
char *target;
|
||||
};
|
||||
|
||||
static struct TestConfigFixture *test_config_setup() {
|
||||
char *cwd = getcwd(0, 0);
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
struct TestConfigFixture *fixture = malloc(sizeof(struct TestConfigFixture));
|
||||
fixture->cwd = getcwd(0, 0);
|
||||
fixture->root_dir = root_dir;
|
||||
fixture->target = "minimal_spec_json";
|
||||
|
||||
return fixture;
|
||||
}
|
||||
|
||||
static void test_config_teardown(struct TestConfigFixture *fixture) {
|
||||
free(fixture->cwd);
|
||||
free(fixture->root_dir);
|
||||
free(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_invalid_args() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
/*
|
||||
A valid $CWD environment variable value must be supplied.
|
||||
*/
|
||||
static void test_config_load_cwd_invalid() {
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval =
|
||||
config_load(0, SAMPLE_ROOT_DIR, SAMPLE_TARGET, &config);
|
||||
enum ConfigError retval = 0;
|
||||
|
||||
retval = config_load(0, fixture->root_dir, fixture->target, &config);
|
||||
sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0");
|
||||
}
|
||||
|
||||
/*
|
||||
A valid $BOOTSTRAP_ROOT_DIR environment variable value must be supplied.
|
||||
*/
|
||||
static void test_config_load_root_dir_invalid() {
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval = config_load(SAMPLE_CWD, 0, SAMPLE_TARGET, &config);
|
||||
retval = config_load(fixture->cwd, 0, fixture->target, &config);
|
||||
sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0");
|
||||
}
|
||||
|
||||
/*
|
||||
A valid target value must be supplied.
|
||||
*/
|
||||
static void test_config_load_target_invalid() {
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval =
|
||||
config_load(SAMPLE_CWD, SAMPLE_ROOT_DIR, 0, &config);
|
||||
retval = config_load(fixture->cwd, fixture->root_dir, 0, &config);
|
||||
sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0");
|
||||
|
||||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
/*
|
||||
If the supplied arguments to `config_load` are non-NULL, we should be able to
|
||||
successfully construct a new @Config instance.
|
||||
*/
|
||||
static void test_config_load_success() {
|
||||
static void test_config_load_spec_not_found() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval =
|
||||
config_load(SAMPLE_CWD, SAMPLE_ROOT_DIR, SAMPLE_TARGET, &config);
|
||||
config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
|
||||
sput_fail_unless(retval == CE_TARGET_NOT_FOUND, "target not found");
|
||||
|
||||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_spec_not_dir() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval =
|
||||
config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
|
||||
sput_fail_unless(retval == CE_TARGET_NOT_DIR, "target not dir");
|
||||
|
||||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_success() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Config *config = 0;
|
||||
enum ConfigError retval =
|
||||
config_load(fixture->cwd, fixture->root_dir, fixture->target, &config);
|
||||
|
||||
sput_fail_unless(retval == 0, "config_load() success");
|
||||
sput_fail_unless(strcmp(config->root_dir, SAMPLE_ROOT_DIR) == 0,
|
||||
"config_load() root_dir");
|
||||
sput_fail_unless(strcmp(config->target, SAMPLE_TARGET) == 0,
|
||||
"config_load() target");
|
||||
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd");
|
||||
sput_fail_unless(
|
||||
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"
|
||||
);
|
||||
sput_fail_unless(
|
||||
strcmp(config->target, fixture->target) == 0, "config_load() target"
|
||||
);
|
||||
|
||||
config_free(config);
|
||||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
#endif /* _BOOTSTRAP_TEST_CONFIG */
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
/*
|
||||
A @DynArray with zero capacity can be instantiated and have entries pushed onto.
|
||||
*/
|
||||
static void test_dyn_array_empty() {
|
||||
static void test_dyn_array_zero_capacity() {
|
||||
struct DynArray *a = dyn_array_new(0);
|
||||
sput_fail_unless(a->size == 0, "a->size == 0");
|
||||
sput_fail_unless(a->_capacity == 1, "a->_capacity == 1");
|
||||
|
|
|
@ -9,76 +9,75 @@
|
|||
#include "path.h"
|
||||
#include "sput.h"
|
||||
|
||||
struct TestParserFixture {
|
||||
char *cwd;
|
||||
char *root_dir;
|
||||
const char *target;
|
||||
struct Config config;
|
||||
};
|
||||
|
||||
static struct TestParserFixture *test_parser_setup(const char *target) {
|
||||
char *cwd = getcwd(0, 0);
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
struct TestParserFixture *fixture = malloc(sizeof(struct TestParserFixture));
|
||||
fixture->cwd = getcwd(0, 0);
|
||||
fixture->root_dir = root_dir;
|
||||
fixture->target = target;
|
||||
|
||||
// Reproduce in `Config` instance for convenience.
|
||||
fixture->config.cwd = fixture->cwd;
|
||||
fixture->config.root_dir = fixture->root_dir;
|
||||
fixture->config.target = fixture->target;
|
||||
|
||||
return fixture;
|
||||
}
|
||||
|
||||
static void test_parser_teardown(struct TestParserFixture *fixture) {
|
||||
free(fixture->cwd);
|
||||
free(fixture->root_dir);
|
||||
free(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() {
|
||||
char *cwd = getcwd(0, 0);
|
||||
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
struct Config config = {
|
||||
cwd,
|
||||
root_dir,
|
||||
"no_spec_json",
|
||||
};
|
||||
struct TestParserFixture *fixture = test_parser_setup("no_spec_json");
|
||||
|
||||
cJSON *parsed = 0;
|
||||
enum SpecParseError retval = parse_spec_json(&config, &parsed);
|
||||
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
|
||||
sput_fail_unless(retval == 0, "no spec.json, success");
|
||||
sput_fail_unless(parsed == 0, "no spec.json, no parsed");
|
||||
|
||||
free(cwd);
|
||||
free(root_dir);
|
||||
test_parser_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_parse_spec_json_minimal() {
|
||||
char *cwd = getcwd(0, 0);
|
||||
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
struct Config config = {
|
||||
cwd,
|
||||
root_dir,
|
||||
"minimal_spec_json",
|
||||
};
|
||||
struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json");
|
||||
|
||||
cJSON *parsed = 0;
|
||||
enum SpecParseError retval = parse_spec_json(&config, &parsed);
|
||||
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
|
||||
sput_fail_unless(retval == 0, "minimal spec.json, success");
|
||||
sput_fail_unless(parsed != 0, "minimal spec.json, parsed");
|
||||
|
||||
free(cwd);
|
||||
free(root_dir);
|
||||
test_parser_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_parse_spec_json_invalid() {
|
||||
char *cwd = getcwd(0, 0);
|
||||
|
||||
const char *segments[] = {cwd, "test", "specs"};
|
||||
char *root_dir =
|
||||
join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||
|
||||
struct Config config = {
|
||||
cwd,
|
||||
root_dir,
|
||||
"invalid_spec_json",
|
||||
};
|
||||
struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json");
|
||||
|
||||
cJSON *parsed = 0;
|
||||
enum SpecParseError retval = parse_spec_json(&config, &parsed);
|
||||
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
|
||||
sput_fail_unless(
|
||||
retval == SPE_INVALID_SYNTAX, "invalid spec.json, INVALID_SYNTAX"
|
||||
);
|
||||
sput_fail_unless(parsed == 0, "invalid spec.json, not parsed");
|
||||
|
||||
free(cwd);
|
||||
free(root_dir);
|
||||
test_parser_teardown(fixture);
|
||||
}
|
||||
|
||||
#endif /* _BOOTSTRAP_TEST_PARSER */
|
||||
|
|
Loading…
Reference in New Issue