krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

v0.20.1: src/model.rs · raw

  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}