markdown_meta_parser/
lib.rs

1use std::collections::HashMap;
2
3#[derive(Debug, Clone)]
4pub struct MetaData {
5    pub content: String,
6    pub required: Vec<String>,
7    pub type_mark: HashMap<String, &'static str>,
8}
9
10#[derive(Debug, Clone)]
11pub enum Value {
12    String(String),
13    Number(f64),
14    Array(Vec<String>),
15    Bool(bool),
16}
17
18impl MetaData {
19    pub fn new(content: &str) -> Self {
20        Self {
21            content: content.to_string(),
22            required: vec![],
23            type_mark: Default::default(),
24        }
25    }
26
27    pub fn parse(&self) -> anyhow::Result<(HashMap<String, Value>, String)> {
28        let content = self.content.clone();
29        let mut markdown_text = String::new();
30        let mut result = HashMap::new();
31
32        let mut state = 0;
33
34        for line in content.lines() {
35            if line.trim() == "---" {
36                state += 1;
37                if state < 3 {
38                    continue;
39                }
40            } else if state == 0 {
41                state = 2;
42            }
43
44            if state == 1 {
45                if line.trim().is_empty() {
46                    continue;
47                }
48
49                let res = Self::parse_line(line);
50                if let Some((key, value)) = res {
51                    if self.type_mark.contains_key(&key) {
52                        let target_type = self.type_mark.get(&key).unwrap();
53                        let value = match target_type.to_lowercase().as_str() {
54                            "bool" => {
55                                if &value.to_lowercase() == "true" {
56                                    Value::Bool(true)
57                                } else if &value.to_lowercase() == "false" {
58                                    Value::Bool(false)
59                                } else {
60                                    Value::String(value)
61                                }
62                            }
63                            "number" => {
64                                if let Ok(v) = value.parse::<f64>() {
65                                    Value::Number(v)
66                                } else {
67                                    Value::String(value)
68                                }
69                            }
70                            "array" => {
71                                if &value[0..1] == "[" && &value[value.len() - 1..] == "]" {
72                                    let mut array = vec![];
73                                    let temp = value[1..value.len() - 1].to_string();
74                                    let v = temp.split(',').collect::<Vec<&str>>();
75                                    for i in v {
76                                        let val = i.trim().to_string();
77                                        if !val.is_empty() {
78                                            array.push(val);
79                                        }
80                                    }
81                                    Value::Array(array)
82                                } else {
83                                    Value::String(value)
84                                }
85                            }
86                            _ => Value::String(value),
87                        };
88                        result.insert(key, value);
89                    } else {
90                        result.insert(key, Value::String(value));
91                    }
92                }
93            } else {
94                markdown_text += &format!("{line}\n");
95            }
96        }
97
98        for req in &self.required {
99            if !result.contains_key(req) {
100                return Err(anyhow::anyhow!("Required key not found: {req}"));
101            }
102        }
103
104        Ok((result, markdown_text))
105    }
106
107    pub fn parse_line(text: &str) -> Option<(String, String)> {
108        let mut v = text.split(':').collect::<Vec<&str>>();
109        let mut result = (String::new(), String::new());
110        if v.len() < 2 {
111            return None;
112        }
113
114        result.0 = v.get(0).unwrap().trim().into();
115        v.remove(0);
116        result.1 = v.join(":").trim().into();
117
118        Some(result)
119    }
120}
121
122impl Value {
123    pub fn as_string(self) -> Option<String> {
124        if let Value::String(s) = self {
125            return Some(s);
126        }
127        None
128    }
129    
130    pub fn as_bool(self) -> Option<bool> {
131        if let Value::Bool(b) = self {
132            return Some(b);
133        }
134        None
135    }
136
137    pub fn as_number(self) -> Option<f64> {
138        if let Value::Number(n) = self {
139            return Some(n);
140        }
141        None
142    }
143
144    pub fn as_array(self) -> Option<Vec<String>> {
145        if let Value::Array(a) = self {
146            return Some(a);
147        }
148        None
149    }
150
151}
152
153#[cfg(test)]
154mod test {
155    use crate::MetaData;
156    #[test]
157    fn one_line() {
158        let r = MetaData::parse_line("title: Hello World");
159        println!("{:?}", r);
160    }
161}