weaver_lib/
document.rs

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