# NorgKit NorgKit is a Swift parser and library for `.norg` / [Neorg](https://github.com/nvim-neorg/neorg) documents. * Depends only on `Foundation`, so usable outside of iOS/ipadOS/macOS apps. * Concurrency safe and sendable. * Types are `Hashable` and `Codable` for caching and serialization. * Reasonably Fast. ## Installation Add the package with Swift Package Manager: ```swift .package(url: "https://git.r.bdr.sh/norg-ui", from: "1.0.0"), ``` …or the xcode package manager, and depend on the `NorgKit` product from your target. ## Usage `NorgParser.parse` outputs a flat list of blocks you can iterate over. ```swift import NorgKit let document = NorgParser.parse(""" * (x) Ship NorgKit Some *bold* and /italic/ prose, plus a {https://example.com}[link]. @code swift let answer = 42 @end """) for block in document.blocks { switch block { case let .heading(level, status, content, line): print("h\(level) @\(line): \(content.plainText) [\(status?.rawValue ?? "no status")]") case let .paragraph(content, _): print(content.plainText) case let .codeBlock(language, code, _): print("```\(language ?? "")\n\(code)\n```") default: break } } ``` ### Hierarchical parsing When you need the document's structure as a tree (eg. for folding), you can call either `.parseTree()`, or `.tree()` on a `NorgDocument` ```swift // Parse straight to a tree... let nodes = NorgParser.parseTree(source) // [NorgNode] // ...or fold an existing flat parse. let document = NorgParser.parse(source) let tree = document.tree() func render(_ node: NorgNode) { emit(node.block) // a NorgBlock node.children.forEach(render) } tree.forEach(render) ``` ### Inline-only parsing `NorgInlineParser.parse` parses a single line of inline markup into `[InlineSpan]`, useful when you only need intra-paragraph styling: ```swift let spans = NorgInlineParser.parse("a `verbatim` and *bold* run") ``` ### Scanning and updating tasks Besides the parser, `NorgKit` contains `TaskScanner`, which extracts tasks across a file and can rewrite status markers in place. It's faster than a parse; fast enough to run on a whole vault! (Eg. to look for all open tasks) ```swift let url = URL(filePath: "/vault/today.norg") let tasks = TaskScanner.scan(content: contents, fileURL: url) // Toggle the task on line 3 to done, preserving indentation. if let updated = TaskScanner.updatedContent(contents, line: 3, to: .done) { try updated.write(to: url, atomically: true, encoding: .utf8) } ``` ## Types Exported - **`NorgDocument`**: a parsed document. Has `blocks: [NorgBlock]` and a `tree()` method that folds them into `[NorgNode]`. - **`NorgBlock`**: a block-level element, including its `line`, and an attached `status` that denotes its task status if any, and can be copied to a new status (or have it cleared, with `nil`) via `settingStatus(_:)`. Its inline text is available as `content` and the nested blocks of a range-able block as `body`. Can be one of: - `heading` - `paragraph` - `unorderedListItem` - `orderedListItem` - `quote` - `codeBlock` - `definition` (`$ Term` / ranged `$$ … $$`), with a `title` and `body`, - `footnote` (`^ Name` / ranged `^^ … ^^`), with a `title` and `body`, - `tableCell` (`: Address` / ranged `:: … ::`), with a `title` and `body`, - `rangedTag`, - `horizontalRule` (`___`), - `weakDelimiter` (`---`), - `strongDelimiter` (`===`). - **`NorgNode`**: a node in the hierarchical view from `tree()` / `parseTree`. Wraps a `block: NorgBlock` and its nested `children: [NorgNode]`. - **`InlineSpan`** — a run of text with `styles: InlineStyle` and an optional `link: InlineLink?`. `Array.plainText` concatenates the text, ignoring styling. - **`InlineLink`** — a linkable: a `kind` (`.link` for `{location}`, `.anchor` for `[name]`) and an optional `target`. The `target` is `nil` for a bare anchor declaration whose location is defined elsewhere in the document. - **`InlineStyle`** — an `OptionSet` of inline modifiers: - `.bold` - `.italic` - `.underline` - `.strikethrough` - `.verbatim` - `.superscript` - `.subscript` - `.spoiler` - `.math` - **`TaskStatus`** — a Norg TODO status, one of: - `.undone` - `.done` - `.needsInput` - `.urgent` - `.recurring` - `.pending` - `.onHold` - `.cancelled` - **`NorgTask`** — a task located in a file, with its `fileURL`, `line`, `status`, and `text`. ## Reasonably Fast (All measures p90) Benchmarked on an M1 Pro. A document with around 40 blocks parses in ~150µs. While a larger document with roughly 1,600 blocks (about 2,500 lines) does so in ~2ms. Folding costs ~100 microseconds, so the benchmarks above apply whether you fold or not. Scanning tasks is faster and meant to be run over whole vaults. The same ~2,500-line document (around 800 tasks) takes ~420µs. You can run the benchmarks by using: ```bash just benchmark ``` ## Development A `Justfile` is provided to run common tasks: ```sh just build # swift build just test # swift test just lint # swiftlint just format # swift-format + swiftlint --fix ```