Add example of spec and explanation on how it works.

pull/9/head
Joshua Potter 2023-11-22 14:39:27 -07:00
parent 13dff67a36
commit f949647e1b
10 changed files with 167 additions and 28 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
.cache/ .cache/
.direnv/ .direnv/
a.out gen-flake
compile_commands.json compile_commands.json

View File

@ -1,5 +1,7 @@
COMMAND=clang -g -I include src/*.c main.c -o gen-flake
all: all:
@clang -g -lncurses -I include src/*.c main.c -o gen-flake @${COMMAND}
bear: bear:
@bear -- clang -g -lncurses -I include src/*.c main.c -o gen-flake @bear -- ${COMMAND}

41
README.md Normal file
View File

@ -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.

BIN
gen-flake

Binary file not shown.

24
include/config.h Normal file
View File

@ -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 */

View File

@ -3,7 +3,13 @@
#include <stdlib.h> #include <stdlib.h>
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); struct DynArray *dyn_array_new(size_t capacity);

60
main.c
View File

@ -1,15 +1,39 @@
#include <assert.h> #include <assert.h>
#include <curses.h>
#include <glob.h> #include <glob.h>
#include <signal.h> #include <signal.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "dyn_array.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(name);
assert(entries); assert(entries);
@ -41,12 +65,6 @@ cleanup:
return retval; return retval;
} }
static void cleanup(int sig) {
endwin();
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
int num = 0; int num = 0;
@ -55,13 +73,23 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// Allow interrupting the program cleanly. struct Config config;
// TODO: How does this cleanup spec correctly? switch (load_config(argv[1], &config)) {
signal(SIGINT, cleanup); 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(); free_config(&config);
keypad(stdscr, TRUE); // Enable keyboard mapping. return EXIT_SUCCESS;
nonl(); // Disables NL to CR/NL conversion on output.
cleanup(0);
} }

5
specs/clang/spec.json Normal file
View File

@ -0,0 +1,5 @@
{
"versions": [
"default"
]
}

41
src/config.c Normal file
View File

@ -0,0 +1,41 @@
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -2,14 +2,6 @@
#include "dyn_array.h" #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 *dyn_array_new(size_t capacity) {
struct DynArray *a = malloc(sizeof(struct DynArray)); struct DynArray *a = malloc(sizeof(struct DynArray));
a->buf = calloc(capacity, sizeof(void *)); a->buf = calloc(capacity, sizeof(void *));