use crate::data::dataframe::DataFrame;
use crate::data::doc::{Doc, Format, SaveOpts};
use color_eyre::{eyre::eyre, Result};
use std::path::Path;
fn split(text: &str) -> (Option<(Format, &str)>, &str) {
for (fence, format) in [("---", Format::Yaml), ("+++", Format::Toml)] {
let Some(rest) = text.strip_prefix(fence) else {
continue;
};
let Some(rest) = rest.strip_prefix('\n') else {
continue;
};
let closing = format!("\n{}", fence);
if let Some(end) = rest.find(&closing) {
let after = &rest[end + closing.len()..];
let body = after.strip_prefix('\n').unwrap_or(after);
return (Some((format, &rest[..end])), body);
}
}
(None, text)
}
pub(super) fn load_markdown(path: &Path) -> Result<DataFrame> {
let text = std::fs::read_to_string(path)?;
let (front, body) = split(&text);
let mut row = serde_json::Map::new();
row.insert(
"file".to_string(),
serde_json::Value::String(path.display().to_string()),
);
if let Some((format, source)) = front {
let parsed = Doc::from_str(source, format)
.and_then(|doc| doc.to_string_as(Format::Json, &SaveOpts::default()))
.map_err(|e| eyre!("frontmatter: {}", e))?;
if let Ok(serde_json::Value::Object(fields)) = serde_json::from_str(&parsed) {
for (k, v) in fields {
row.insert(k, v);
}
}
}
row.insert(
"body".to_string(),
serde_json::Value::String(body.trim().to_string()),
);
let as_records = serde_json::Value::Array(vec![serde_json::Value::Object(row)]);
let doc = Doc::from_str(&as_records.to_string(), Format::Json)?;
let (df, _) = super::doc_io::DocState::from_doc(doc)?;
Ok(df)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fence_is_the_first_line_and_its_repeat() {
let (front, body) = split("---\ntitle: Tea\n---\nBody here\n");
assert!(matches!(front, Some((Format::Yaml, "title: Tea"))));
assert_eq!(body, "Body here\n");
let (front, body) = split("+++\ntitle = \"Tea\"\n+++\nBody\n");
assert!(matches!(front, Some((Format::Toml, "title = \"Tea\""))));
assert_eq!(body, "Body\n");
assert!(split("# Just a heading\n").0.is_none());
assert!(split("---\ntitle: Tea\nnever closed\n").0.is_none());
let (front, body) = split("Text\n\n---\n\nMore\n");
assert!(front.is_none());
assert_eq!(body, "Text\n\n---\n\nMore\n");
}
}