README.md
100 lines · 3974 bytes
1# OrgSwift
2
3A dependency-free Swift library that renders a practical subset of
4[org-mode](https://orgmode.org) to sanitized HTML. Pure Foundation — no
5SwiftUI, WebKit, UIKit, or third-party packages.
6
7Originally extracted from the hand-rolled renderer in the Hutch iOS client so it
8could be shared across apps, and since rebuilt around an element tree: org is
9parsed once into a document model, and each output format walks it. See
10[ARCHITECTURE.md](ARCHITECTURE.md).
11
12## Supported syntax
13
14Headings (with trailing `:tags:`), paragraphs, ordered/unordered lists (with
15arbitrary-depth nesting, wrapped lines, and `[ ]`/`[x]`/`[-]` task checkboxes),
16description lists (`term :: definition` → `<dl>`), multi-paragraph list items,
17tables (with `:---:` alignment), `#+begin_src` / `example` / `quote` / `center` /
18`verse` / `export` blocks and `#+begin_<name>` special blocks,
19`#+TITLE`/`#+AUTHOR`/`#+DATE` metadata, `#+CAPTION`/`#+ATTR_HTML`/`#+NAME`
20figures, org links and images (`[[file:x.png]]` → `<img>`, `[[dest][label]]` →
21link, `[[id:target]]` → `#target`), horizontal rules, comments, timestamps
22(`<2024-01-15 Mon>`, inactive, times, and ranges → `<time>`), footnotes
23(references, inline, and definitions → a `<section class="footnotes">`), and
24inline markup (`*bold*`, `/italic/`, `~code~`, `=verbatim=`, `+strike+`,
25`_underline_`, `x^2` superscript, bare-URL and email autolinks). Other
26`#+KEYWORD:` lines are consumed as document metadata.
27
28Output is sanitized: only `http`/`https`/`mailto` link schemes and
29`http`/`https` image schemes are allowed; everything else is dropped.
30
31## Usage
32
33```swift
34import OrgSwift
35
36let html = OrgRenderer.renderToHTML(orgSource)
37```
38
39### Relative links
40
41Pass an `OrgRenderOptions` to rewrite repository-relative links and images to
42absolute `blob` URLs. When `owner`/`repositoryName` are nil (the default),
43relative links are left as-is, which the scheme allowlist then drops.
44
45```swift
46let html = OrgRenderer.renderToHTML(
47 orgSource,
48 options: OrgRenderOptions(
49 host: "git.sr.ht", // default
50 owner: "~ccleberg",
51 repositoryName: "Hutch",
52 ref: "HEAD", // default
53 readmePath: "README.org" // resolves paths relative to this file
54 )
55)
56```
57
58`./images/badge.svg` then resolves to
59`https://git.sr.ht/~ccleberg/Hutch/blob/HEAD/images/badge.svg`.
60
61### Title and heading level
62
63Two presentation knobs, both defaulting to how Hutch rendered:
64
65- `metadataHeader` (default `true`) — emit a leading `<div class="org-metadata">`
66 with the `#+TITLE`/`#+AUTHOR`/`#+DATE`. Set `false` to drop it (e.g. when the
67 surrounding UI shows the title itself).
68- `headingLevelOffset` (default `0`) — added to each heading's star count, clamped
69 to `1...6`. Default renders `*` as `<h1>`; use `1` to render `*` as `<h2>`,
70 leaving `<h1>` for a document title.
71
72### Syntax highlighting
73
74Code blocks are highlighted through a protocol so the library carries no
75highlighter dependency:
76
77```swift
78public protocol CodeHighlighter {
79 func highlightedHTML(code: String, language: String?) -> String?
80}
81```
82
83The default `PlainCodeHighlighter` returns `nil`, so blocks fall back to escaped
84`<pre><code>`. Provide your own conformer to plug in a real highlighter:
85
86```swift
87let html = OrgRenderer.renderToHTML(orgSource, highlighter: MyHighlighter())
88```
89
90A conformer returning `nil` for a given block gets the same escaped fallback.
91
92## Conformance
93
94OrgSwift is tested against the shared [org-conformance](../org-conformance)
95corpus, whose golden outputs come from `orgo` (itself validated against Emacs
96`ox-html`). `swift test` locates a sibling `../org-conformance` checkout, or set
97`ORG_CONFORMANCE_DIR`. The suite reduces both this renderer's HTML and orgo's to
98a semantic skeleton and compares them; the current pass/divergence state is
99recorded per case, so a closed gap or a regression both surface as a failing
100test. See [GAPS.md](GAPS.md) for the outstanding backlog.