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/// One source file → one Document. This is the unit of parsing and caching (spec §2.3).
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Document {
42 pub source_path: Utf8PathBuf,
43 pub content_hash: ContentHash,
44 pub keywords: Keywords,
45 /// Pre-first-heading content plus child headings.
46 pub root: Section,
47}
48
49/// A section = content directly under a heading (or the file preamble), followed by
50/// nested subsections. Recursive, mirroring org's headline hierarchy — so that
51/// "renaming a heading invalidates its subtree's link targets" is a local operation.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Section {
54 /// `None` for the file preamble.
55 pub heading: Option<Heading>,
56 /// Block-level content of THIS section.
57 pub content: Vec<Element>,
58 /// Nested sub-headings.
59 pub children: Vec<Section>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Heading {
64 pub level: u8,
65 pub todo: Option<TodoKeyword>,
66 /// `'A'..` from `[#A]`.
67 pub priority: Option<char>,
68 /// Inline objects — headings can contain markup/links.
69 pub title: Vec<Object>,
70 pub tags: Vec<String>,
71 pub properties: Properties,
72 /// `:ID:`.
73 pub id: Option<String>,
74 /// `:CUSTOM_ID:`.
75 pub custom_id: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum Element {
80 Paragraph(Vec<Object>),
81 List(List),
82 Table(Table),
83 SrcBlock {
84 lang: Option<String>,
85 params: BlockParams,
86 code: String,
87 },
88 ExampleBlock(String),
89 QuoteBlock(Vec<Element>),
90 CenterBlock(Vec<Element>),
91 /// html passes through; others dropped at render (spec §1 OUT).
92 ExportBlock {
93 backend: String,
94 raw: String,
95 },
96 HorizontalRule,
97 FootnoteDefinition {
98 label: String,
99 content: Vec<Element>,
100 },
101 /// Stray `#+FOO:` kept as metadata.
102 Keyword {
103 key: String,
104 value: String,
105 },
106 /// Generic drawer; LOGBOOK special-cased (spec §8 Q7).
107 Drawer {
108 name: String,
109 content: Vec<Element>,
110 },
111 Comment(String),
112}
113
114/// `#+BEGIN_SRC` switches / header args. Parsed but mostly ignored in v1.
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
116pub struct BlockParams {
117 pub raw: String,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct List {
122 pub kind: ListKind,
123 pub items: Vec<ListItem>,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub enum ListKind {
128 Unordered,
129 Ordered,
130 Description,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ListItem {
135 pub bullet: Bullet,
136 pub checkbox: Option<Checkbox>,
137 /// Description-list term before `::`.
138 pub term: Option<Vec<Object>>,
139 /// Items hold block content (may nest lists).
140 pub content: Vec<Element>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub enum Bullet {
145 Dash,
146 Plus,
147 /// `1.` / `1)` — carries the ordinal.
148 Ordered(u32),
149}
150
151/// `[ ]` / `[X]` / `[-]`.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub enum Checkbox {
154 Off,
155 On,
156 Trans,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Table {
161 /// Rule rows preserved to locate the header band.
162 pub rows: Vec<TableRow>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub enum TableRow {
167 Cells(Vec<Vec<Object>>),
168 Rule,
169}
170
171/// Inline objects (spec §2.3).
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub enum Object {
174 Text(String),
175 Bold(Vec<Object>),
176 Italic(Vec<Object>),
177 Underline(Vec<Object>),
178 StrikeThrough(Vec<Object>),
179 /// `=...=` : no nested markup (String, not Vec<Object>, by design).
180 Verbatim(String),
181 /// `~...~` : no nested markup.
182 Code(String),
183 Link(Link),
184 FootnoteRef {
185 label: String,
186 inline: Option<Vec<Object>>,
187 },
188 Timestamp(Timestamp),
189 LineBreak,
190 /// Resolved `\alpha`-style entity, only if enabled.
191 Entity(String),
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct Link {
196 /// Unresolved at parse time — resolution is a separate global stage (spec §2.3).
197 pub target: LinkTarget,
198 pub description: Option<Vec<Object>>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub enum LinkTarget {
203 /// `https:`, `mailto:`, ...
204 External(String),
205 File {
206 path: Utf8PathBuf,
207 search: Option<String>,
208 },
209 /// `[[id:...]]`.
210 Id(String),
211 /// `[[#...]]`.
212 CustomId(String),
213 /// `[[*Heading text]]`.
214 Heading(String),
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct Timestamp {
219 /// `<...>` vs `[...]`.
220 pub active: bool,
221 pub start: NaiveDateTime,
222 pub end: Option<NaiveDateTime>,
223 pub has_time: bool,
224}