weaver_lib/
document.rs

1use gray_matter::{Matter, engine::YAML};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, path::PathBuf};
4use toml::Value;
5
6use crate::{document_toc::toc_from_document, normalize_line_endings};
7
8#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
9pub struct Heading {
10    pub depth: u8,
11    pub text: String,
12    pub slug: String,
13}
14
15#[derive(Debug, Serialize, Deserialize, Default, Clone)]
16pub struct Document {
17    pub at_path: String,
18    pub metadata: BaseMetaData,
19    pub markdown: String,
20    pub excerpt: Option<String>,
21    pub html: Option<String>,
22    pub toc: Vec<Heading>,
23    pub emit: bool,
24}
25
26#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
27#[serde(default)]
28pub struct BaseMetaData {
29    pub title: String,
30    pub description: String,
31    pub tags: Vec<String>,
32    pub keywords: Vec<String>,
33    pub template: String,
34    pub emit: bool,
35
36    #[serde(flatten)]
37    pub user: HashMap<String, Value>,
38}
39
40impl Default for BaseMetaData {
41    fn default() -> Self {
42        Self {
43            title: Default::default(),
44            tags: Default::default(),
45            description: Default::default(),
46            keywords: Default::default(),
47            template: "default".into(),
48            emit: true,
49            user: Default::default(),
50        }
51    }
52}
53
54impl Document {
55    pub fn new_from_path(path: PathBuf) -> Self {
56        let contents_result = std::fs::read_to_string(&path);
57
58        if contents_result.is_err() {
59            dbg!("error reading file: {}", contents_result.err());
60            panic!("failed to read '{}'", path.display());
61        }
62
63        let matter = Matter::<YAML>::new();
64        let parseable = normalize_line_endings(contents_result.as_ref().unwrap().as_bytes());
65        let parse_result = matter.parse(&parseable);
66        let base_metadata_opt = match parse_result.data {
67            Some(data) => data.deserialize::<BaseMetaData>(),
68            None => Ok(BaseMetaData::default()),
69        };
70
71        if base_metadata_opt.is_err() {
72            eprintln!(
73                "error parsing '{}': {:?}",
74                &path.display(),
75                base_metadata_opt.err()
76            );
77            return Self::default();
78        }
79
80        let base_metadata = base_metadata_opt.unwrap();
81        let should_emit = base_metadata.clone().emit;
82
83        Self {
84            at_path: path.display().to_string(),
85            metadata: base_metadata,
86            markdown: parse_result.content.clone(),
87            excerpt: parse_result.excerpt,
88            emit: should_emit,
89            toc: toc_from_document(parse_result.content.as_str()),
90            ..Default::default()
91        }
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98    use pretty_assertions::assert_eq;
99
100    #[test]
101    fn test_document_loading() {
102        let base_path_wd = std::env::current_dir()
103            .unwrap()
104            .as_os_str()
105            .to_os_string()
106            .to_str()
107            .unwrap()
108            .to_string();
109        let base_path = format!("{}/test_fixtures/markdown", base_path_wd);
110        let document = Document::new_from_path(format!("{}/full_frontmatter.md", base_path).into());
111
112        assert_eq!(
113            BaseMetaData {
114                tags: vec!["1".into()],
115                keywords: vec!["2".into()],
116                title: "test".into(),
117                description: "test".into(),
118                user: HashMap::new(),
119                emit: true,
120                template: "default".into(),
121            },
122            document.metadata
123        )
124    }
125}