use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct MetaData {
pub content: String,
pub required: Vec<String>,
pub type_mark: HashMap<String, &'static str>,
}
#[derive(Debug, Clone)]
pub enum Value {
String(String),
Number(f64),
Array(Vec<String>),
Bool(bool),
}
impl MetaData {
pub fn new(content: &str) -> Self {
Self {
content: content.to_string(),
required: vec![],
type_mark: Default::default(),
}
}
pub fn parse(&self) -> anyhow::Result<(HashMap<String, Value>, String)> {
let content = self.content.clone();
let mut markdown_text = String::new();
let mut result = HashMap::new();
let mut state = 0;
for line in content.lines() {
if line.trim() == "---" {
state += 1;
if state < 3 {
continue;
}
} else if state == 0 {
state = 2;
}
if state == 1 {
if line.trim().is_empty() {
continue;
}
let res = Self::parse_line(line);
if let Some((key, value)) = res {
if self.type_mark.contains_key(&key) {
let target_type = self.type_mark.get(&key).unwrap();
let value = match target_type.to_lowercase().as_str() {
"bool" => {
if &value.to_lowercase() == "true" {
Value::Bool(true)
} else if &value.to_lowercase() == "false" {
Value::Bool(false)
} else {
Value::String(value)
}
}
"number" => {
if let Ok(v) = value.parse::<f64>() {
Value::Number(v)
} else {
Value::String(value)
}
}
"array" => {
if &value[0..1] == "[" && &value[value.len() - 1..] == "]" {
let mut array = vec![];
let temp = value[1..value.len() - 1].to_string();
let v = temp.split(',').collect::<Vec<&str>>();
for i in v {
let val = i.trim().to_string();
if !val.is_empty() {
array.push(val);
}
}
Value::Array(array)
} else {
Value::String(value)
}
}
_ => Value::String(value),
};
result.insert(key, value);
} else {
result.insert(key, Value::String(value));
}
}
} else {
markdown_text += &format!("{line}\n");
}
}
for req in &self.required {
if !result.contains_key(req) {
return Err(anyhow::anyhow!("Required key not found: {req}"));
}
}
Ok((result, markdown_text))
}
pub fn parse_line(text: &str) -> Option<(String, String)> {
let mut v = text.split(':').collect::<Vec<&str>>();
let mut result = (String::new(), String::new());
if v.len() < 2 {
return None;
}
result.0 = v.get(0).unwrap().trim().into();
v.remove(0);
result.1 = v.join(":").trim().into();
Some(result)
}
}
impl Value {
pub fn as_string(self) -> Option<String> {
if let Value::String(s) = self {
return Some(s);
}
None
}
pub fn as_bool(self) -> Option<bool> {
if let Value::Bool(b) = self {
return Some(b);
}
None
}
pub fn as_number(self) -> Option<f64> {
if let Value::Number(n) = self {
return Some(n);
}
None
}
pub fn as_array(self) -> Option<Vec<String>> {
if let Value::Array(a) = self {
return Some(a);
}
None
}
}
#[cfg(test)]
mod test {
use crate::MetaData;
#[test]
fn one_line() {
let r = MetaData::parse_line("title: Hello World");
println!("{:?}", r);
}
}