/* clectio -- tiny, fast, suckless daily readings. * * Computes nothing at runtime: the liturgical calendar, its reading citations * and the scripture text are all compiled in (see gen/ and the Makefile). For a * date it does a table lookup and prints the day and its readings. Configuration * is compile-time (config.h), suckless-style: edit and recompile. * * Public domain. The compiled scripture corpus is NOT part of this program's * licence (see README): the default is the public-domain Latin Vulgate. */ #define _POSIX_C_SOURCE 200809L /* localtime_r, isatty under -std=c99 */ #define _DEFAULT_SOURCE /* struct winsize / TIOCGWINSZ under -std=c99 */ #include "config.h" #include LITURGY /* gen/liturgy_
.h: names,parts,cites_*,vpool,readings,rpool,days,cal,EPOCH,NDAYS */ #include "text.lz.h" /* embedded DEFLATE text blob: text_lz[], text_lz_len */ #include "puff.h" #include #include #include #include #include #include static char *textbuf; /* inflated verse text */ static const char **verse; /* verse[i] -> the text of the i-th key, nul-terminated */ static int usecolor; static const char *const colname[] = { "green", "white", "red", "violet", "rose", "black" }; static const char *const colansi[] = { "\033[32m", "\033[37m", "\033[31m", "\033[35m", "\033[95m", "\033[90m" }; /* Days since the civil epoch 1970-01-01 (Howard Hinnant's algorithm). */ static long days_from_civil(long y, unsigned m, unsigned d) { y -= m <= 2; long era = (y >= 0 ? y : y - 399) / 400; unsigned yoe = (unsigned)(y - era * 400); unsigned doy = (153u * (m + (m > 2 ? -3u : 9u)) + 2u) / 5u + d - 1; unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + (long)doe - 719468; } static void load_text(void) { unsigned long rawlen = text_lz[0] | text_lz[1] << 8 | text_lz[2] << 16 | (unsigned long)text_lz[3] << 24; unsigned long dstlen = rawlen, srclen = text_lz_len - 4; size_t cap = 8192, n = 0; char *p; textbuf = malloc(rawlen + 1); puff((unsigned char *)textbuf, &dstlen, text_lz + 4, &srclen); textbuf[dstlen] = 0; verse = malloc(cap * sizeof(*verse)); verse[n++] = textbuf; for (p = textbuf; *p; p++) if (*p == '\n') { *p = 0; if (p[1]) { if (n == cap) { cap *= 2; verse = realloc(verse, cap * sizeof(*verse)); } verse[n++] = p + 1; } } } static const char *citation(int r) { #ifdef SIGLA_LATIN return cites_la[readings_cite_la[r]]; #else return cites_en[readings[r].cite]; #endif } /* Column width of the terminal on stdout, or 72 if it cannot be determined. */ static int termwidth(void) { struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) return ws.ws_col; return 72; } /* Print s wrapped at w columns, breaking on spaces (greedy). w <= 0 prints s * unwrapped. UTF-8 aware: continuation bytes (0x80-0xBF) do not count as a * column, so multibyte letters measure as one. */ static void putwrapped(const char *s, int w) { if (w <= 0) { printf("%s\n", s); return; } while (*s) { const char *p = s, *brk = NULL; int col = 0; while (*p && col < w) { if (*p == ' ') brk = p; if ((*p & 0xC0) != 0x80) col++; p++; } if (!*p) { /* the rest fits on one line */ printf("%s\n", s); return; } if (!brk) /* a single word longer than w: hard-break at the column */ brk = p; fwrite(s, 1, (size_t)(brk - s), stdout); putchar('\n'); for (s = brk; *s == ' '; s++) /* skip the space(s) we broke on */ ; } } static void print_day(long off, const char *datestr, int gospel, int wrap) { const Day *d = &days[cal[off]]; unsigned ri; int n; if (usecolor) printf("%s%s\033[0m\n", colansi[d->colour], names[d->name]); else printf("%s \xc2\xb7 %s\n", names[d->name], colname[d->colour]); n = printf("%s for %s\n", FORMNAME, datestr) - 1; /* underline that line */ while (n-- > 0) putchar('='); putchar('\n'); for (ri = 0; ri < d->rlen; ri++) { const Reading *r = &readings[rpool[d->roff + ri]]; unsigned v; if (gospel && strcmp(parts[r->part], "Gospel") != 0) continue; printf("\n%s (%s)\n\n", parts[r->part], citation(rpool[d->roff + ri])); for (v = 0; v < r->vlen; v++) putwrapped(verse[vpool[r->voff + v]], wrap); } } int main(int argc, char **argv) { struct tm tm; long off; char datestr[16]; int y, m, dd, i; int gospel = GOSPEL_ONLY; /* compiled default, overridable per run */ int istty = isatty(1); int wrap; const char *datearg = NULL; usecolor = COLOR && istty; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { fprintf(stderr, "usage: clectio [-a|--all | -g|--gospel] [YYYY-MM-DD]\n" " -a, --all print all readings\n" " -g, --gospel print the Gospel only\n" " (default: today, %s)\n" " compiled: %s, %s, wrap %s, range %d-%d\n", READMODE, FORMNAME, SIGLANAME, WRAPNAME, EPOCH_Y, EPOCH_Y + (NDAYS / 366)); return 0; } else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--all")) { gospel = 0; } else if (!strcmp(argv[i], "-g") || !strcmp(argv[i], "--gospel")) { gospel = 1; } else if (argv[i][0] == '-' && argv[i][1]) { fprintf(stderr, "clectio: unknown option %s (try -h)\n", argv[i]); return 2; } else if (!datearg) { datearg = argv[i]; } else { fprintf(stderr, "clectio: unexpected argument %s (try -h)\n", argv[i]); return 2; } } if (datearg) { if (sscanf(datearg, "%d-%d-%d", &y, &m, &dd) != 3) { fprintf(stderr, "clectio: bad date %s (want YYYY-MM-DD)\n", datearg); return 2; } snprintf(datestr, sizeof datestr, "%04d-%02d-%02d", y, m, dd); } else { time_t t = time(NULL); localtime_r(&t, &tm); y = tm.tm_year + 1900; m = tm.tm_mon + 1; dd = tm.tm_mday; strftime(datestr, sizeof datestr, "%Y-%m-%d", &tm); } off = days_from_civil(y, (unsigned)m, (unsigned)dd) - days_from_civil(EPOCH_Y, EPOCH_M, EPOCH_D); if (off < 0 || off >= NDAYS) { fprintf(stderr, "clectio: %s is outside the compiled range %d..%d\n", datestr, EPOCH_Y, EPOCH_Y + (NDAYS / 366)); return 1; } /* WRAP>0: always wrap to that width. WRAP==0: fit the terminal, but leave * piped output as one line per verse (0 -> putwrapped prints unwrapped). */ if (WRAP > 0) wrap = WRAP; else wrap = istty ? termwidth() : 0; load_text(); print_day(off, datestr, gospel, wrap); return 0; }