diff options
| author | lectio <lukasz@arcofasiagroup.com> | 2026-07-28 22:07:21 +0200 |
|---|---|---|
| committer | lectio <lukasz@arcofasiagroup.com> | 2026-07-28 22:07:45 +0200 |
| commit | 42fd9a69fa2bc9ef11c65c169a58ff40c9cb0fa1 (patch) | |
| tree | eb135eae4d3374973c08b27956a6e067a95e56bc /mktext.c | |
| download | clectio-42fd9a69fa2bc9ef11c65c169a58ff40c9cb0fa1.tar.gz clectio-42fd9a69fa2bc9ef11c65c169a58ff40c9cb0fa1.zip | |
clectio: tiny suckless C daily readings, all readings compiled in
clectio prints the Catholic liturgical day and its Mass readings for a date
with a single table lookup -- no computation, config file, network, or deps
beyond libc. The calendar, reading citations and scripture text are all
compiled in; the whole shipped package fits on a 1.44 MB floppy (~1.29 MiB)
and the binary is ~0.9 MB (OF) / ~0.3 MB (EF).
- suckless config.h: FORM_OF/FORM_EF, SIGLA_ENGLISH/SIGLA_LATIN, COLOR.
- Bring your own Bible: `make CORPUS=mybible.tsv` recompiles with any
Vulgate-numbered 6-column TSV; only the ~12k cited verses are embedded.
- The hard liturgical logic stays in lectio (Go, oracle-validated); its
clectio-gen emits the corpus-independent tables in gen/. The scripture text
is LZSS-packed (lz.h) to fit; mktext/bin2h resolve+embed it at build time.
- Verified byte-identical to lectio's readings; day names match 157/157 over
a 3-year sample. Default text: public-domain Latin Vulgate. ISC-licensed.
Claude-Session: https://claude.ai/code/session_01S1CD1u3tgSS4xNu6WHfp5a
Diffstat (limited to 'mktext.c')
| -rw-r--r-- | mktext.c | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/mktext.c b/mktext.c new file mode 100644 index 0000000..55af074 --- /dev/null +++ b/mktext.c @@ -0,0 +1,116 @@ +/* mktext -- resolve a lectionary's verse keys against a scripture corpus and + * write the LZSS-packed text clectio compiles in. This is what makes clectio + * bring-your-own-Bible: point it at any corpus formatted like ours (a 6-column + * TSV: Book, Abbrev, BookNum, Chapter, Verse, Text) and it produces the text for + * exactly the verses the calendar cites -- nothing more. The corpus must use the + * same (Vulgate) chapter/verse numbering as the keys. + * + * usage: mktext verses.keys corpus.tsv out.lz + * out.lz = [uint32 LE rawlen][LZSS-packed "chap:verse text\n" lines]. + */ +#define LZ_PACK +#include "lz.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +typedef struct { + char *key; /* "Book\tChap\tVerse" */ + char *cv; /* "Chap:Verse" */ + char *text; /* verse text */ +} Rec; + +static Rec *recs; +static size_t nrec; + +static int cmp(const void *a, const void *b) { + return strcmp(((const Rec *)a)->key, ((const Rec *)b)->key); +} + +static char *slurp(const char *path, size_t *len) { + FILE *f = fopen(path, "rb"); + if (!f) { fprintf(stderr, "mktext: cannot open %s\n", path); exit(1); } + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = malloc((size_t)n + 1); + if (fread(buf, 1, (size_t)n, f) != (size_t)n) { fprintf(stderr, "mktext: read error\n"); exit(1); } + buf[n] = 0; + fclose(f); + *len = (size_t)n; + return buf; +} + +/* split a TSV line into fields in place; return field count (max 6). */ +static int fields(char *line, char *f[6]) { + int n = 0; + f[n++] = line; + for (char *p = line; *p && n < 6; p++) + if (*p == '\t') { *p = 0; f[n++] = p + 1; } + return n; +} + +int main(int argc, char **argv) { + if (argc != 4) { + fprintf(stderr, "usage: mktext verses.keys corpus.tsv out.lz\n"); + return 2; + } + size_t klen, tlen; + char *keys = slurp(argv[1], &klen); + char *tsv = slurp(argv[2], &tlen); + + /* index the corpus by (Book\tChap\tVerse) */ + size_t cap = 4096; + recs = malloc(cap * sizeof(Rec)); + for (char *line = strtok(tsv, "\n"); line; line = strtok(NULL, "\n")) { + char *fl[6]; + if (fields(line, fl) < 6) + continue; + if (nrec == cap) { cap *= 2; recs = realloc(recs, cap * sizeof(Rec)); } + size_t kl = strlen(fl[0]) + strlen(fl[3]) + strlen(fl[4]) + 3; + char *key = malloc(kl); + snprintf(key, kl, "%s\t%s\t%s", fl[0], fl[3], fl[4]); + size_t cl = strlen(fl[3]) + strlen(fl[4]) + 2; + char *cv = malloc(cl); + snprintf(cv, cl, "%s:%s", fl[3], fl[4]); + recs[nrec].key = key; + recs[nrec].cv = cv; + recs[nrec].text = fl[5]; + nrec++; + } + qsort(recs, nrec, sizeof(Rec), cmp); + + /* resolve each key line to "chap:verse text\n" */ + size_t obuf = 1 << 20, olen = 0; + char *out = malloc(obuf); + size_t missing = 0, nkeys = 0; + for (char *line = strtok(keys, "\n"); line; line = strtok(NULL, "\n")) { + nkeys++; + Rec probe; + probe.key = line; + Rec *r = bsearch(&probe, recs, nrec, sizeof(Rec), cmp); + const char *cv = "0:0", *text = ""; + if (r) { cv = r->cv; text = r->text; } + else { missing++; /* keep a placeholder so indices stay aligned */ } + size_t need = strlen(cv) + strlen(text) + 3; + while (olen + need > obuf) { obuf *= 2; out = realloc(out, obuf); } + olen += (size_t)snprintf(out + olen, need, "%s %s", cv, text); + out[olen++] = '\n'; + } + if (missing) + fprintf(stderr, "mktext: warning: %zu of %zu verses not in corpus (blank)\n", missing, nkeys); + + size_t plen; + unsigned char *packed = lz_pack((unsigned char *)out, olen, &plen); + + FILE *of = fopen(argv[3], "wb"); + if (!of) { fprintf(stderr, "mktext: cannot write %s\n", argv[3]); return 1; } + unsigned char hdr[4] = { (unsigned char)olen, (unsigned char)(olen >> 8), + (unsigned char)(olen >> 16), (unsigned char)(olen >> 24) }; + fwrite(hdr, 1, 4, of); + fwrite(packed, 1, plen, of); + fclose(of); + fprintf(stderr, "mktext: %zu verses, %zu -> %zu bytes (%.1fx)\n", + nkeys, olen, plen, olen ? (double)olen / (double)plen : 0); + return 0; +} |
