1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6fn 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 #[serde(
26 serialize_with = "serialize_pathbuf",
27 deserialize_with = "deserialize_pathbuf"
28 )]
29 pub source_path: PathBuf,
30
31 #[serde(
33 serialize_with = "serialize_pathbuf",
34 deserialize_with = "deserialize_pathbuf"
35 )]
36 pub output_path: PathBuf,
37
38 pub title: String,
40
41 pub content: DocumentContent,
43
44 pub metadata: DocumentMetadata,
46
47 pub html: String,
49
50 pub source_mtime: DateTime<Utc>,
52
53 pub build_time: DateTime<Utc>,
55
56 pub cross_refs: Vec<CrossReference>,
58
59 pub toc: Vec<TocEntry>,
61
62 pub toctrees: Vec<crate::rst::ToctreeRecord>,
65
66 pub directive_records: Vec<crate::rst::DirectiveRecord>,
68
69 pub role_records: Vec<crate::rst::RoleRecord>,
71
72 pub labels: Vec<LabelRecord>,
74
75 pub registry: crate::rst::RegistryExport,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct LabelRecord {
87 pub name: String,
88 pub line: usize,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub enum DocumentContent {
94 RestructuredText(RstContent),
95 Markdown(MarkdownContent),
96 PlainText(String),
97}
98
99impl std::fmt::Display for DocumentContent {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 match self {
102 DocumentContent::RestructuredText(rst) => write!(f, "{}", rst.raw),
103 DocumentContent::Markdown(md) => write!(f, "{}", md.raw),
104 DocumentContent::PlainText(text) => write!(f, "{}", text),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct RstContent {
111 pub raw: String,
113
114 pub ast: Vec<RstNode>,
116
117 pub directives: Vec<RstDirective>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct MarkdownContent {
123 pub raw: String,
125
126 pub ast: Vec<MarkdownNode>,
128
129 pub front_matter: Option<serde_yaml::Value>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, Default)]
134pub struct DocumentMetadata {
135 pub authors: Vec<String>,
137
138 pub created: Option<DateTime<Utc>>,
140
141 pub modified: Option<DateTime<Utc>>,
143
144 pub tags: Vec<String>,
146
147 pub category: Option<String>,
149
150 pub custom: HashMap<String, serde_json::Value>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CrossReference {
156 pub ref_type: String,
158
159 pub target: String,
161
162 pub text: Option<String>,
164
165 pub line_number: usize,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct TocEntry {
171 pub title: String,
173
174 pub level: usize,
176
177 pub anchor: String,
179
180 pub line_number: usize,
182
183 pub children: Vec<TocEntry>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub enum RstNode {
189 Title {
190 text: String,
191 level: usize,
192 line: usize,
193 },
194 Paragraph {
195 content: String,
196 line: usize,
197 },
198 CodeBlock {
199 language: Option<String>,
200 content: String,
201 line: usize,
202 },
203 List {
204 items: Vec<String>,
205 ordered: bool,
206 line: usize,
207 },
208 Table {
209 headers: Vec<String>,
210 rows: Vec<Vec<String>>,
211 line: usize,
212 },
213 Directive {
214 name: String,
215 args: Vec<String>,
216 options: HashMap<String, String>,
217 content: String,
218 line: usize,
219 },
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub enum MarkdownNode {
224 Heading {
225 text: String,
226 level: usize,
227 line: usize,
228 },
229 Paragraph {
230 content: String,
231 line: usize,
232 },
233 CodeBlock {
234 language: Option<String>,
235 content: String,
236 line: usize,
237 },
238 List {
239 items: Vec<String>,
240 ordered: bool,
241 line: usize,
242 },
243 Table {
244 headers: Vec<String>,
245 rows: Vec<Vec<String>>,
246 line: usize,
247 },
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct RstDirective {
252 pub name: String,
254
255 pub args: Vec<String>,
257
258 pub options: HashMap<String, String>,
260
261 pub content: String,
263
264 pub line: usize,
266}
267
268impl Document {
269 pub fn new(source_path: PathBuf, output_path: PathBuf) -> Self {
270 Self {
271 source_path,
272 output_path,
273 title: String::new(),
274 content: DocumentContent::PlainText(String::new()),
275 metadata: DocumentMetadata::default(),
276 html: String::new(),
277 source_mtime: Utc::now(),
278 build_time: Utc::now(),
279 cross_refs: Vec::new(),
280 toc: Vec::new(),
281 toctrees: Vec::new(),
282 directive_records: Vec::new(),
283 role_records: Vec::new(),
284 labels: Vec::new(),
285 registry: crate::rst::RegistryExport::default(),
286 }
287 }
288
289 #[allow(dead_code)]
290 pub fn set_title(&mut self, title: String) {
291 self.title = title;
292 }
293
294 #[allow(dead_code)]
295 pub fn add_cross_ref(&mut self, cross_ref: CrossReference) {
296 self.cross_refs.push(cross_ref);
297 }
298
299 #[allow(dead_code)]
300 pub fn add_toc_entry(&mut self, entry: TocEntry) {
301 self.toc.push(entry);
302 }
303
304 #[allow(dead_code)]
305 pub fn set_html(&mut self, html: String) {
306 self.html = html;
307 self.build_time = Utc::now();
308 }
309}
310
311impl TocEntry {
312 pub fn new(title: String, level: usize, anchor: String, line_number: usize) -> Self {
313 Self {
314 title,
315 level,
316 anchor,
317 line_number,
318 children: Vec::new(),
319 }
320 }
321
322 #[allow(dead_code)]
323 pub fn add_child(&mut self, child: TocEntry) {
324 self.children.push(child);
325 }
326}