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