ARCHITECTURE.md
79 lines · 4424 bytes
1# Architecture
2
3OrgSwift parses org into an element tree and renders that tree. Parsing happens once; each
4output format is a walk over the result.
5
6```
7Sources/OrgSwift/
8 AST/
9 OrgDocument.swift the element tree
10 OrgParser.swift source → OrgDocument (blocks)
11 OrgInlineParser.swift text → [OrgObject] (inlines)
12 OrgHTMLTreeRenderer.swift OrgDocument → HTML
13 OrgAttributedStringRenderer.swift [OrgObject] → AttributedString
14 OrgRenderer.swift the public entry point, plus OrgRenderOptions
15 Escaping.swift, Links.swift, … shared helpers: escaping, URL sanitising and
16 repository-relative resolution, line predicates
17 Skeleton.swift the HTML → semantic-skeleton reduction used by tests
18```
19
20`OrgRenderer.renderToHTML` is a thin façade over `OrgParser.parse` + `OrgHTMLTreeRenderer`.
21It, `OrgRenderOptions`, and `CodeHighlighter` are the whole public API most callers need.
22
23## Why a tree
24
25The renderer this replaced went from source straight to an HTML string in a single pass,
26doing inline work by regex-substituting markup into escaped text and protecting the results
27with placeholder tokens. That worked, but it baked HTML into the parse: any second output
28format would have meant re-deriving the parse rather than reusing it.
29
30With a tree, `OrgAttributedStringRenderer` is roughly 140 lines and shares the parse
31entirely. It is **Foundation-only** — no SwiftUI — so it works server-side, in a CLI,
32anywhere; a SwiftUI layer would sit on top of it for the inline runs inside each block.
33
34The types mirror orgo's `model.rs` — `OrgElement`/`OrgObject` against orgo's
35`Element`/`Object`, `OrgTableRow.{cells,rule}` against `TableRow::{Cells,Rule}`, the same
36`ListKind`/`Checkbox` vocabulary. That makes future *tree-level* conformance possible: today
37the corpus compares rendered HTML reduced to a skeleton, which is a string-level proxy for
38"do these two agree on structure", and matching trees would let that question be asked
39directly.
40
41Resolution is simpler on a tree, too. Link targets stay typed — `.external`, `.file`, `.id`
42— so repository-relative resolution applies to the `.file` case in the renderer instead of a
43resolver closure threaded through the parse. An unsafe or unresolvable target degrades to its
44text rather than becoming a bad anchor.
45
46## Conformance
47
48Measured against the [org-conformance](../org-conformance) corpus, whose goldens come from
49orgo (itself diffed against Emacs `ox-html`), reduced by the shared skeleton algorithm:
50
51**11 / 12** — every case except `outofscope`, which is `scope: out` in the corpus
52(deliberately unsupported constructs, where orgo itself may differ).
53
54`ConformanceTests` gates this: a regression fails, and `outofscope` starting to match fails
55too, forcing the record in its expectations map to be updated. `GAPS.md` carries the detail.
56
57## Notes from the migration
58
59The tree renderer replaced the single-pass one only after it was shown to be equivalent:
60every corpus case, each option combination, identical URL resolution, unsafe-scheme
61rejection, and a 28-construct battery drawn from the old renderer's own test inputs. That
62battery earned its keep — it caught three behaviours the corpus never reaches
63(`#+CAPTION:`/`#+NAME:` wrapping a non-image block in `<figure class="org-block">`, the
64nested `[[dest][[img]]]` badge form, and bare email autolinks), each of which would otherwise
65have regressed a consumer.
66
67One behaviour changed on purpose. A range whose halves are *inactive* timestamps, `[a]--[b]`,
68is now joined into one range; the old renderer joined active ranges only and left `--` as
69text. orgo applies the `--` rule to both bracket kinds, requiring only that the halves agree
70on activeness, so the new behaviour is the more correct one. It is asserted directly in
71`joinsInactiveTimestampRanges`.
72
73## Next
74
75A SwiftUI renderer belongs in a **separate product** depending on this one, so the parser and
76HTML renderer stay Foundation-only and callers who want HTML never import SwiftUI. Tables are
77the interesting part: `Grid`/`GridRow` with `.gridColumnAlignment()`, wrapped in a horizontal
78`ScrollView` for phone-width overflow — the approach MarkdownUI uses. `OrgTable` already
79carries the rows, the rule position, and the per-column alignments that needs.