use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FmValue {
Str(String),
Seq(Vec<FmValue>),
Map(Vec<(String, FmValue)>),
}
impl FmValue {
pub fn as_str(&self) -> Option<&str> {
match self {
FmValue::Str(s) => Some(s),
_ => None,
}
}
pub fn to_flat_string(&self) -> String {
match self {
FmValue::Str(s) => s.clone(),
FmValue::Seq(items) => {
let inner: Vec<String> = items
.iter()
.map(|i| format!("'{}'", i.to_flat_string()))
.collect();
format!("[{}]", inner.join(", "))
}
FmValue::Map(entries) => {
let inner: Vec<String> = entries
.iter()
.map(|(k, v)| format!("'{}': '{}'", k, v.to_flat_string()))
.collect();
format!("{{{}}}", inner.join(", "))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Frontmatter {
entries: Vec<(String, FmValue)>,
}
impl Frontmatter {
pub fn get(&self, key: &str) -> Option<&FmValue> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn contains_key(&self, key: &str) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn keys(&self) -> BTreeSet<String> {
self.entries.iter().map(|(k, _)| k.clone()).collect()
}
}
fn scalar_to_string(value: &serde_yaml::Value) -> Option<String> {
use serde_yaml::Value;
match value {
Value::Null => Some("None".to_string()),
Value::Bool(b) => Some(if *b {
"True".to_string()
} else {
"False".to_string()
}),
Value::String(s) => Some(s.clone()),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Some(i.to_string())
} else if let Some(u) = n.as_u64() {
Some(u.to_string())
} else {
n.as_f64().map(|f| format!("{f:?}"))
}
}
_ => None,
}
}
fn convert(value: &serde_yaml::Value) -> FmValue {
use serde_yaml::Value;
match value {
Value::Sequence(items) => FmValue::Seq(items.iter().map(convert).collect()),
Value::Mapping(map) => {
let mut entries = Vec::with_capacity(map.len());
for (k, v) in map {
let key = scalar_to_string(k).unwrap_or_else(|| convert(k).to_flat_string());
entries.push((key, convert(v)));
}
FmValue::Map(entries)
}
scalar => FmValue::Str(scalar_to_string(scalar).unwrap_or_default()),
}
}
pub fn parse_mapping(frontmatter: &str) -> std::result::Result<Option<Frontmatter>, String> {
let value: serde_yaml::Value = serde_yaml::from_str(frontmatter).map_err(|e| e.to_string())?;
match convert(&value) {
FmValue::Map(entries) => Ok(Some(Frontmatter { entries })),
_ => Ok(None),
}
}