diff --git a/.gitignore b/.gitignore index c83ed64..b50d6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .cache/ .direnv/ -a.out +gen-flake compile_commands.json diff --git a/Makefile b/Makefile index 85ac7e7..b980bd7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ +COMMAND=clang -g -I include src/*.c main.c -o gen-flake + all: - @clang -g -lncurses -I include src/*.c main.c -o gen-flake + @${COMMAND} bear: - @bear -- clang -g -lncurses -I include src/*.c main.c -o gen-flake + @bear -- ${COMMAND} diff --git a/README.md b/README.md new file mode 100644 index 0000000..67e30e8 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# gen-flake + +CLI utility for initializing projects deterministically using flakes. + +## Problem + +Nix supports the concept of [templates](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-init) +out of the box, but the usefulness of the implementation is dubious. In +particular, +```bash +> nix flake init +``` +simply copies the files specified in the template directory to the current +local directory, with no means of configuring the copied files. This is +especially useless when working with projects that typically generate files +on initialization (i.e. most projects). + +## How This Works + +Within the `specs` directory exists so-called *specs*. A spec is a directory +containing a `spec.json` file and a `run.sh` file. The former is configured like +so: + +```spec.json +{ + versions: [...], +} +``` + +The keys of this top-level JSON object correspond to the parameters that are +prompted by the `gen-flake` curses interface. The value is used to determine +what kind of prompt `gen-flake` provides for the given question. Possible value +types are: + +* `[...]` (list) + * This indicates a select option prompt. The user chooses amongst the values + specified in the JSON list. + +Once all prompts are evaluated, the keys of the object are converted into +uppercase environment variables and passed to the `run.sh` file relative to the +current directory. diff --git a/gen-flake b/gen-flake index 11e4692..878528c 100755 Binary files a/gen-flake and b/gen-flake differ diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..27b0450 --- /dev/null +++ b/include/config.h @@ -0,0 +1,24 @@ +#ifndef GEN_FLAKE_CONFIG_H +#define GEN_FLAKE_CONFIG_H + +struct Config { + // The root directory housing our specs. This string is nonempty. + const char *spec_path; + // The name of the spec we want to load. This string is nonempty. + const char *target; +}; + +enum ConfigError { + // Indicates the `$GEN_FLAKE_SPEC_PATH` environment variable is not set. + ENV_SPEC_PATH_EMPTY = 1, + // Indicates the `$GEN_FLAKE_SPEC_PATH` environment variable is empty. + ENV_SPEC_PATH_MISSING, + // Indicates the target argument is invalid. + INVALID_TARGET, +}; + +enum ConfigError load_config(const char *target, struct Config *config); + +void free_config(struct Config *config); + +#endif /* GEN_FLAKE_CONFIG_H */ diff --git a/include/dyn_array.h b/include/dyn_array.h index e5770ee..9b60d8c 100644 --- a/include/dyn_array.h +++ b/include/dyn_array.h @@ -3,7 +3,13 @@ #include -struct DynArray; +struct DynArray { + void **buf; + // The size of @buf excluding `NUL`. + size_t size; + // The allocated size of @buf including `NUL`. + size_t _capacity; +}; struct DynArray *dyn_array_new(size_t capacity); diff --git a/main.c b/main.c index 697e4e0..d82e9aa 100644 --- a/main.c +++ b/main.c @@ -1,15 +1,39 @@ #include -#include #include #include +#include #include #include #include #include +#include "config.h" #include "dyn_array.h" -static int glob_specs(char *name, struct DynArray *entries) { +static const char *glob_spec_pattern(const struct Config *const config) { + assert(config); + + int spec_path_length = strlen(config->spec_path); + int target_length = strlen(config->target); + // Support paths that have trailing forward slashes. + int sep_length = 0; + if (config->spec_path[spec_path_length - 1] == '/') { + sep_length = 1; + } + + char *pattern = calloc(1, spec_path_length + sep_length + target_length + 1); + memcpy(pattern, config->spec_path, spec_path_length); + if (sep_length) { + memcpy(pattern + spec_path_length, "/", sep_length); + } + memcpy(pattern + spec_path_length + sep_length, config->target, + target_length); + + return pattern; +} + +static int glob_specs(char *name, const char *spec_path, + struct DynArray *entries) { assert(name); assert(entries); @@ -41,12 +65,6 @@ cleanup: return retval; } -static void cleanup(int sig) { - endwin(); - - exit(EXIT_SUCCESS); -} - int main(int argc, char **argv) { int num = 0; @@ -55,13 +73,23 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - // Allow interrupting the program cleanly. - // TODO: How does this cleanup spec correctly? - signal(SIGINT, cleanup); + struct Config config; + switch (load_config(argv[1], &config)) { + case ENV_SPEC_PATH_EMPTY: + fprintf(stderr, "Must specify GEN_FLAKE_SPEC_PATH environment variable."); + exit(EXIT_FAILURE); + case ENV_SPEC_PATH_MISSING: + fprintf(stderr, + "GEN_FLAKE_SPEC_PATH environment variable should not be empty."); + exit(EXIT_FAILURE); + case INVALID_TARGET: + fprintf(stderr, "Spec `%s` is invalid.", argv[1]); + exit(EXIT_FAILURE); + default: + // Return value of `0` indicates no issue. + break; + } - initscr(); - keypad(stdscr, TRUE); // Enable keyboard mapping. - nonl(); // Disables NL to CR/NL conversion on output. - - cleanup(0); + free_config(&config); + return EXIT_SUCCESS; } diff --git a/specs/clang/spec.json b/specs/clang/spec.json new file mode 100644 index 0000000..61b562d --- /dev/null +++ b/specs/clang/spec.json @@ -0,0 +1,5 @@ +{ + "versions": [ + "default" + ] +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..ed9a82a --- /dev/null +++ b/src/config.c @@ -0,0 +1,41 @@ +#include +#include + +#include "config.h" + +const char *ENV_SPEC_PATH = "GEN_FLAKE_SPEC_PATH"; + +enum ConfigError load_config(const char *target, struct Config *config) { + const char *spec_path = getenv(ENV_SPEC_PATH); + if (spec_path == 0) { + return ENV_SPEC_PATH_MISSING; + } + if (spec_path[0] == 0) { + return ENV_SPEC_PATH_EMPTY; + } + if (target == 0) { + return INVALID_TARGET; + } + + size_t target_len = strlen(target); + if (target_len == 0) { + return INVALID_TARGET; + } + + config = malloc(sizeof(struct Config)); + config->spec_path = spec_path; + + char *copy_target = calloc(1, target_len + 1); + strcpy(copy_target, target); + config->target=copy_target; + + return 0; +} + +void free_config(struct Config *config) { + if (!config) { + return; + } + free((void *)config->target); + free(config); +} diff --git a/src/dyn_array.c b/src/dyn_array.c index 38593ea..4866627 100644 --- a/src/dyn_array.c +++ b/src/dyn_array.c @@ -2,14 +2,6 @@ #include "dyn_array.h" -struct DynArray { - void **buf; - // The size of @buf excluding `NUL`. - size_t size; - // The allocated size of @buf including `NUL`. - size_t _capacity; -}; - struct DynArray *dyn_array_new(size_t capacity) { struct DynArray *a = malloc(sizeof(struct DynArray)); a->buf = calloc(capacity, sizeof(void *));