Add example of spec and explanation on how it works.
parent
13dff67a36
commit
f949647e1b
|
@ -1,4 +1,4 @@
|
|||
.cache/
|
||||
.direnv/
|
||||
a.out
|
||||
gen-flake
|
||||
compile_commands.json
|
||||
|
|
6
Makefile
6
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}
|
||||
|
|
|
@ -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.
|
|
@ -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 */
|
|
@ -3,7 +3,13 @@
|
|||
|
||||
#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);
|
||||
|
||||
|
|
62
main.c
62
main.c
|
@ -1,15 +1,39 @@
|
|||
#include <assert.h>
|
||||
#include <curses.h>
|
||||
#include <glob.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
|
||||
initscr();
|
||||
keypad(stdscr, TRUE); // Enable keyboard mapping.
|
||||
nonl(); // Disables NL to CR/NL conversion on output.
|
||||
|
||||
cleanup(0);
|
||||
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;
|
||||
}
|
||||
|
||||
free_config(&config);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versions": [
|
||||
"default"
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 *));
|
||||
|
|
Loading…
Reference in New Issue