| name | last commit | updated |
|---|---|---|
| Sources/ | A list item holds block elements, not just paragraphs (!7) | 1 month ago |
| Tests/ | A list item holds block elements, not just paragraphs (!7) | 1 month ago |
| .gitignore | OrgSwift: extract org renderer from hutch | 1 month ago |
| ARCHITECTURE.md | Take TODO/DONE colors from the caller | 1 month ago |
| GAPS.md | A list item holds block elements, not just paragraphs (!7) | 1 month ago |
| Package.swift | Add OrgSwiftUI, a native renderer for the shared tree | 1 month ago |
| README.md | A list item holds block elements, not just paragraphs (!7) | 1 month ago |
OrgSwift
A dependency-free Swift library that renders a practical subset of org-mode. Two products:
OrgSwift— org to sanitized HTML orAttributedString. Pure Foundation, no SwiftUI, WebKit, UIKit, or third-party packages.OrgSwiftUI— org to native SwiftUI views, no web view involved.
Originally extracted from the hand-rolled renderer in the Hutch iOS client so it could be shared across apps, and since rebuilt around an element tree: org is parsed once into a document model, and each output format walks it. See ARCHITECTURE.md.
Supported syntax
Headings (with trailing :tags:), paragraphs, ordered/unordered lists (with
arbitrary-depth nesting, wrapped lines, and [ ]/[x]/[-] task checkboxes),
description lists (term :: definition → <dl>), multi-paragraph list items,
list items holding block elements (a source block, table, or quote inside an item),
tables (with :---: alignment), #+begin_src / example / quote / center /
verse / export blocks and #+begin_<name> special blocks,
#+TITLE/#+AUTHOR/#+DATE metadata, #+CAPTION/#+ATTR_HTML/#+NAME
figures, org links and images ([[file:x.png]] → <img>, [[dest][label]] →
link, [[id:target]] → #target), horizontal rules, comments, timestamps
(<2024-01-15 Mon>, inactive, times, and ranges → <time>), footnotes
(references, inline, and definitions → a <section class="footnotes">), and
inline markup (*bold*, /italic/, ~code~, =verbatim=, +strike+,
_underline_, x^2 superscript, bare-URL and email autolinks). Other
#+KEYWORD: lines are consumed as document metadata.
Output is sanitized: only http/https/mailto link schemes and
http/https image schemes are allowed; everything else is dropped.
Usage
import OrgSwift
let html = OrgRenderer.renderToHTML(orgSource)
Relative links
Pass an OrgRenderOptions to rewrite repository-relative links and images to
absolute blob URLs. When owner/repositoryName are nil (the default),
relative links are left as-is, which the scheme allowlist then drops.
let html = OrgRenderer.renderToHTML(
orgSource,
options: OrgRenderOptions(
host: "git.sr.ht", // default
owner: "~ccleberg",
repositoryName: "Hutch",
ref: "HEAD", // default
readmePath: "README.org" // resolves paths relative to this file
)
)
./images/badge.svg then resolves to
https://git.sr.ht/~ccleberg/Hutch/blob/HEAD/images/badge.svg.
Native SwiftUI
OrgSwiftUI renders the same parse as views rather than HTML — selectable text,
Dynamic Type, and VoiceOver, without a WKWebView.
import OrgSwiftUI
OrgView(orgSource, options: options)
OrgRenderOptions means the same thing here, so links and images resolve exactly
as they do in HTML. An image whose source will not resolve to an absolute URL
shows its alt text instead: HTML can lean on a document base URL, a view cannot.
Syntax highlighting for #+begin_src blocks is the app's to supply, which is what
keeps this package dependency-free:
struct Highlighter: OrgCodeStyler {
func highlighted(code: String, language: String?) -> AttributedString? {
// nil means "show it as plain monospaced text"
}
}
OrgView(orgSource, options: options, styler: Highlighter())
Org's TODO/DONE keywords default to semantic colors, because a package
cannot know your palette. Pass your own:
OrgView(orgSource, options: options,
keywords: OrgKeywordStyle(todo: .gbOK, done: .gbDone))
Title and heading level
Two presentation knobs, both defaulting to how Hutch rendered:
metadataHeader(defaulttrue) — emit a leading<div class="org-metadata">with the#+TITLE/#+AUTHOR/#+DATE. Setfalseto drop it (e.g. when the surrounding UI shows the title itself).headingLevelOffset(default0) — added to each heading's star count, clamped to1...6. Default renders*as<h1>; use1to render*as<h2>, leaving<h1>for a document title.
Syntax highlighting
Code blocks are highlighted through a protocol so the library carries no highlighter dependency:
public protocol CodeHighlighter {
func highlightedHTML(code: String, language: String?) -> String?
}
The default PlainCodeHighlighter returns nil, so blocks fall back to escaped
<pre><code>. Provide your own conformer to plug in a real highlighter:
let html = OrgRenderer.renderToHTML(orgSource, highlighter: MyHighlighter())
A conformer returning nil for a given block gets the same escaped fallback.
Conformance
OrgSwift is tested against the shared org-conformance
corpus, whose golden outputs come from orgo (itself validated against Emacs
ox-html). swift test locates a sibling ../org-conformance checkout, or set
ORG_CONFORMANCE_DIR. The suite reduces both this renderer's HTML and orgo's to
a semantic skeleton and compares them; the current pass/divergence state is
recorded per case, so a closed gap or a regression both surface as a failing
test. See GAPS.md for the outstanding backlog.
Clone
SSHgit clone ssh://git@gitbay.org/krz/org-swift.git
HTTPSgit clone https://gitbay.org/krz/org-swift.git