Correctly handle nonexistent `spec.json` file.
parent
694fe98167
commit
1b8b454ccd
|
@ -14,11 +14,11 @@ struct Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigError {
|
enum ConfigError {
|
||||||
// Indicates the $CWD could not be retrieved.
|
// The $CWD could not be retrieved.
|
||||||
CE_ENV_CWD_INVALID = 1,
|
CE_ENV_CWD_INVALID = 1,
|
||||||
// Indicates the $BOOTSTRAP_ROOT_DIR environment variable is empty.
|
// The $BOOTSTRAP_ROOT_DIR environment variable is empty.
|
||||||
CE_ENV_ROOT_DIR_INVALID,
|
CE_ENV_ROOT_DIR_INVALID,
|
||||||
// Indicates the target argument is invalid.
|
// The target argument is invalid.
|
||||||
CE_TARGET_INVALID,
|
CE_TARGET_INVALID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef _BOOTSTRAP_LOADER_H
|
||||||
|
#define _BOOTSTRAP_LOADER_H
|
||||||
|
|
||||||
|
#include "cJSON.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
enum SpecJsonError {
|
||||||
|
// The `spec.json` file exists but cannot be open.
|
||||||
|
SJE_JSON_CANNOT_OPEN = 1,
|
||||||
|
// The JSON of the corresponding file is not syntactically valid JSON.
|
||||||
|
SJE_JSON_INVALID,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reads in the `spec.json` file relative to the paths of the provided @Config.
|
||||||
|
|
||||||
|
A spec directory does not necessarily contain a `spec.json` file. If this file
|
||||||
|
cannot be found, the @parsed pointer is set to NULL with a success return code.
|
||||||
|
|
||||||
|
@return: 0 on success and a @SpecJsonError otherwise.
|
||||||
|
*/
|
||||||
|
enum SpecJsonError
|
||||||
|
read_spec_json(const struct Config *const config, cJSON **parsed);
|
||||||
|
|
||||||
|
#endif /* _BOOTSTRAP_LOADER_H */
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef _BOOTSTRAP_PATH_H
|
||||||
|
#define _BOOTSTRAP_PATH_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
char *join_path_segments(size_t n, const char *segments[static n]);
|
||||||
|
|
||||||
|
#endif /* _BOOTSTRAP_PATH_H */
|
42
main.c
42
main.c
|
@ -2,8 +2,9 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cJSON.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dyn_array.h"
|
#include "loader.h"
|
||||||
|
|
||||||
const char *ENV_BOOTSTRAP_ROOT_DIR = "BOOTSTRAP_ROOT_DIR";
|
const char *ENV_BOOTSTRAP_ROOT_DIR = "BOOTSTRAP_ROOT_DIR";
|
||||||
|
|
||||||
|
@ -15,24 +16,51 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *cwd = getcwd(0, 0);
|
int retval = EXIT_SUCCESS;
|
||||||
|
char *cwd = getcwd(0, 0);
|
||||||
const char *root_dir = getenv(ENV_BOOTSTRAP_ROOT_DIR);
|
const char *root_dir = getenv(ENV_BOOTSTRAP_ROOT_DIR);
|
||||||
const char *target = argv[1];
|
const char *target = argv[1];
|
||||||
|
|
||||||
|
// `cwd` must be free'd.
|
||||||
|
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
switch (config_load(cwd, root_dir, target, &config)) {
|
switch (config_load(cwd, root_dir, target, &config)) {
|
||||||
case CE_ENV_CWD_INVALID:
|
case CE_ENV_CWD_INVALID:
|
||||||
fprintf(stderr, "Could not retrieve the $CWD value.");
|
fprintf(stderr, "Could not retrieve the $CWD value.");
|
||||||
exit(EXIT_FAILURE);
|
retval = EXIT_FAILURE;
|
||||||
|
goto cwd_cleanup;
|
||||||
case CE_ENV_ROOT_DIR_INVALID:
|
case CE_ENV_ROOT_DIR_INVALID:
|
||||||
fprintf(stderr, "Must specify $BOOTSTRAP_ROOT_DIR environment variable.");
|
fprintf(stderr, "Must specify $BOOTSTRAP_ROOT_DIR environment variable.");
|
||||||
exit(EXIT_FAILURE);
|
retval = EXIT_FAILURE;
|
||||||
|
goto cwd_cleanup;
|
||||||
case CE_TARGET_INVALID:
|
case CE_TARGET_INVALID:
|
||||||
fprintf(stderr, "Target spec `%s` is invalid.", argv[1]);
|
fprintf(stderr, "Target spec `%s` is invalid.", argv[1]);
|
||||||
exit(EXIT_FAILURE);
|
retval = EXIT_FAILURE;
|
||||||
|
goto cwd_cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `config` must be free'd.
|
||||||
|
|
||||||
|
cJSON *parsed = 0;
|
||||||
|
switch (read_spec_json(config, &parsed)) {
|
||||||
|
case SJE_JSON_CANNOT_OPEN:
|
||||||
|
fprintf(stderr, "Found `spec.json` but could not open.");
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto config_cleanup;
|
||||||
|
case SJE_JSON_INVALID:
|
||||||
|
fprintf(stderr, "`spec.json` does not conform to bootstrap format.");
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto config_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Extract the prompts out of the `spec.json` file.
|
||||||
|
// TODO: Load in the curses interface.
|
||||||
|
// TODO: Run `run.sh`.
|
||||||
|
|
||||||
|
config_cleanup:
|
||||||
config_free(config);
|
config_free(config);
|
||||||
free((void *)cwd);
|
|
||||||
return EXIT_SUCCESS;
|
cwd_cleanup:
|
||||||
|
free(cwd);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "loader.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 =
|
||||||
|
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 SpecJsonError
|
||||||
|
read_spec_json(const struct Config *const config, cJSON **parsed) {
|
||||||
|
FILE *handle = 0;
|
||||||
|
int retval = find_spec_json(config, &handle);
|
||||||
|
|
||||||
|
if (retval != 0) {
|
||||||
|
return SJE_JSON_CANNOT_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `spec.json` file does not exist.
|
||||||
|
if (!handle) {
|
||||||
|
*parsed = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to parse the spec.json file.
|
||||||
|
fclose(handle);
|
||||||
|
assert(false);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "path.h"
|
||||||
|
|
||||||
|
char *join_path_segments(size_t n, const char *segments[static n]) {
|
||||||
|
assert(n > 0);
|
||||||
|
|
||||||
|
size_t length = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
length += strlen(segments[i]);
|
||||||
|
}
|
||||||
|
length += n - 1; // Path separators.
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
char *joined = calloc(1, length + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
const size_t segment_len = strlen(segments[i]);
|
||||||
|
memcpy(joined + offset, segments[i], segment_len);
|
||||||
|
offset += segment_len;
|
||||||
|
if (i < n - 1) {
|
||||||
|
joined[offset++] = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return joined;
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
#include "sput.h"
|
#include "sput.h"
|
||||||
#include "test_config.h"
|
#include "test_config.h"
|
||||||
#include "test_dyn_array.h"
|
#include "test_dyn_array.h"
|
||||||
|
#include "test_loader.h"
|
||||||
|
#include "test_path.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
sput_start_testing();
|
sput_start_testing();
|
||||||
|
@ -14,6 +16,13 @@ int main(int argc, char *argv[]) {
|
||||||
sput_run_test(test_dyn_array_empty);
|
sput_run_test(test_dyn_array_empty);
|
||||||
sput_run_test(test_dyn_array_nonzero_capacity);
|
sput_run_test(test_dyn_array_nonzero_capacity);
|
||||||
|
|
||||||
|
sput_enter_suite("path");
|
||||||
|
sput_run_test(test_join_path_single_segments);
|
||||||
|
sput_run_test(test_join_path_multiple_segments);
|
||||||
|
|
||||||
|
sput_enter_suite("loader");
|
||||||
|
sput_run_test(test_read_spec_json_missing);
|
||||||
|
|
||||||
sput_finish_testing();
|
sput_finish_testing();
|
||||||
|
|
||||||
return sput_get_return_value();
|
return sput_get_return_value();
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "hello world"
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "hello world"
|
|
@ -8,6 +8,9 @@ static const char *SAMPLE_CWD = "/home/jrpotter/Documents/bootstrap";
|
||||||
static const char *SAMPLE_ROOT_DIR = "/usr/local/share/specs";
|
static const char *SAMPLE_ROOT_DIR = "/usr/local/share/specs";
|
||||||
static const char *SAMPLE_TARGET = "example-target";
|
static const char *SAMPLE_TARGET = "example-target";
|
||||||
|
|
||||||
|
/*
|
||||||
|
A valid $CWD environment variable value must be supplied.
|
||||||
|
*/
|
||||||
static void test_config_load_cwd_invalid() {
|
static void test_config_load_cwd_invalid() {
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval =
|
enum ConfigError retval =
|
||||||
|
@ -15,12 +18,18 @@ static void test_config_load_cwd_invalid() {
|
||||||
sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0");
|
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() {
|
static void test_config_load_root_dir_invalid() {
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval = config_load(SAMPLE_CWD, 0, SAMPLE_TARGET, &config);
|
enum ConfigError retval = config_load(SAMPLE_CWD, 0, SAMPLE_TARGET, &config);
|
||||||
sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0");
|
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() {
|
static void test_config_load_target_invalid() {
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval =
|
enum ConfigError retval =
|
||||||
|
@ -28,6 +37,10 @@ static void test_config_load_target_invalid() {
|
||||||
sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0");
|
sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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_success() {
|
||||||
struct Config *config = 0;
|
struct Config *config = 0;
|
||||||
enum ConfigError retval =
|
enum ConfigError retval =
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
#include "dyn_array.h"
|
#include "dyn_array.h"
|
||||||
#include "sput.h"
|
#include "sput.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
A @DynArray with zero capacity can be instantiated and have entries pushed onto.
|
||||||
|
*/
|
||||||
static void test_dyn_array_empty() {
|
static void test_dyn_array_empty() {
|
||||||
struct DynArray *a = dyn_array_new(0);
|
struct DynArray *a = dyn_array_new(0);
|
||||||
sput_fail_unless(a->size == 0, "a->size == 0");
|
sput_fail_unless(a->size == 0, "a->size == 0");
|
||||||
|
@ -22,6 +25,9 @@ static void test_dyn_array_empty() {
|
||||||
dyn_array_free(a);
|
dyn_array_free(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A @DynArray with nonzero capacity can be instantiated and have entries pushed onto.
|
||||||
|
*/
|
||||||
static void test_dyn_array_nonzero_capacity() {
|
static void test_dyn_array_nonzero_capacity() {
|
||||||
struct DynArray *a = dyn_array_new(3);
|
struct DynArray *a = dyn_array_new(3);
|
||||||
sput_fail_unless(a->size == 0, "a->size == 0");
|
sput_fail_unless(a->size == 0, "a->size == 0");
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef _BOOTSTRAP_TEST_LOADER
|
||||||
|
#define _BOOTSTRAP_TEST_LOADER
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cJSON.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "path.h"
|
||||||
|
#include "sput.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
A missing `spec.json` file is not an error. Our parsed @cJSON instance should
|
||||||
|
be set to NULL in this case.
|
||||||
|
*/
|
||||||
|
static void test_read_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",
|
||||||
|
};
|
||||||
|
|
||||||
|
cJSON *parsed = 0;
|
||||||
|
enum SpecJsonError retval = read_spec_json(&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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _BOOTSTRAP_TEST_LOADER */
|
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef _BOOTSTRAP_TEST_PATH
|
||||||
|
#define _BOOTSTRAP_TEST_PATH
|
||||||
|
|
||||||
|
#include "path.h"
|
||||||
|
#include "sput.h"
|
||||||
|
|
||||||
|
static void test_join_path_single_segments() {
|
||||||
|
const char *segments[] = {"abc"};
|
||||||
|
char *joined = join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||||
|
sput_fail_unless(strcmp(joined, "abc") == 0, "abc");
|
||||||
|
free(joined);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_join_path_multiple_segments() {
|
||||||
|
const char *segments[] = {"abc", "def", "ghi"};
|
||||||
|
char *joined = join_path_segments(sizeof(segments) / sizeof(char *), segments);
|
||||||
|
sput_fail_unless(strcmp(joined, "abc/def/ghi") == 0, "abc/def/ghi");
|
||||||
|
free(joined);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _BOOTSTRAP_TEST_PATH */
|
Loading…
Reference in New Issue