diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-20 09:53:33 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-20 09:53:49 +0200 |
| commit | 66543815ae6cbf6e576a889c9291678744243953 (patch) | |
| tree | c3bc567ee53163abc3cf24bd7cba8038ee14e521 /test | |
| parent | 99dbddb2106f8a3ca31c7e0c43b7dc1a81652786 (diff) | |
| download | colitur-66543815ae6cbf6e576a889c9291678744243953.tar.gz colitur-66543815ae6cbf6e576a889c9291678744243953.zip | |
feat(render): Typst as a seventh escaping flavour
Adds Escape.Typst: type, all, to_string ("typst"), of_string,
of_extension (.typ), and a per-character expand function, structured
identically to the existing latex escaper (one pass, no re-scan, so
double-escaping stays impossible by construction).
The metacharacter set was verified against the installed typst 0.14.2
binary, not assumed: a probe document escaping each of #, *, _, $, @,
<, >, `, \, ~ and - was compiled and pdftotext'd back to confirm the
literal character survives, and each was separately confirmed to do
something else when left bare (# opens code mode, */_ toggle
strong/emph, $ opens math, @ opens a reference -- a bare unresolved
@word is a hard compile error, not merely mangled output -- </> can
close around a bare word into label syntax that swallows it whole, `
opens raw, ~ is a non-breaking space, and a run of two or three '-'
becomes an en/em dash). All ten are backslash-escapable; none needed a
non-backslash workaround. '-' is escaped unconditionally rather than
only inside a detected run, since this escaper has no lookahead -- a
probe confirmed escaping every hyphen independently still typesets as
literal hyphens for a run of any length, so the single per-character
rule is sufficient.
test_escape.ml's new Typst cases were written first and shown to fail
against a stubbed identity apply before the real escaper landed, per
this project's own regression-test discipline. Comments in both files
mark which new tests are genuine regression tests (the per-character
escaping, including a real shipped citation and a synthetic
markdown-habit overlay name) versus characterisation (to_string/
of_string/of_extension are flat table lookups with no logic to have
been wrong).
Diffstat (limited to 'test')
| -rw-r--r-- | test/test_escape.ml | 62 |
1 files changed, 61 insertions, 1 deletions
diff --git a/test/test_escape.ml b/test/test_escape.ml index 35a9b9d..7c55395 100644 --- a/test/test_escape.ml +++ b/test/test_escape.ml @@ -12,6 +12,56 @@ let test_latex () = check "real name" "Ss.mi Nominis Iesu \\& Mari\xc3\xa6" (E.apply E.Latex "Ss.mi Nominis Iesu & Mari\xc3\xa6") +(* Typst's markup metacharacters, verified against the installed `typst` + 0.14.2 binary rather than trusted from a spec list: a probe document + escaping each one individually, `typst compile`'d and `pdftotext`'d + back, confirmed the literal character survives (and, separately, that + the UNescaped form really does something else -- #box[..] consumes a + bare '#', *x* renders bold, @word errors "cannot reference text", a + bare <label> vanishes into label syntax, a~b becomes a non-breaking + join, and a--b/a---b become en/em dashes). Every case below is a + REGRESSION test: run against escape.ml's pre-fix stub (`Typst -> s`, + identity, no escaping at all -- see escape.ml's own TODO comment), + every one of these lines fails, because the stub hands Typst the bare + metacharacter it would then parse as markup instead of the + backslash-escaped literal. *) +let test_typst () = + check "hash" "\\#" (E.apply E.Typst "#"); + check "star" "\\*" (E.apply E.Typst "*"); + check "underscore" "\\_" (E.apply E.Typst "_"); + check "dollar" "\\$" (E.apply E.Typst "$"); + check "at" "\\@" (E.apply E.Typst "@"); + check "lt" "\\<" (E.apply E.Typst "<"); + check "gt" "\\>" (E.apply E.Typst ">"); + check "backtick" "\\`" (E.apply E.Typst "`"); + check "backslash first" "\\\\" (E.apply E.Typst "\\"); + check "tilde" "\\~" (E.apply E.Typst "~"); + check "dash" "\\-" (E.apply E.Typst "-"); + (* Runs of two/three unescaped hyphens become en/em dashes in live + Typst ("a--b" -> "a\xe2\x80\x93b", "a---b" -> "a\xe2\x80\x94b"). This + escaper has no lookahead (see [expand]'s own comment: one pass, no + re-scan), so it cannot detect "is this hyphen part of a run" -- it + escapes every '-' unconditionally instead, which a live probe + confirmed defangs a run of ANY length too: "\-\-\-" still typesets + as three literal hyphens, never an em dash. *) + check "double dash defanged" "\\-\\-" (E.apply E.Typst "--"); + check "triple dash defanged" "\\-\\-\\-" (E.apply E.Typst "---"); + (* A real shipped citation (data/ef/lectionary.sexp's 2 Nov Epistle), + not synthetic -- the metacharacter in this list most likely to + actually occur in colitur's own data, since citation ranges are + routinely hyphenated ("11-16") and Latin/English feast names almost + never carry '#'/'*'/'_'/'$'/'@'/'<'/'>'/'`'/'~'. *) + check "real citation" "Ezech 34:11\\-16" (E.apply E.Typst "Ezech 34:11-16"); + (* A realistic MISTAKE, not shipped data: this project also ships a + Markdown flavour, so a diocesan overlay author hand-editing a local + feast's `name` field in a text editor typing markdown-habit + asterisks for emphasis is a plausible real input, not a contrived + one -- verified live that unescaped asterisks here would render + bold, and confirmed escaped ones render as the two literal + asterisks the author actually typed. *) + check "overlay author's markdown-habit name" "Our Lady of the \\*Assumption\\*" + (E.apply E.Typst "Our Lady of the *Assumption*") + let test_groff () = check "backslash" "\\e" (E.apply E.Groff "\\"); (* RG-irrelevant but groff-critical: a leading dot starts a request. *) @@ -34,11 +84,20 @@ let test_ics () = let test_none_is_identity () = check "none" "& < > \\ % {}" (E.apply E.None_ "& < > \\ % {}") +(* CHARACTERISATION, not regression: to_string/of_string/of_extension are + flat data-table lookups with no per-character logic to get subtly + wrong (unlike [typst]/[latex]/[html]/[ics]'s own [expand] functions), + so they were correct the moment the Typst variant and its three table + entries were added -- there was no "stubbed wrong, then fixed" step + for this one to have failed against, unlike test_typst above. Pinned + here, not as its own case, because that is where every other + flavour's mapping already lives. *) let test_flavour_names () = List.iter (fun f -> Alcotest.(check bool) "roundtrip" true (E.of_string (E.to_string f) = Some f)) - [ E.Latex; E.Groff; E.Html; E.Xml; E.Ics; E.None_ ]; + [ E.Latex; E.Typst; E.Groff; E.Html; E.Xml; E.Ics; E.None_ ]; Alcotest.(check bool) "tex" true (E.of_extension ".tex" = Some E.Latex); + Alcotest.(check bool) "typ" true (E.of_extension ".typ" = Some E.Typst); Alcotest.(check bool) "ms" true (E.of_extension ".ms" = Some E.Groff); Alcotest.(check bool) "mom" true (E.of_extension ".mom" = Some E.Groff); Alcotest.(check bool) "html" true (E.of_extension ".html" = Some E.Html); @@ -108,6 +167,7 @@ let test_fold_pathological_input_terminates () = let suite = ( "Escape", [ Alcotest.test_case "latex" `Quick test_latex; + Alcotest.test_case "typst" `Quick test_typst; Alcotest.test_case "groff" `Quick test_groff; Alcotest.test_case "html/xml" `Quick test_html_xml; Alcotest.test_case "ics" `Quick test_ics; |
