summaryrefslogtreecommitdiffhomepage
path: root/pipeline
diff options
context:
space:
mode:
Diffstat (limited to 'pipeline')
-rw-r--r--pipeline/README.md99
-rwxr-xr-xpipeline/build.sh31
-rw-r--r--pipeline/config.sh37
-rwxr-xr-xpipeline/one.sh38
-rwxr-xr-xpipeline/pdf.sh26
-rwxr-xr-xpipeline/read.sh47
-rwxr-xr-xpipeline/run.sh19
-rwxr-xr-xpipeline/write.sh89
8 files changed, 386 insertions, 0 deletions
diff --git a/pipeline/README.md b/pipeline/README.md
new file mode 100644
index 0000000..59b98dc
--- /dev/null
+++ b/pipeline/README.md
@@ -0,0 +1,99 @@
+# The pipeline: how to run the readers, the library and the writers
+
+Fixed scripts rather than a test suite. They produce output that is meant to
+be **looked at**, and they double as the answer to "what do I type?".
+
+```
+config.sh where everything is, and where the output goes
+build.sh build every reader, library and writer, in both languages
+one.sh one tool, one document - the answer to "how do I run this?"
+read.sh every reader and both libraries, over every document
+write.sh every writer, in both languages, over every document
+pdf.sh compile the .typ files that write.sh produced
+run.sh all of the above, in order
+```
+
+## What you need first
+
+An **abstraction** to read: a directory holding a `.ssp` for each
+document-language and a `.ocda.db` for each document, and, where there is
+one, a `.sisupod` beside it. That is what spine writes:
+
+```sh
+spine --abstraction --ocda-db --pod2 --output=<dir> <pod>...
+```
+
+Everything downstream reads those files and nothing else. **No tool here
+parses markup**: parsing is spine's work, and the point of the exercise is
+that the abstraction is enough without it.
+
+## Running it
+
+```sh
+ARTEFACTS=<dir> OUT=<dir> sh pipeline/run.sh
+```
+
+`ARTEFACTS` is the directory above. `OUT` is where the output goes and
+defaults to `./pipeline-out`. Both, and every path to a sibling repository,
+are variables with defaults in `config.sh`; override them in the environment
+rather than editing anything.
+
+`LIMIT=3` takes the first three documents, which is what to use while looking
+at something.
+
+## Where the output goes
+
+```
+$OUT/read/ssp-d/ ssp-g/ the .ssp readers' summaries
+$OUT/read/db-d/ db-g/ the .ocda.db readers' summaries
+$OUT/read/dump-d/ dump-g/ the library's dump, from each serialisation
+$OUT/write/text/{d,g}/ <doc>.txt
+$OUT/write/md/{d,g}/ <doc>.md
+$OUT/write/html/{d,g}/ <doc>.html
+$OUT/write/epub/{d,g}/ <doc>.epub
+$OUT/write/typst/{d,g}/<doc>/ <doc>.typ, and image/ beside it
+$OUT/write/pdf/ <doc>.pdf
+```
+
+`d` is the D implementation and `g` the Gleam one, throughout.
+
+## One tool, one document
+
+```sh
+sh pipeline/one.sh text-d <doc>.ssp
+sh pipeline/one.sh md-g <doc>.ocda.db en
+sh pipeline/one.sh html-d <doc>.sisupod
+sh pipeline/one.sh dump-d <doc>.ocda.db en
+sh pipeline/one.sh agree-g <doc>.ssp <doc>.ocda.db
+```
+
+The tool names are `<format>-<language>`: `text`, `md`, `html`, `epub`,
+`typst` for the writers, and `ssp`, `db`, `dump`, `agree` for the readers and
+the library; `-d` for D and `-g` for Gleam.
+
+Output goes to stdout, so a comparison is a pipeline. The epub and the typst
+name a file instead, because an epub is a zip and a `.typ` names its images by
+path:
+
+```sh
+"$WRITERS/dlang/result/bin/ocda-write" --epub <doc>.ocda.db en --out x.epub
+"$WRITERS/dlang/result/bin/ocda-write" --typst <doc>.ocda.db en --out d/x.typ
+typst compile --root d d/x.typ d/x.pdf
+```
+
+## What the counts at the end mean
+
+Each stage prints a few numbers. They are not a pass, they are a way to see a
+regression without reading 36 documents.
+
+| `all four dumps identical` | the library's whole claim: two serialisations, two languages, one model |
+|---|---|
+| `D == Gleam, <format>` | the writers agree, in unrelated languages, neither written from the other. This is what says the model is sufficient |
+| `.ssp and .ocda.db give the same .typ` | a writer is not told which serialisation it was handed |
+| `typst compiled silently` | a link to a label that does not exist is a typst compile error, so this says every internal link in every document resolves |
+
+## What this is not
+
+It is not the project's checking. The sweeps that hold each writer against an
+oracle - `spine --text`, spine's scroll html, epubcheck, typst - are separate
+and stricter. This is the pipeline you run to *look* at all of it at once.
diff --git a/pipeline/build.sh b/pipeline/build.sh
new file mode 100755
index 0000000..e4eac9c
--- /dev/null
+++ b/pipeline/build.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Build every reader, every library and every writer, in both languages.
+#
+# Each project is its own flake and builds on its own. The library has no
+# public url yet, so the three that depend on it are given the local copy with
+# --override-input; that argument goes away when it is published.
+set -e
+. "$(dirname "$0")/config.sh"
+
+build() { # <directory> [flake output]
+ dir=$1; out=${2:-}
+ printf '%-44s' "$(basename "$(dirname "$dir")")/$(basename "$dir") ${out}"
+ if ( cd "$dir" && nix build ${out:+"$out"} \
+ --override-input ocda-lib "path:$OCDA_LIB" >/dev/null 2>&1 ) \
+ || ( cd "$dir" && nix build ${out:+"$out"} >/dev/null 2>&1 )
+ then echo ok
+ else echo FAILED; failed=1
+ fi
+}
+
+failed=0
+build "$OCDA_LIB/dlang" .#ocda-dump
+build "$OCDA_LIB/dlang" .#ocda-agree
+build "$OCDA_LIB/gleam"
+build "$SSP_READER/dlang"
+build "$SSP_READER/gleam"
+build "$DB_READER/dlang"
+build "$DB_READER/gleam"
+build "$WRITERS/dlang"
+build "$WRITERS/gleam"
+exit $failed
diff --git a/pipeline/config.sh b/pipeline/config.sh
new file mode 100644
index 0000000..04c4bfd
--- /dev/null
+++ b/pipeline/config.sh
@@ -0,0 +1,37 @@
+# Where everything is, and where the output goes.
+#
+# Every path is a variable with a default, and the defaults assume the layout
+# these repositories are developed in: each project cloned beside the others,
+# and this one among them. Nothing here reaches outside those clones, so a
+# different arrangement needs only this file changed.
+#
+# Override any of them in the environment:
+#
+# OUT=/tmp/my-run sh pipeline/run.sh
+#
+# shellcheck shell=sh
+
+# this repository
+WRITERS=${WRITERS:-$(cd "$(dirname "$0")/.." && pwd)}
+
+# the sibling repositories, by their own names. A clone puts each under its
+# short name; where they sit relative to each other is the only local fact.
+SIBLINGS=${SIBLINGS:-$(cd "$WRITERS/.." && pwd)}
+OCDA_LIB=${OCDA_LIB:-$SIBLINGS/sisudoc-ocda-lib}
+SSP_READER=${SSP_READER:-$SIBLINGS/sisudoc-ssp-reader}
+DB_READER=${DB_READER:-$SIBLINGS/sisudoc-ocda-db-reader}
+
+# the abstraction artefacts to read: a directory holding .ssp files, and for
+# each of them a .ocda.db and, where there is one, a .sisupod beside it. This
+# is what spine writes with --abstraction --ocda-db --pod2; see pipeline/README.
+ARTEFACTS=${ARTEFACTS:-}
+
+# where the output goes. One directory per stage, so a run can be looked at.
+OUT=${OUT:-./pipeline-out}
+
+# the tools a pdf needs. Absent, the typst stage is skipped and says so.
+TYPST=${TYPST:-typst}
+
+# how many documents to run. Empty means all of them; a number takes the first
+# n, which is what to use while looking at something.
+LIMIT=${LIMIT:-}
diff --git a/pipeline/one.sh b/pipeline/one.sh
new file mode 100755
index 0000000..18ffc5f
--- /dev/null
+++ b/pipeline/one.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# One tool, one document: the answer to "how do I run this by hand?".
+#
+# sh pipeline/one.sh <tool> <file> [lang]
+#
+# where <tool> is one of
+#
+# ssp-d ssp-g the .ssp readers, D and Gleam
+# db-d db-g the .ocda.db readers
+# dump-d dump-g the library's canonical dump of the whole model
+# agree-d agree-g the library's two loaders against each other
+# text-d text-g the writers. Each takes a .ssp, a .ocda.db or a
+# md-d md-g .sisupod and is not told which.
+# html-d html-g
+# epub-d epub-g these two write a file, so they take --out
+# typst-d typst-g
+#
+# and <file> is a .ssp, a .ocda.db or a .sisupod. Output goes to stdout except
+# for the epub and the typst, which name a file.
+set -e
+. "$(dirname "$0")/config.sh"
+tool=${1:?usage: one.sh <tool> <file> [lang]}
+file=${2:?usage: one.sh <tool> <file> [lang]}
+lang=${3:-}
+
+case $tool in
+ ssp-d) exec "$SSP_READER/dlang/result/bin/ssp-reader" "$file" ;;
+ ssp-g) exec "$SSP_READER/gleam/result/bin/ssp_reader" "$file" ;;
+ db-d) exec "$DB_READER/dlang/result/bin/ocda-db-reader" "$file" ;;
+ db-g) exec "$DB_READER/gleam/result/bin/ocda-db-reader" "$file" ;;
+ dump-d) exec "$OCDA_LIB/dlang/result/bin/ocda-dump" "$file" $lang ;;
+ dump-g) exec "$OCDA_LIB/gleam/result/bin/ocda-lib" dump "$file" $lang ;;
+ agree-d) exec "$OCDA_LIB/dlang/result/bin/ocda-agree" "$file" $lang ;;
+ agree-g) exec "$OCDA_LIB/gleam/result/bin/ocda-lib" agree "$file" $lang ;;
+ *-d) exec "$WRITERS/dlang/result/bin/ocda-write" "--${tool%-d}" "$file" $lang ;;
+ *-g) exec "$WRITERS/gleam/result/bin/ocda-write" "--${tool%-g}" "$file" $lang ;;
+ *) echo "one.sh: unknown tool $tool" >&2; exit 2 ;;
+esac
diff --git a/pipeline/pdf.sh b/pipeline/pdf.sh
new file mode 100755
index 0000000..a769137
--- /dev/null
+++ b/pipeline/pdf.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Compile every .typ the write stage produced, into $OUT/write/pdf/.
+#
+# typst is the only thing this stage needs, and it is a single binary. A link
+# to a label that does not exist is a compile error, so a stage that says
+# nothing has also said that every internal link in every document resolves.
+set -e
+. "$(dirname "$0")/config.sh"
+command -v "$TYPST" >/dev/null 2>&1 || {
+ echo "pdf.sh: no typst on the path; set TYPST=/path/to/typst"; exit 0; }
+mkdir -p "$OUT/write/pdf"
+n=0; ok=0
+for typ in "$OUT"/write/typst/d/*/*.typ; do
+ [ -f "$typ" ] || continue
+ dir=$(dirname "$typ"); stem=$(basename "$typ" .typ)
+ n=$((n+1))
+ if "$TYPST" compile --root "$dir" "$typ" "$OUT/write/pdf/$stem.pdf" \
+ > "$OUT/write/pdf/$stem.log" 2>&1 && [ ! -s "$OUT/write/pdf/$stem.log" ]
+ then ok=$((ok+1))
+ else echo "TYPST $stem: $(head -3 "$OUT/write/pdf/$stem.log" | tr '\n' ' ' | cut -c1-140)"
+ fi
+done
+echo
+echo "documents typeset: $n"
+echo "typst compiled silently $ok"
+echo " (so every internal link resolves)"
diff --git a/pipeline/read.sh b/pipeline/read.sh
new file mode 100755
index 0000000..b3129a5
--- /dev/null
+++ b/pipeline/read.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+# Every reader and both libraries, over every document, into $OUT/read/.
+#
+# What each produces:
+#
+# read/ssp-{d,g}/<doc>.txt the .ssp readers' summary
+# read/db-{d,g}/<doc>.txt the .ocda.db readers' summary
+# read/dump-{d,g}/<doc>.ssp the library's dump, loaded from the .ssp
+# read/dump-{d,g}/<doc>.db the same, loaded from the .ocda.db
+#
+# The dumps are the interesting ones. All four of them should be identical:
+# the two serialisations say the same thing, and the two languages read it the
+# same way. `diff` says so, and the summary at the end counts it.
+set -e
+. "$(dirname "$0")/config.sh"
+: "${ARTEFACTS:?set ARTEFACTS to a directory of .ssp and .ocda.db files}"
+mkdir -p "$OUT/read"
+for d in ssp-d ssp-g db-d db-g dump-d dump-g; do mkdir -p "$OUT/read/$d"; done
+
+list=$(find "$ARTEFACTS" -name '*.ssp' | sort)
+[ -n "$LIMIT" ] && list=$(echo "$list" | head -n "$LIMIT")
+
+n=0; same=0
+for ssp in $list; do
+ stem=$(basename "$ssp" .ssp); lang=${stem##*.}; uid=${stem%.*}
+ db=$(find "$ARTEFACTS" -name "$uid.ocda.db" | head -1)
+ n=$((n+1))
+ sh "$(dirname "$0")/one.sh" ssp-d "$ssp" > "$OUT/read/ssp-d/$stem.txt"
+ sh "$(dirname "$0")/one.sh" ssp-g "$ssp" > "$OUT/read/ssp-g/$stem.txt"
+ sh "$(dirname "$0")/one.sh" dump-d "$ssp" > "$OUT/read/dump-d/$stem.ssp"
+ sh "$(dirname "$0")/one.sh" dump-g "$ssp" > "$OUT/read/dump-g/$stem.ssp"
+ [ -n "$db" ] || continue
+ sh "$(dirname "$0")/one.sh" db-d "$db" > "$OUT/read/db-d/$stem.txt"
+ sh "$(dirname "$0")/one.sh" db-g "$db" > "$OUT/read/db-g/$stem.txt"
+ sh "$(dirname "$0")/one.sh" dump-d "$db" "$lang" > "$OUT/read/dump-d/$stem.db"
+ sh "$(dirname "$0")/one.sh" dump-g "$db" "$lang" > "$OUT/read/dump-g/$stem.db"
+ # the four-way agreement, which is the library's whole claim
+ if cmp -s "$OUT/read/dump-d/$stem.ssp" "$OUT/read/dump-d/$stem.db" \
+ && cmp -s "$OUT/read/dump-g/$stem.ssp" "$OUT/read/dump-g/$stem.db" \
+ && cmp -s "$OUT/read/dump-d/$stem.ssp" "$OUT/read/dump-g/$stem.ssp"
+ then same=$((same+1))
+ else echo "DUMPS DIFFER $stem"
+ fi
+done
+echo
+echo "documents read: $n"
+echo "all four dumps identical $same"
diff --git a/pipeline/run.sh b/pipeline/run.sh
new file mode 100755
index 0000000..811514e
--- /dev/null
+++ b/pipeline/run.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# The whole pipeline: build, read, write, typeset.
+#
+# ARTEFACTS=<dir of .ssp and .ocda.db> OUT=<dir> sh pipeline/run.sh
+#
+# Nothing here is a test in the pass/fail sense; it is a run whose output is
+# meant to be looked at. The counts it prints are there so that a change that
+# breaks something is visible without reading 36 documents.
+set -e
+here=$(dirname "$0")
+. "$here/config.sh"
+: "${ARTEFACTS:?set ARTEFACTS to a directory of .ssp and .ocda.db files}"
+mkdir -p "$OUT"
+echo "== build"; sh "$here/build.sh"
+echo; echo "== read"; sh "$here/read.sh"
+echo; echo "== write"; sh "$here/write.sh"
+echo; echo "== pdf"; sh "$here/pdf.sh"
+echo
+echo "output in $OUT"
diff --git a/pipeline/write.sh b/pipeline/write.sh
new file mode 100755
index 0000000..7e1bead
--- /dev/null
+++ b/pipeline/write.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+# Every writer, in both languages, over every document, into $OUT/write/.
+#
+# write/text/{d,g}/<doc>.txt
+# write/md/{d,g}/<doc>.md
+# write/html/{d,g}/<doc>.html
+# write/epub/{d,g}/<doc>.epub
+# write/typst/{d,g}/<doc>/<doc>.typ and image/ beside it
+#
+# Each writer is given the .ocda.db, because that carries the images. The .ssp
+# is used as well, for the one comparison that matters at this level: a writer
+# is not told which serialisation it was handed, so the two must agree.
+#
+# The summary counts, for each format, how many documents the two languages'
+# writers agreed on byte for byte. That is the check that says the model is
+# sufficient: neither writer was written from the other.
+set -e
+. "$(dirname "$0")/config.sh"
+: "${ARTEFACTS:?set ARTEFACTS to a directory of .ssp and .ocda.db files}"
+
+list=$(find "$ARTEFACTS" -name '*.ssp' | sort)
+[ -n "$LIMIT" ] && list=$(echo "$list" | head -n "$LIMIT")
+
+for fmt in text md html epub typst; do
+ for impl in d g; do mkdir -p "$OUT/write/$fmt/$impl"; done
+done
+
+n=0
+agree_text=0; agree_md=0; agree_html=0; agree_epub=0; agree_typst=0
+forms=0
+for ssp in $list; do
+ stem=$(basename "$ssp" .ssp); lang=${stem##*.}; uid=${stem%.*}
+ db=$(find "$ARTEFACTS" -name "$uid.ocda.db" | head -1)
+ src=${db:-$ssp}
+ [ -n "$db" ] && args="$src $lang" || args="$src"
+ n=$((n+1))
+ one() { sh "$(dirname "$0")/one.sh" "$@"; }
+
+ one text-d $args > "$OUT/write/text/d/$stem.txt"
+ one text-g $args > "$OUT/write/text/g/$stem.txt"
+ cmp -s "$OUT/write/text/d/$stem.txt" "$OUT/write/text/g/$stem.txt" \
+ && agree_text=$((agree_text+1)) || echo "TEXT DIFFERS $stem"
+
+ one md-d $args > "$OUT/write/md/d/$stem.md"
+ one md-g $args > "$OUT/write/md/g/$stem.md"
+ cmp -s "$OUT/write/md/d/$stem.md" "$OUT/write/md/g/$stem.md" \
+ && agree_md=$((agree_md+1)) || echo "MD DIFFERS $stem"
+
+ one html-d $args > "$OUT/write/html/d/$stem.html"
+ one html-g $args > "$OUT/write/html/g/$stem.html"
+ cmp -s "$OUT/write/html/d/$stem.html" "$OUT/write/html/g/$stem.html" \
+ && agree_html=$((agree_html+1)) || echo "HTML DIFFERS $stem"
+
+ "$WRITERS/dlang/result/bin/ocda-write" --epub $args \
+ --out "$OUT/write/epub/d/$stem.epub"
+ "$WRITERS/gleam/result/bin/ocda-write" --epub $args \
+ --out "$OUT/write/epub/g/$stem.epub"
+ cmp -s "$OUT/write/epub/d/$stem.epub" "$OUT/write/epub/g/$stem.epub" \
+ && agree_epub=$((agree_epub+1)) || echo "EPUB DIFFERS $stem"
+
+ # a .typ names its images by path, so it and they go in a directory together
+ for impl in d g; do mkdir -p "$OUT/write/typst/$impl/$stem"; done
+ "$WRITERS/dlang/result/bin/ocda-write" --typst $args \
+ --out "$OUT/write/typst/d/$stem/$stem.typ"
+ "$WRITERS/gleam/result/bin/ocda-write" --typst $args \
+ --out "$OUT/write/typst/g/$stem/$stem.typ"
+ cmp -s "$OUT/write/typst/d/$stem/$stem.typ" \
+ "$OUT/write/typst/g/$stem/$stem.typ" \
+ && agree_typst=$((agree_typst+1)) || echo "TYPST DIFFERS $stem"
+
+ # and the claim that a writer cannot tell the serialisations apart. A .typ
+ # names its images rather than carrying them, so this one holds for it even
+ # though only the .ocda.db has the image files.
+ if [ -n "$db" ]; then
+ "$WRITERS/dlang/result/bin/ocda-write" --typst "$ssp" > "$OUT/from-ssp.typ"
+ if cmp -s "$OUT/from-ssp.typ" "$OUT/write/typst/d/$stem/$stem.typ"
+ then forms=$((forms+1))
+ else echo "FORMS DIFFER $stem"
+ fi
+ fi
+done
+echo
+echo "documents written: $n"
+echo "D == Gleam, text $agree_text"
+echo "D == Gleam, markdown $agree_md"
+echo "D == Gleam, html $agree_html"
+echo "D == Gleam, epub $agree_epub"
+echo "D == Gleam, typst $agree_typst"
+echo ".ssp and .ocda.db give the same .typ $forms"