1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
# ocda-write, in Gleam
Sample writers over the abstraction:
ocda-write --text|--md|--html|--epub|--typst <file> [lang]
ocda-write --md <file> [lang] [--image-prefix <path>]
writes a document out, from any serialisation of its abstraction.
## Modules
| | |
|---|---|
| `write/text` | the model -> spine's plain text, byte for byte |
| `write/html` | the model -> one self-contained, valid html5 file |
| `write/epub` | the model -> an epub3, one file per segment |
| `write/markdown` | the model -> CommonMark, object numbers and all |
| `write/typst` | the model -> typst markup, for a pdf |
| `write/zip` | writing a zip, stored only, with no timestamps |
| `write/rgx` | the internal markup, as compiled patterns |
| `ocda_write` | the program: which format, which file, which language |
`write.text.write` takes an `ocda/model.Document` and returns a `String`. It
is given no file, no path and no option: a writer is a function from the
model to bytes.
## Build and run
```sh
nix build --override-input ocda-lib path:../../sisudoc-ocda-lib
./result/bin/ocda-write --text <doc>.ssp
./result/bin/ocda-write --text <doc>.ocda.db de
./result/bin/ocda-write --md <doc>.ssp
./result/bin/ocda-write --epub <doc>.ocda.db en --out <doc>.epub
./result/bin/ocda-write --typst <doc>.ocda.db en --out out/<doc>.typ
```
The override applies to `nix develop` too, until the library is published:
```sh
nix develop --override-input ocda-lib path:../../sisudoc-ocda-lib -c gleam build
```
## The check
```sh
./result/bin/ocda-write --text <doc>.ssp | cmp - <the D writer's>
```
**36 of 36 document-languages, from both serialisations, byte identical to the
D writer** - which is the check that matters now, since byte identity with
`spine --text` is no longer the target.
And for `--md`, `--html`, `--epub` and `--typst`, **byte identical to the D
writer on all 36**, which is the agreement that says the model is enough. None
of them is held to spine's bytes: the epub's judge is epubcheck, which read the
specification so that the writer need not be trusted to have, and the pdf's is
typst, where a link to a label that does not exist is a compile error. See the
repository README for what they do differently and why.
## The library, as a path dependency inside the package
`gleam.toml` names it `./ocda-lib`, and that is a **symlink** to
`../../sisudoc-ocda-lib/gleam`, kept out of git.
It has to be inside the package: a path dependency reaching outside cannot
be seen by a sandboxed build, and a store path in `gleam.toml` would change
with every change to the library. So the path is stable, and what sits at
the end of it is arranged by whoever is building: a symlink in a working
tree, a copy from the store in the nix build, where nix-gleam puts the
library in place as a local package.
Create it after cloning:
```sh
ln -s ../../sisudoc-ocda-lib/gleam ocda-lib
```
## Places this is not idiomatic Gleam, on purpose
- **A column's width is the widest cell in bytes, and the padding is counted
in codepoints.** Not a typo. Spine takes the width from `cell.length`,
which for a D string is a byte count, and pads with `format("%-*s")`, which
counts codepoints. So a cell holding `©` is padded one place further than
one holding `c`. Measured rather than guessed, and reproduced rather than
corrected, because the output is compared byte for byte.
- **The escape in `write/typst` walks codepoints and folds a list**, where
everything else here is a regular expression. It has to: the escaping skips
protected runs, the protection nests, and the D writer walks codepoints, so
a grapheme walk would part company with it over a combining mark. `label_of`
walks codepoints for the same reason, and once did not: it used graphemes
while the D one used bytes, and five documents with typographic quotation
marks in a book index entry disagreed.
- **`gleam/regexp` has no replace-with-function**, so `replace_all` here is
`scan` plus a walk over the subject. It is the one piece of scaffolding
this writer needs that the D one gets from Phobos.
- **The claimed-id set is threaded across segments, not restarted in each.**
An id has to be unique in a *file* to be valid, and unique in the
*publication* to be useful: a link carries a file and a fragment, and an
anchor with two destinations has none. Restarting the set per segment gave
twelve documents that epubcheck passed and the D writer disagreed with.
- **Every replacement goes through the function form, never the string
form.** Erlang's `re:replace` reads `&` in a replacement *string* as the
whole match, so replacing `&` with `"&"` yields `"quot;` where the
match was a quotation mark. Byte identity with the D writer is what caught
it, on one document's copyright notice, and nothing else would have: the
file still parsed and still read correctly everywhere else.
## Shipping
An Erlang shipment rather than an escript, because the library reaches
sqlite through a NIF and a NIF cannot be loaded from inside an escript
archive.
|