krz/orgo

Lightning fast org-mode static site generator.

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

v0.3.0: 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/// 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}