krz/orgo
Lightning fast org-mode static site generator.
clone: git clone https://gitbay.org/krz/orgo.git
1//! The org element tree. This *is* the document model (spec §2.2) — the parser's
2//! output type is what the renderer consumes; there is no separate document AST.
3//!
4//! Org's two-tier structure is mirrored in the type system:
5//! - [`Element`] — block-level things (headings, paragraphs, lists, tables, blocks).
6//! - [`Object`] — inline things inside an element's content (bold, links, timestamps).
7//!
8//! This split lets the renderer never accidentally nest a heading inside emphasis.
9//! The whole tree is `serde`-serializable so the parse cache and golden-file
10//! snapshots share one representation (spec §4, §5).
11
12use camino::Utf8PathBuf;
13use chrono::NaiveDateTime;
14use serde::{Deserialize, Serialize};
15
16/// Content hash of raw source bytes. blake3 (spec §4.1). Serialized as hex.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct ContentHash(pub [u8; 32]);
19
20/// Parsed `#+KEYWORD:` directives (`#+TITLE`, `#+DATE`, `#+OPTIONS`, ...).
21#[derive(Debug, Clone, Default, Serialize, Deserialize)]
22pub struct Keywords {
23 pub entries: Vec<(String, String)>,
24}
25
26/// Parsed `:PROPERTIES:` drawer as an ordered key/value map.
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct Properties {
29 pub entries: Vec<(String, String)>,
30}
31
32/// A TODO keyword resolved against the configured keyword set.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct TodoKeyword {
35 pub name: String,
36 pub done: bool,
37}
38
39/// A problem found while parsing, carrying the 1-based source line it was found on.
40///
41/// Diagnostics are warnings, not errors: the parser's contract is that it always returns
42/// a document (spec §1 — out-of-scope constructs degrade, never crash). What a warning
43/// buys is that degrading stops being *silent*, which matters most exactly where the
44/// damage is largest — an unterminated `#+BEGIN_SRC` swallows the rest of the file.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct Diagnostic {
47 /// 1-based line number in the source file.
48 pub line: usize,
49 pub message: String,
50}
51
52/// One source file → one Document. This is the unit of parsing and caching (spec §2.3).
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Document {
55 pub source_path: Utf8PathBuf,
56 pub content_hash: ContentHash,
57 pub keywords: Keywords,
58 /// Pre-first-heading content plus child headings.
59 pub root: Section,
60 /// Non-fatal problems found while parsing this file.
61 #[serde(default)]
62 pub diagnostics: Vec<Diagnostic>,
63}
64
65/// A section = content directly under a heading (or the file preamble), followed by
66/// nested subsections. Recursive, mirroring org's headline hierarchy — so that
67/// "renaming a heading invalidates its subtree's link targets" is a local operation.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Section {
70 /// `None` for the file preamble.
71 pub heading: Option<Heading>,
72 /// Block-level content of THIS section.
73 pub content: Vec<Element>,
74 /// Nested sub-headings.
75 pub children: Vec<Section>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Heading {
80 pub level: u8,
81 pub todo: Option<TodoKeyword>,
82 /// `'A'..` from `[#A]`.
83 pub priority: Option<char>,
84 /// Inline objects — headings can contain markup/links.
85 pub title: Vec<Object>,
86 pub tags: Vec<String>,
87 pub properties: Properties,
88 /// `:ID:`.
89 pub id: Option<String>,
90 /// `:CUSTOM_ID:`.
91 pub custom_id: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub enum Element {
96 Paragraph(Vec<Object>),
97 List(List),
98 Table(Table),
99 SrcBlock {
100 lang: Option<String>,
101 params: BlockParams,
102 code: String,
103 },
104 ExampleBlock(String),
105 /// An image link carrying affiliated `#+CAPTION:`/`#+ATTR_HTML:` metadata, which
106 /// promotes it from an inline image to a block-level `<figure>`.
107 Figure {
108 link: Link,
109 caption: Vec<Object>,
110 /// Raw `#+ATTR_HTML:` attribute string, passed through to the `<img>` tag.
111 attrs: String,
112 },
113 QuoteBlock(Vec<Element>),
114 CenterBlock(Vec<Element>),
115 /// `#+BEGIN_<name>` for any other name — `note`, `warning`, `aside`. Its contents are
116 /// org, not literal text, and the name becomes a class so a stylesheet can reach it.
117 SpecialBlock {
118 name: String,
119 content: Vec<Element>,
120 },
121 /// `#+BEGIN_VERSE`: line breaks are significant.
122 VerseBlock(Vec<String>),
123 /// html passes through; others dropped at render (spec §1 OUT).
124 ExportBlock {
125 backend: String,
126 raw: String,
127 },
128 HorizontalRule,
129 FootnoteDefinition {
130 label: String,
131 content: Vec<Element>,
132 },
133 /// Stray `#+FOO:` kept as metadata.
134 Keyword {
135 key: String,
136 value: String,
137 },
138 /// Generic drawer; LOGBOOK special-cased (spec §8 Q7).
139 Drawer {
140 name: String,
141 content: Vec<Element>,
142 },
143 Comment(String),
144}
145
146/// `#+BEGIN_SRC` switches / header args. Parsed but mostly ignored in v1.
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct BlockParams {
149 pub raw: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct List {
154 pub kind: ListKind,
155 pub items: Vec<ListItem>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub enum ListKind {
160 Unordered,
161 Ordered,
162 Description,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct ListItem {
167 pub bullet: Bullet,
168 /// `[@4]` — an explicit number for this item, restarting the list's counting.
169 pub counter: Option<u32>,
170 pub checkbox: Option<Checkbox>,
171 /// Description-list term before `::`.
172 pub term: Option<Vec<Object>>,
173 /// Items hold block content (may nest lists).
174 pub content: Vec<Element>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub enum Bullet {
179 Dash,
180 Plus,
181 /// `1.` / `1)` — carries the ordinal.
182 Ordered(u32),
183}
184
185/// `[ ]` / `[X]` / `[-]`.
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187pub enum Checkbox {
188 Off,
189 On,
190 Trans,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct Table {
195 /// Rule rows preserved to locate the header band.
196 pub rows: Vec<TableRow>,
197 /// An affiliated `#+CAPTION:` directly above the table.
198 #[serde(default)]
199 pub caption: Vec<Object>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub enum TableRow {
204 Cells(Vec<Vec<Object>>),
205 Rule,
206}
207
208/// Inline objects (spec §2.3).
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub enum Object {
211 Text(String),
212 Bold(Vec<Object>),
213 Italic(Vec<Object>),
214 Underline(Vec<Object>),
215 StrikeThrough(Vec<Object>),
216 /// `=...=` : no nested markup (String, not Vec<Object>, by design).
217 Verbatim(String),
218 /// `~...~` : no nested markup.
219 Code(String),
220 Link(Link),
221 FootnoteRef {
222 label: String,
223 inline: Option<Vec<Object>>,
224 },
225 Timestamp(Timestamp),
226 LineBreak,
227 /// Resolved `\alpha`-style entity, only if enabled.
228 Entity(String),
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct Link {
233 /// Unresolved at parse time — resolution is a separate global stage (spec §2.3).
234 pub target: LinkTarget,
235 pub description: Option<Vec<Object>>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub enum LinkTarget {
240 /// `https:`, `mailto:`, ...
241 External(String),
242 File {
243 path: Utf8PathBuf,
244 search: Option<String>,
245 },
246 /// `[[id:...]]`.
247 Id(String),
248 /// `[[#...]]`.
249 CustomId(String),
250 /// `[[*Heading text]]`.
251 Heading(String),
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct Timestamp {
256 /// `<...>` vs `[...]`.
257 pub active: bool,
258 pub start: NaiveDateTime,
259 pub end: Option<NaiveDateTime>,
260 pub has_time: bool,
261}