sphinx_ultra/
document.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6// Custom serialization for PathBuf to handle cross-platform compatibility
7fn serialize_pathbuf<S>(path: &Path, serializer: S) -> Result<S::Ok, S::Error>
8where
9    S: Serializer,
10{
11    serializer.serialize_str(&path.to_string_lossy())
12}
13
14fn deserialize_pathbuf<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
15where
16    D: Deserializer<'de>,
17{
18    let s = String::deserialize(deserializer)?;
19    Ok(PathBuf::from(s))
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Document {
24    /// Source file path
25    #[serde(
26        serialize_with = "serialize_pathbuf",
27        deserialize_with = "deserialize_pathbuf"
28    )]
29    pub source_path: PathBuf,
30
31    /// Output file path
32    #[serde(
33        serialize_with = "serialize_pathbuf",
34        deserialize_with = "deserialize_pathbuf"
35    )]
36    pub output_path: PathBuf,
37
38    /// Document title
39    pub title: String,
40
41    /// Document content (parsed)
42    pub content: DocumentContent,
43
44    /// Document metadata
45    pub metadata: DocumentMetadata,
46
47    /// Rendered HTML content
48    pub html: String,
49
50    /// Source file modification time
51    pub source_mtime: DateTime<Utc>,
52
53    /// Build time
54    pub build_time: DateTime<Utc>,
55
56    /// Cross-references found in this document
57    pub cross_refs: Vec<CrossReference>,
58
59    /// Table of contents
60    pub toc: Vec<TocEntry>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum DocumentContent {
65    RestructuredText(RstContent),
66    Markdown(MarkdownContent),
67    PlainText(String),
68}
69
70impl std::fmt::Display for DocumentContent {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            DocumentContent::RestructuredText(rst) => write!(f, "{}", rst.raw),
74            DocumentContent::Markdown(md) => write!(f, "{}", md.raw),
75            DocumentContent::PlainText(text) => write!(f, "{}", text),
76        }
77    }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct RstContent {
82    /// Raw RST content
83    pub raw: String,
84
85    /// Parsed AST
86    pub ast: Vec<RstNode>,
87
88    /// Directives found in the document
89    pub directives: Vec<RstDirective>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct MarkdownContent {
94    /// Raw Markdown content
95    pub raw: String,
96
97    /// Parsed AST
98    pub ast: Vec<MarkdownNode>,
99
100    /// Front matter
101    pub front_matter: Option<serde_yaml::Value>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, Default)]
105pub struct DocumentMetadata {
106    /// Document author(s)
107    pub authors: Vec<String>,
108
109    /// Document creation date
110    pub created: Option<DateTime<Utc>>,
111
112    /// Document last modified date
113    pub modified: Option<DateTime<Utc>>,
114
115    /// Document tags
116    pub tags: Vec<String>,
117
118    /// Document category
119    pub category: Option<String>,
120
121    /// Custom metadata fields
122    pub custom: HashMap<String, serde_json::Value>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct CrossReference {
127    /// Reference type (doc, ref, func, class, etc.)
128    pub ref_type: String,
129
130    /// Reference target
131    pub target: String,
132
133    /// Reference text
134    pub text: Option<String>,
135
136    /// Line number where reference appears
137    pub line_number: usize,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct TocEntry {
142    /// Entry title
143    pub title: String,
144
145    /// Entry level (1-6)
146    pub level: usize,
147
148    /// Anchor ID
149    pub anchor: String,
150
151    /// Line number
152    pub line_number: usize,
153
154    /// Child entries
155    pub children: Vec<TocEntry>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub enum RstNode {
160    Title {
161        text: String,
162        level: usize,
163        line: usize,
164    },
165    Paragraph {
166        content: String,
167        line: usize,
168    },
169    CodeBlock {
170        language: Option<String>,
171        content: String,
172        line: usize,
173    },
174    List {
175        items: Vec<String>,
176        ordered: bool,
177        line: usize,
178    },
179    Table {
180        headers: Vec<String>,
181        rows: Vec<Vec<String>>,
182        line: usize,
183    },
184    Directive {
185        name: String,
186        args: Vec<String>,
187        options: HashMap<String, String>,
188        content: String,
189        line: usize,
190    },
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub enum MarkdownNode {
195    Heading {
196        text: String,
197        level: usize,
198        line: usize,
199    },
200    Paragraph {
201        content: String,
202        line: usize,
203    },
204    CodeBlock {
205        language: Option<String>,
206        content: String,
207        line: usize,
208    },
209    List {
210        items: Vec<String>,
211        ordered: bool,
212        line: usize,
213    },
214    Table {
215        headers: Vec<String>,
216        rows: Vec<Vec<String>>,
217        line: usize,
218    },
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct RstDirective {
223    /// Directive name (e.g., "code-block", "toctree", "autoclass")
224    pub name: String,
225
226    /// Directive arguments
227    pub args: Vec<String>,
228
229    /// Directive options
230    pub options: HashMap<String, String>,
231
232    /// Directive content
233    pub content: String,
234
235    /// Line number where directive starts
236    pub line: usize,
237}
238
239impl Document {
240    pub fn new(source_path: PathBuf, output_path: PathBuf) -> Self {
241        Self {
242            source_path,
243            output_path,
244            title: String::new(),
245            content: DocumentContent::PlainText(String::new()),
246            metadata: DocumentMetadata::default(),
247            html: String::new(),
248            source_mtime: Utc::now(),
249            build_time: Utc::now(),
250            cross_refs: Vec::new(),
251            toc: Vec::new(),
252        }
253    }
254
255    #[allow(dead_code)]
256    pub fn set_title(&mut self, title: String) {
257        self.title = title;
258    }
259
260    #[allow(dead_code)]
261    pub fn add_cross_ref(&mut self, cross_ref: CrossReference) {
262        self.cross_refs.push(cross_ref);
263    }
264
265    #[allow(dead_code)]
266    pub fn add_toc_entry(&mut self, entry: TocEntry) {
267        self.toc.push(entry);
268    }
269
270    #[allow(dead_code)]
271    pub fn set_html(&mut self, html: String) {
272        self.html = html;
273        self.build_time = Utc::now();
274    }
275}
276
277impl TocEntry {
278    pub fn new(title: String, level: usize, anchor: String, line_number: usize) -> Self {
279        Self {
280            title,
281            level,
282            anchor,
283            line_number,
284            children: Vec::new(),
285        }
286    }
287
288    #[allow(dead_code)]
289    pub fn add_child(&mut self, child: TocEntry) {
290        self.children.push(child);
291    }
292}