aboutsummaryrefslogtreecommitdiff
path: root/Makefile
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 11:48:30 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 11:48:30 +0200
commit6762ce46af3cb12bc6ae37cda762c5d95add7903 (patch)
treebc1d8c86050d0149ff961a9a4ff838f9c474ac2a /Makefile
parent897c274fd28402159ca6d45eedc1257b1ce98696 (diff)
parent390bc6ac5196a946c473d0dbe7760fa41837c428 (diff)
downloadcolitur-6762ce46af3cb12bc6ae37cda762c5d95add7903.tar.gz
colitur-6762ce46af3cb12bc6ae37cda762c5d95add7903.zip
feat: output, rendering and publishing
Gives colitur a publishable exit. Until now its only output was terminal rows; it can now print an ordo booklet and a wall calendar, publish an iCalendar feed people subscribe to, and serve a static JSON/XML API. lib/render escaping (six flavours + RFC 5545 folding), a deliberately logic-less template engine, the view model, and five emitters (CSV, JSON, XML, iCalendar, S-expression) CLI emit, table, render, publish -- all accepting --overlay templates ordo booklet in six flavours, wall grid in three schema day-v1.json and colitur-v1.xsd, the published contract man colitur-templates.5, plus colitur.1 updates The view model is why the engine can stay logic-less: a month grid needs leading blank cells, week bucketing and an in-month test, and a logic-less template can compute none of it. Shaping the data in OCaml keeps the engine safe for untrusted templates and makes the grid trivial. Formats split by whether correctness is mechanical. Presentation goes through templates; iCalendar and XML get dedicated emitters, because folding, exclusive DTEND, stable UIDs and schema fidelity are rules a template cannot enforce and each fails silently in a subscriber's client rather than loudly at generation. publish is deterministic and non-destructive: two runs produce a byte-identical tree, and --prune removes only files a previous run created, refusing any manifest entry that escapes the output directory. No new dependencies. The kernel and rite modules are untouched, and colitur day and colitur readings remain byte-identical.
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile65
1 files changed, 61 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 5b3bb55..d9377df 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,11 @@ MAN5DIR := $(PREFIX)/share/man/man5
# (<prefix>/share/colitur/ef), so `dune install` -- not a hand-rolled copy --
# is what places the four .sexp files where a running colitur will look for
# them. See bin/main.ml's own [data_dir] for the full resolution order.
+# Task 13 (schema/dune, templates/dune) extends the same `dune install`
+# mechanism to <prefix>/share/colitur/schema/ (bin/main.ml's own
+# [schema_path], read by `colitur publish`) and <prefix>/share/colitur/
+# templates/ (a convenience install -- `--template` takes a plain file path,
+# nothing in colitur probes this location the way [data_dir]/[schema_path] do).
#
# dune needs the project-local opam switch on PATH. Every recipe that invokes
# dune goes through this, so `make` works from a plain shell with no
@@ -17,7 +22,7 @@ MAN5DIR := $(PREFIX)/share/man/man5
# over documenting the dune commands.
DUNE := opam exec --
-.PHONY: help build test check install uninstall reinstall clean fmt man doc release
+.PHONY: help build test check check-schema check-templates install uninstall reinstall clean fmt man doc release
help: ## show this help
@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed -E 's/:.*## /\t/' | sort
@@ -30,19 +35,69 @@ test: ## fast suite (~5s): properties sample 200 years
check: ## full gate (~2min): every year 1583-9999, not a sample
COLITUR_EXHAUSTIVE_SWEEP=1 $(DUNE) dune test --force
-install: build ## install binary, calendar data and man page into PREFIX (default ~/.local)
+check-schema: build ## validate emitted XML against schema/colitur-v1.xsd (needs xmllint; skipped if absent)
+ @if command -v xmllint >/dev/null 2>&1; then \
+ opam exec -- dune exec colitur -- emit --format xml --from 2027 --to 2027 > /tmp/colitur-check.xml && \
+ xmllint --noout --schema schema/colitur-v1.xsd /tmp/colitur-check.xml && \
+ echo "xml: valid against schema/colitur-v1.xsd"; \
+ else \
+ echo "SKIPPED: xmllint not installed -- XML is emitted but NOT schema-validated"; \
+ fi
+
+# Typesets every shipped template through the real tool that would typeset it
+# for a reader, not merely through this program's own renderer. The golden
+# tests already prove `colitur table` PRODUCES the expected LaTeX/groff/HTML
+# text; only this proves that text actually TYPESETS -- a template can render
+# byte-for-byte as pinned and still be malformed LaTeX or groff. Each tool is
+# genuinely optional (neither is in colitur's own frozen deps), so absence is
+# printed LOUDLY as SKIPPED rather than silently treated as a pass -- a
+# silent skip reads as a pass, which is the whole failure mode this guards
+# against.
+check-templates: build ## typeset every shipped template (needs pdflatex/groff; skipped if absent)
+ @ok=1; \
+ if command -v pdflatex >/dev/null 2>&1; then \
+ for t in ordo grid; do \
+ opam exec -- dune exec colitur -- table --year 2027 --template templates/ef/$$t.tex > /tmp/$$t.tex && \
+ (cd /tmp && pdflatex -halt-on-error -interaction=nonstopmode $$t.tex >/dev/null) && \
+ echo "pdflatex: $$t.tex OK" || { echo "pdflatex: $$t.tex FAILED"; ok=0; }; \
+ done; \
+ else echo "SKIPPED: pdflatex not installed -- LaTeX templates render but are NOT typeset"; fi; \
+ if command -v groff >/dev/null 2>&1; then \
+ for t in ordo grid; do \
+ gflags=""; \
+ [ "$$t" = grid ] && gflags="-P-pa4l"; \
+ if opam exec -- dune exec colitur -- table --year 2027 --template templates/ef/$$t.ms \
+ > /tmp/$$t.ms 2>/tmp/$$t-render.err; then \
+ groff -ms -t -Tpdf $$gflags /tmp/$$t.ms > /tmp/$$t-ms.pdf 2>/tmp/$$t-ms.warnings; \
+ if [ -s /tmp/$$t-ms.warnings ]; then \
+ echo "groff: $$t.ms FAILED (groff exits 0 on a tbl/troff warning -- this target"; \
+ echo " treats ANY stderr output as a failure, since a warning here has meant"; \
+ echo " silently lost table columns before):"; \
+ cat /tmp/$$t-ms.warnings; ok=0; \
+ else \
+ echo "groff: $$t.ms OK"; \
+ fi; \
+ else \
+ echo "groff: $$t.ms FAILED (colitur table render):"; cat /tmp/$$t-render.err; ok=0; \
+ fi; \
+ done; \
+ else echo "SKIPPED: groff not installed -- groff templates render but are NOT typeset"; fi; \
+ test $$ok -eq 1
+
+install: build ## install binary, calendar data, templates, schema and man pages into PREFIX (default ~/.local)
$(DUNE) dune install --prefix $(PREFIX)
@mkdir -p $(MANDIR)
install -m 644 man/colitur.1 $(MANDIR)/colitur.1
@mkdir -p $(MAN5DIR)
install -m 644 man/colitur-overlay.5 $(MAN5DIR)/colitur-overlay.5
- @echo "installed $(BINDIR)/$(COLITUR), data in $(PREFIX)/share/colitur/ef, man pages in $(MANDIR) and $(MAN5DIR)"
+ install -m 644 man/colitur-templates.5 $(MAN5DIR)/colitur-templates.5
+ @echo "installed $(BINDIR)/$(COLITUR), data in $(PREFIX)/share/colitur/{ef,templates,schema}, man pages in $(MANDIR) and $(MAN5DIR)"
@command -v $(COLITUR) >/dev/null 2>&1 || \
echo "note: $(BINDIR) is not on PATH -- add it, or run $(BINDIR)/$(COLITUR) directly"
uninstall: ## remove everything install put into PREFIX
-$(DUNE) dune uninstall --prefix $(PREFIX)
- rm -f $(MANDIR)/colitur.1 $(MAN5DIR)/colitur-overlay.5
+ rm -f $(MANDIR)/colitur.1 $(MAN5DIR)/colitur-overlay.5 $(MAN5DIR)/colitur-templates.5
@echo "removed $(COLITUR) from $(PREFIX)"
reinstall: uninstall install ## uninstall then install (the installed copy is a snapshot, not a link)
@@ -50,10 +105,12 @@ reinstall: uninstall install ## uninstall then install (the installed copy is a
man: ## preview the man pages
man -l man/colitur.1
man -l man/colitur-overlay.5
+ man -l man/colitur-templates.5
doc: ## lint the man pages (groff warnings; silence means clean)
groff -man -Tutf8 -ww -z man/colitur.1
groff -man -Tutf8 -ww -z man/colitur-overlay.5
+ groff -man -Tutf8 -ww -z man/colitur-templates.5
fmt: ## format the OCaml sources
$(DUNE) dune build @fmt --auto-promote