aboutsummaryrefslogtreecommitdiff
path: root/schema
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 /schema
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 'schema')
-rw-r--r--schema/colitur-v1.xsd49
-rw-r--r--schema/day-v1.json73
-rw-r--r--schema/dune16
3 files changed, 138 insertions, 0 deletions
diff --git a/schema/colitur-v1.xsd b/schema/colitur-v1.xsd
new file mode 100644
index 0000000..46af9a9
--- /dev/null
+++ b/schema/colitur-v1.xsd
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:element name="calendar">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="days">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="day" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="season" type="xs:string"/>
+ <xs:element name="week" type="xs:string"/>
+ <xs:element name="slug" type="xs:string"/>
+ <xs:element name="rank" type="xs:string"/>
+ <xs:element name="colour" type="xs:string"/>
+ <xs:element name="subject" type="xs:string"/>
+ <xs:element name="name" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="lang" type="xs:string" use="required"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="commemoration" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="citation" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="part" type="xs:string" use="required"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ <xs:attribute name="date" type="xs:string" use="required"/>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ <xs:attribute name="rite" type="xs:string" use="required"/>
+ <xs:attribute name="year" type="xs:string" use="required"/>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>
diff --git a/schema/day-v1.json b/schema/day-v1.json
new file mode 100644
index 0000000..b48133a
--- /dev/null
+++ b/schema/day-v1.json
@@ -0,0 +1,73 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://colitur/schema/day-v1.json",
+ "title": "colitur liturgical year, v1",
+ "type": "object",
+ "required": ["rite", "year", "months", "days"],
+ "properties": {
+ "rite": { "type": "string" },
+ "year": { "type": "string", "pattern": "^[0-9]{4}$" },
+ "months": { "type": "array", "items": { "$ref": "#/$defs/month" } },
+ "days": { "type": "array", "items": { "$ref": "#/$defs/day" } }
+ },
+ "$defs": {
+ "month": {
+ "type": "object",
+ "required": ["num", "name", "days", "weeks"],
+ "properties": {
+ "num": { "type": "string" },
+ "name": { "type": "object", "additionalProperties": { "type": "string" } },
+ "days": { "type": "array", "items": { "$ref": "#/$defs/day" } },
+ "weeks": { "type": "array", "items": { "$ref": "#/$defs/week" } }
+ }
+ },
+ "week": {
+ "type": "object",
+ "required": ["num", "days"],
+ "properties": {
+ "num": { "type": "string" },
+ "days": { "type": "array", "minItems": 7, "maxItems": 7, "items": { "$ref": "#/$defs/day" } }
+ }
+ },
+ "day": {
+ "type": "object",
+ "required": ["iso", "dom", "dow", "in_month", "season", "week", "slug",
+ "name", "rank", "colour", "is_white", "is_red", "is_green",
+ "is_violet", "is_rose", "is_black", "subject", "comms",
+ "transferred_in", "transferred_out", "first", "gospel", "last"],
+ "properties": {
+ "iso": { "type": "string", "description": "empty on a grid padding cell" },
+ "dom": { "type": "string" },
+ "dow": { "type": "string", "description": "0 = Sunday" },
+ "in_month": { "type": "boolean", "description": "false = grid padding cell" },
+ "season": { "type": "string" },
+ "week": { "type": "string", "description": "empty when the rite numbers no week here" },
+ "slug": { "type": "string" },
+ "name": { "type": "object", "additionalProperties": { "type": "string" } },
+ "rank": { "type": "string" },
+ "colour": { "type": "string", "enum": ["white","red","green","violet","rose","black",""] },
+ "is_white": { "type": "boolean" }, "is_red": { "type": "boolean" },
+ "is_green": { "type": "boolean" }, "is_violet":{ "type": "boolean" },
+ "is_rose": { "type": "boolean" }, "is_black": { "type": "boolean" },
+ "subject": { "type": "string" },
+ "comms": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["slug", "name", "privileged"],
+ "properties": {
+ "slug": { "type": "string" },
+ "name": { "type": "object", "additionalProperties": { "type": "string" } },
+ "privileged": { "type": "boolean" }
+ }
+ }
+ },
+ "transferred_in": { "type": "array", "items": { "type": "object" } },
+ "transferred_out": { "type": "array", "items": { "type": "object" } },
+ "first": { "type": "string", "description": "Epistle/Lesson citation, never scripture text" },
+ "gospel": { "type": "string", "description": "Gospel citation, never scripture text" },
+ "last": { "type": "boolean", "description": "true on the 7th cell of a grid row" }
+ }
+ }
+ }
+}
diff --git a/schema/dune b/schema/dune
new file mode 100644
index 0000000..b3c7dfe
--- /dev/null
+++ b/schema/dune
@@ -0,0 +1,16 @@
+; The published contracts, installed into <prefix>/share/colitur/schema/ so
+; an installed `colitur` can find schema/day-v1.json from an arbitrary
+; working directory, not only a build-tree checkout -- bin/main.ml's own
+; [schema_path] (`colitur publish`) probes exactly this location as its
+; INSTALLED candidate, mirroring data/dune's own ef/ layout. Task 13.
+;
+; colitur-v1.xsd ships alongside it as the XML sibling contract (`colitur
+; emit --format xml`); nothing in colitur itself reads it at runtime -- only
+; `make check-schema`, via xmllint, and a template author who wants to
+; validate their own rendered XML by hand.
+(install
+ (section share)
+ (package colitur)
+ (files
+ (day-v1.json as schema/day-v1.json)
+ (colitur-v1.xsd as schema/colitur-v1.xsd)))