markdown_meta_parser/
lib.rs1use 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}