Add documentation for `config`, `dyn_array`, `error`, and `string_buf`.
parent
16e971af0b
commit
7cc11c86e3
|
@ -1,5 +1,6 @@
|
|||
.cache/
|
||||
.direnv/
|
||||
compile_commands.json
|
||||
bootstrap
|
||||
compile_commands.json
|
||||
docs/
|
||||
test/runner
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
TODO:
|
||||
- [ ] Add documentation throughout (ownership, docstrings, etc.).
|
||||
- [ ] Add evaluator tests.
|
||||
- [ ] Color output to console.
|
||||
|
||||
CLI utility for initializing projects in reproducible ways.
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
bear
|
||||
clang-tools
|
||||
codelldb
|
||||
doxygen
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
ncurses
|
||||
|
|
|
@ -1,27 +1,59 @@
|
|||
/**
|
||||
@file
|
||||
@brief A `spec` configuration.
|
||||
*/
|
||||
#ifndef _BOOTSTRAP_CONFIG_H
|
||||
#define _BOOTSTRAP_CONFIG_H
|
||||
|
||||
#include "error.h"
|
||||
|
||||
/**
|
||||
@brief A collection of parameters that identify a `spec`.
|
||||
|
||||
Each member of the @ref Config is expected to outlive the @ref Config instance
|
||||
itself.
|
||||
*/
|
||||
struct Config {
|
||||
// The directory the `bootstrap` command was run from.
|
||||
// OWNERSHIP: Does not own this pointer.
|
||||
/// The directory the `bootstrap` command was run from. Does not take
|
||||
/// ownership of this pointer.
|
||||
const char *cwd;
|
||||
// The root directory housing our specs.
|
||||
// OWNERSHIP: Does not own this pointer.
|
||||
/// The root directory housing our specs. Does not take ownership of this
|
||||
/// pointer.
|
||||
const char *root_dir;
|
||||
// The name of the spec we want to bootstrap.
|
||||
// OWNERSHIP: Does not own this pointer.
|
||||
/// The name of the spec we want to bootstrap. Does not take ownership of
|
||||
/// this pointer.
|
||||
const char *target;
|
||||
};
|
||||
|
||||
struct Error *config_load(
|
||||
/**
|
||||
@brief Create a new @ref Config instance.
|
||||
|
||||
@param cwd
|
||||
The current working directory the `bootstrap` command was invoked from.
|
||||
@param root_dir
|
||||
An absolute path to the collection of specs `bootstrap` will search through.
|
||||
@param target
|
||||
The `spec` that should be bootstrapped.
|
||||
@return
|
||||
A new @ref Config instance. The caller takes ownership of this value.
|
||||
|
||||
@see config_free
|
||||
*/
|
||||
struct Error *config_new(
|
||||
const char *cwd,
|
||||
const char *root_dir,
|
||||
const char *target,
|
||||
struct Config **config
|
||||
);
|
||||
|
||||
/**
|
||||
@brief Deallocates a previously allocated @ref Config instance.
|
||||
|
||||
@param config
|
||||
A pointer to a @ref Config instance. If null, this function is a no-op.
|
||||
|
||||
@see config_new
|
||||
*/
|
||||
void config_free(struct Config *config);
|
||||
|
||||
#endif /* BOOTSTRAP_CONFIG_H */
|
||||
|
|
|
@ -1,22 +1,72 @@
|
|||
/**
|
||||
@file
|
||||
@brief Dynamic `void*` arrays.
|
||||
*/
|
||||
#ifndef _BOOTSTRAP_DYN_ARRAY_H
|
||||
#define _BOOTSTRAP_DYN_ARRAY_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
@brief A dynamic array of generic pointers.
|
||||
|
||||
A `void*` wrapper that grows larger as needed. If more space is needed during an
|
||||
append operation, the capacity of the internal array is doubled.
|
||||
*/
|
||||
struct DynArray {
|
||||
/// The underlying `void*` pointer.
|
||||
void **buf;
|
||||
// The size of @buf excluding `NUL`.
|
||||
/// The size of @ref DynArray.buf.
|
||||
size_t size;
|
||||
// The allocated size of @buf including `NUL`.
|
||||
// The allocated size of @ref DynArray.buf.
|
||||
size_t _capacity;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Create a new @ref DynArray instance.
|
||||
|
||||
@param capacity
|
||||
The initial size of the internal array. To avoid too many reallocations, aim to
|
||||
make this value large enough to accommodate the eventual size of the buffer.
|
||||
|
||||
@see dyn_array_free
|
||||
*/
|
||||
struct DynArray *dyn_array_new(size_t capacity);
|
||||
|
||||
/**
|
||||
@brief Returns the number of items contained in the internal buffer.
|
||||
|
||||
@param a
|
||||
A valid pointer to a @ref DynArray instance.
|
||||
@return
|
||||
The number of items contained in the internal buffer.
|
||||
*/
|
||||
size_t dyn_array_size(struct DynArray *a);
|
||||
|
||||
/**
|
||||
@brief Appends a new item onto the end of the internal @ref DynArray.buf.
|
||||
|
||||
This function takes ownership of @p item and will attempt to `free` the
|
||||
parameter when the @ref DynArray is `free`'d. For this reason, only provide
|
||||
entries that have been allocated on the heap.
|
||||
|
||||
@param a
|
||||
A valid pointer to a @ref DynArray instance.
|
||||
@param item
|
||||
A valid pointer to a heap-allocated object.
|
||||
|
||||
@see dyn_array_free
|
||||
*/
|
||||
void dyn_array_push(struct DynArray *a, void *item);
|
||||
|
||||
/**
|
||||
@brief Deallocates a previously allocated @ref DynArray instance.
|
||||
|
||||
@param a
|
||||
A pointer to a @ref DynArray instance. If null, this function is a no-op.
|
||||
|
||||
@see dyn_array_new
|
||||
*/
|
||||
void dyn_array_free(struct DynArray *a);
|
||||
|
||||
#endif /* _BOOTSTRAP_DYN_ARRAY_H */
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
@file
|
||||
@brief Error handling.
|
||||
*/
|
||||
#ifndef _BOOTSTRAP_ERROR_H
|
||||
#define _BOOTSTRAP_ERROR_H
|
||||
|
||||
|
@ -5,27 +9,55 @@
|
|||
|
||||
#include "string_buf.h"
|
||||
|
||||
/**
|
||||
@brief The various error codes produced by `bootstrap`.
|
||||
*/
|
||||
enum ErrorCode {
|
||||
/// Could not retrieve the value of `$CWD`.
|
||||
ERROR_CONFIG_ENV_CWD_INVALID = 1,
|
||||
/// No root directory was specified. This must either be set via the command
|
||||
/// line flag `-d` or environment variable `$BOOTSTRAP_ROOT_DIR`. If both are
|
||||
/// provided, the command line flag takes priority.
|
||||
ERROR_CONFIG_ENV_ROOT_DIR_INVALID,
|
||||
/// There is no file at the location corresponding to the specified spec.
|
||||
ERROR_CONFIG_TARGET_NOT_FOUND,
|
||||
/// For generic reasons, `bootstrap` could not access the file at the provided
|
||||
/// spec.
|
||||
ERROR_CONFIG_TARGET_INVALID,
|
||||
/// The file at the location corresponding to the provided spec is not a
|
||||
/// directory.
|
||||
ERROR_CONFIG_TARGET_NOT_DIR,
|
||||
|
||||
/// For generic reasons, `bootstrap` could not access the `spec.json` file.
|
||||
ERROR_PARSER_SPEC_JSON_INVALID,
|
||||
/// The `spec.json` file is not valid JSON.
|
||||
ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
|
||||
|
||||
/// The top-level JSON object of the `spec.json` file is not an object.
|
||||
ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT,
|
||||
/// A field in `spec.json` is not an object.
|
||||
ERROR_VALIDATOR_FIELD_NOT_OBJECT,
|
||||
/// The `type` of a `spec.json` field is not a string.
|
||||
ERROR_VALIDATOR_FIELD_TYPE_INVALID,
|
||||
/// The `type` of a `spec.json` field does not correspond to a known prompt
|
||||
/// type.
|
||||
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
|
||||
/// The `prompt` of a `spec.json` field is not a string.
|
||||
ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
|
||||
|
||||
/// The `run.sh` file could not be found.
|
||||
ERROR_EVALUATOR_RUN_SH_NOT_FOUND,
|
||||
/// The `run.sh` file is not executable.
|
||||
ERROR_EVALUATOR_RUN_SH_NOT_EXEC,
|
||||
/// A user response to a prompt is not valid.
|
||||
ERROR_EVALUATOR_RESPONSE_INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
@brief A `bootstrap` error.
|
||||
|
||||
@see ERROR_NEW
|
||||
*/
|
||||
struct Error {
|
||||
enum ErrorCode code;
|
||||
const char *message;
|
||||
|
@ -47,10 +79,13 @@ static inline struct Error *priv_error_new(
|
|||
// clang-format off
|
||||
|
||||
/**
|
||||
Return the number of elements of `__VA_ARGS__`.
|
||||
@brief Return the number of elements of `__VA_ARGS__`.
|
||||
|
||||
Take the `__VA_ARGS__` list and append a list of decreasing numbers
|
||||
31, 30, ..., 0. Then, by using ALEN0, return the 31st element of that list.
|
||||
31, 30, ..., 0.
|
||||
|
||||
@return
|
||||
The number of elements of `__VA_ARGS__`.
|
||||
*/
|
||||
#define ALEN(...) \
|
||||
ALEN0( \
|
||||
|
@ -68,7 +103,17 @@ Take the `__VA_ARGS__` list and append a list of decreasing numbers
|
|||
...) _1F
|
||||
|
||||
/**
|
||||
Create a new `struct Error` instance.
|
||||
@brief Creates a new @ref Error instance.
|
||||
|
||||
It is the responsibility of the caller to free the @ref Error instance.
|
||||
|
||||
@param __VA_ARGS__
|
||||
Allows supplying up to 31 `const char*` instances. These arguments will be
|
||||
concatenated together and supplied to the new @ref Error instance.
|
||||
@return
|
||||
A new @ref Error instance. The caller takes ownership of this value.
|
||||
|
||||
@see error_free
|
||||
*/
|
||||
#define ERROR_NEW(code, ...) \
|
||||
ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__)
|
||||
|
@ -80,6 +125,14 @@ Create a new `struct Error` instance.
|
|||
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
@brief Deallocates a previously allocated @ref Error isntance.
|
||||
|
||||
@param error
|
||||
A pointer to a @ref Error instance. If null, this function is a no-op.
|
||||
|
||||
@see ERROR_NEW
|
||||
*/
|
||||
void error_free(struct Error *error);
|
||||
|
||||
#endif /* _BOOTSTRAP_ERROR_H */
|
||||
|
|
|
@ -1,102 +1,100 @@
|
|||
/**
|
||||
@file
|
||||
@brief Dynamic character arrays.
|
||||
*/
|
||||
#ifndef _BOOTSTRAP_STRING_BUF_H
|
||||
#define _BOOTSTRAP_STRING_BUF_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* @brief A dynamic character array.
|
||||
*
|
||||
* A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates
|
||||
* additional space as needed.
|
||||
*/
|
||||
@brief A dynamic character array.
|
||||
|
||||
A `char*` wrapper that grows larger as needed. Supports appending individual
|
||||
`char`s or NUL-terminated strings. If more space is needed during an append
|
||||
operation, the capacity is doubled repeatedly until the requested data fits.
|
||||
*/
|
||||
struct StringBuf {
|
||||
/// The underlying `char*` pointer.
|
||||
char *buf;
|
||||
// The length of @buf excluding `NUL`.
|
||||
/// The size of @ref StringBuf.buf (excluding the `NUL` character).
|
||||
size_t size;
|
||||
// The allocated size of @buf including `NUL`.
|
||||
/// The allocated size of @ref StringBuf.buf (including the `NUL` character).
|
||||
size_t _capacity;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new `StringBuf` instance.
|
||||
*
|
||||
* @param capacity
|
||||
* The initial size of the internal array (including the trailing `NUL`
|
||||
* character). To avoid too many reallocations, aim to make this value large
|
||||
* enough to accommodate the size the string is expected to eventually take.
|
||||
* @return
|
||||
* A new `StringBuf` instance. The caller takes ownership of this value.
|
||||
*
|
||||
* @see string_buf_free
|
||||
*/
|
||||
@brief Create a new @ref StringBuf instance.
|
||||
|
||||
@param capacity
|
||||
The initial size of the internal array (including the trailing `NUL`
|
||||
character). To avoid too many reallocations, aim to make this value large
|
||||
enough to accommodate the eventual size of the desired string.
|
||||
@return
|
||||
A new @ref StringBuf instance. The caller takes ownership of this value.
|
||||
|
||||
@see string_buf_free
|
||||
*/
|
||||
struct StringBuf *string_buf_new(size_t capacity);
|
||||
|
||||
/**
|
||||
* Return the number of characters contained in the internal buffer.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
* @return
|
||||
* The number of characters contained in the internal buffer.
|
||||
*/
|
||||
@brief Returns the number of characters contained in the internal buffer
|
||||
(excluding the NUL character).
|
||||
|
||||
@param sb
|
||||
A valid pointer to a @ref StringBuf instance.
|
||||
@return
|
||||
The number of characters contained in the internal buffer.
|
||||
*/
|
||||
size_t string_buf_size(struct StringBuf *sb);
|
||||
|
||||
/**
|
||||
* Return the internal `NUL`-terminated string buffer.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
* @return
|
||||
* The internally managed string.
|
||||
*/
|
||||
const char *string_buf_value(struct StringBuf *sb);
|
||||
@brief Appends a character to the end of the provided @ref StringBuf.
|
||||
|
||||
/**
|
||||
* Append a character to the end of a `StringBuf`.
|
||||
*
|
||||
* If appending would cause the internal buffer to overflow, reallocates the
|
||||
* internal array to accommodate.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
* @param c
|
||||
* The `char` to append to the end of `sb`.
|
||||
*/
|
||||
If appending would cause the internal buffer to overflow, doubles the capacity
|
||||
of the internal array to accommodate.
|
||||
|
||||
@param sb
|
||||
A valid pointer to a @ref StringBuf instance.
|
||||
@param c
|
||||
The `char` to append to the end of @p sb.
|
||||
*/
|
||||
void string_buf_cappend(struct StringBuf *sb, char c);
|
||||
|
||||
/**
|
||||
* Append a `NUL`-terminated string to the end of a `StringBuf`.
|
||||
*
|
||||
* If appending would cause the internal buffer to overflow, `realloc`s are
|
||||
* performed internally to accommodate.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
* @param s
|
||||
* The `char*` to append to the end of `sb`.
|
||||
*/
|
||||
@brief Appends a NUL-terminated string to the end of a @ref StringBuf.
|
||||
|
||||
If appending would cause the internal buffer to overflow, doubles the capacity
|
||||
of the internal array the necessary number of times to accommodate.
|
||||
|
||||
@param sb
|
||||
A valid pointer to a @ref StringBuf instance.
|
||||
@param s
|
||||
The `char*` to append to the end of @p sb.
|
||||
*/
|
||||
void string_buf_sappend(struct StringBuf *sb, const char s[static 1]);
|
||||
|
||||
/**
|
||||
* Convert a `StringBuf` instance into a `char*`.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
* @return
|
||||
* A null pointer if `sb` is null. Otherwise a `NUL`-terminated string
|
||||
* corresponding to the value of `sb`. The caller takes ownership of this
|
||||
* value.
|
||||
*/
|
||||
@brief Converts a @ref StringBuf instance into a `char*`.
|
||||
|
||||
This function frees the memory associated with @p sb.
|
||||
|
||||
@param sb
|
||||
A valid pointer to a @ref StringBuf instance.
|
||||
@return
|
||||
A null pointer if @p sb is null. Otherwise a NUL-terminated string
|
||||
corresponding to the value of @p sb. The caller takes ownership of this value.
|
||||
*/
|
||||
const char *string_buf_convert(struct StringBuf *sb);
|
||||
|
||||
/**
|
||||
* Deallocate a previously allocated `StringBuf` instance.
|
||||
*
|
||||
* @param sb
|
||||
* A valid pointer to a `StringBuf` instance.
|
||||
*
|
||||
* @see string_buf_new
|
||||
*/
|
||||
@brief Deallocates a previously allocated @ref StringBuf instance.
|
||||
|
||||
@param sb
|
||||
A pointer to a @ref StringBuf instance. If null, this function is a no-op.
|
||||
|
||||
@see string_buf_new
|
||||
*/
|
||||
void string_buf_free(struct StringBuf *sb);
|
||||
|
||||
#endif /* _BOOTSTRAP_STRING_BUF_H */
|
||||
|
|
2
main.c
2
main.c
|
@ -21,7 +21,7 @@ static int run(const char *root_dir, const char *target) {
|
|||
struct Error *error = 0;
|
||||
struct Config *config = 0;
|
||||
|
||||
if ((error = config_load(cwd, root_dir, target, &config))) {
|
||||
if ((error = config_new(cwd, root_dir, target, &config))) {
|
||||
fprintf(stderr, "%s", error->message);
|
||||
goto cleanup_cwd;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "path.h"
|
||||
|
||||
struct Error *config_load(
|
||||
struct Error *config_new(
|
||||
const char *cwd,
|
||||
const char *root_dir,
|
||||
const char *target,
|
||||
|
|
|
@ -18,6 +18,7 @@ size_t dyn_array_size(struct DynArray *a) {
|
|||
|
||||
void dyn_array_push(struct DynArray *a, void *item) {
|
||||
assert(a);
|
||||
assert(item);
|
||||
if (a->size == a->_capacity) {
|
||||
a->_capacity *= 2;
|
||||
a->buf = realloc(a->buf, sizeof(void *) * a->_capacity);
|
||||
|
|
|
@ -10,10 +10,10 @@ int main(int argc, char *argv[]) {
|
|||
sput_start_testing();
|
||||
|
||||
sput_enter_suite("config");
|
||||
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_run_test(test_config_new_invalid_args);
|
||||
sput_run_test(test_config_new_spec_not_found);
|
||||
sput_run_test(test_config_new_spec_not_dir);
|
||||
sput_run_test(test_config_new_success);
|
||||
|
||||
sput_enter_suite("dyn_array");
|
||||
sput_run_test(test_dyn_array_zero_capacity);
|
||||
|
|
|
@ -33,16 +33,16 @@ static void test_config_teardown(struct TestConfigFixture *fixture) {
|
|||
free(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_invalid_args() {
|
||||
static void test_config_new_invalid_args() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Error *error = 0;
|
||||
struct Config *config = 0;
|
||||
|
||||
error = config_load(0, fixture->root_dir, fixture->target, &config);
|
||||
error = config_new(0, fixture->root_dir, fixture->target, &config);
|
||||
sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0");
|
||||
error_free(error);
|
||||
error = config_load(fixture->cwd, 0, fixture->target, &config);
|
||||
error = config_new(fixture->cwd, 0, fixture->target, &config);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0"
|
||||
);
|
||||
|
@ -51,13 +51,13 @@ static void test_config_load_invalid_args() {
|
|||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_spec_not_found() {
|
||||
static void test_config_new_spec_not_found() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Error *error = 0;
|
||||
struct Config *config = 0;
|
||||
|
||||
error = config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
|
||||
error = config_new(fixture->cwd, fixture->root_dir, "not_found", &config);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found"
|
||||
);
|
||||
|
@ -66,13 +66,13 @@ static void test_config_load_spec_not_found() {
|
|||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_spec_not_dir() {
|
||||
static void test_config_new_spec_not_dir() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Error *error = 0;
|
||||
struct Config *config = 0;
|
||||
|
||||
error = config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
|
||||
error = config_new(fixture->cwd, fixture->root_dir, "not_dir", &config);
|
||||
sput_fail_unless(
|
||||
error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir"
|
||||
);
|
||||
|
@ -81,21 +81,20 @@ static void test_config_load_spec_not_dir() {
|
|||
test_config_teardown(fixture);
|
||||
}
|
||||
|
||||
static void test_config_load_success() {
|
||||
static void test_config_new_success() {
|
||||
struct TestConfigFixture *fixture = test_config_setup();
|
||||
|
||||
struct Error *error = 0;
|
||||
struct Config *config = 0;
|
||||
|
||||
error =
|
||||
config_load(fixture->cwd, fixture->root_dir, fixture->target, &config);
|
||||
sput_fail_unless(error == 0, "config_load() success");
|
||||
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd");
|
||||
error = config_new(fixture->cwd, fixture->root_dir, fixture->target, &config);
|
||||
sput_fail_unless(error == 0, "config_new() success");
|
||||
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_new() cwd");
|
||||
sput_fail_unless(
|
||||
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"
|
||||
strcmp(config->root_dir, fixture->root_dir) == 0, "config_new() root_dir"
|
||||
);
|
||||
sput_fail_unless(
|
||||
strcmp(config->target, fixture->target) == 0, "config_load() target"
|
||||
strcmp(config->target, fixture->target) == 0, "config_new() target"
|
||||
);
|
||||
|
||||
config_free(config);
|
||||
|
|
Loading…
Reference in New Issue