use decodal::{Data, Diagnostic, DiagnosticKind, Engine, ImportLoader, LoadedImport, Span, Value}; const POST: &str = r#"--- title: Hello draft: false --- # Hello This body stays as Markdown. "#; struct ContentLoader; impl ImportLoader for ContentLoader { fn load( &mut self, _current_key: Option<&str>, specifier: &str, ) -> decodal::Result { if specifier != "./post.md" { return Err(Diagnostic::new( DiagnosticKind::Import, Span::default(), format!("unknown content import `{specifier}`"), )); } Ok(LoadedImport::value( "content/post.md", parse_markdown(POST)?, )) } } fn parse_markdown(source: &str) -> decodal::Result { let source = source.strip_prefix("---\n").ok_or_else(frontmatter_error)?; let (frontmatter, body) = source.split_once("\n---\n").ok_or_else(frontmatter_error)?; let mut fields = Vec::new(); for line in frontmatter.lines() { let (name, value) = line.split_once(':').ok_or_else(frontmatter_error)?; let value = match value.trim() { "true" => Value::bool(true), "false" => Value::bool(false), value => Value::string(value), }; fields.push((name.trim(), value)); } Ok(Value::object([ ("frontmatter", Value::object(fields)), ("body", Value::string(body)), ])) } fn frontmatter_error() -> Diagnostic { Diagnostic::new( DiagnosticKind::Import, Span::default(), "invalid Markdown frontmatter", ) } fn main() -> decodal::Result<()> { let mut engine = Engine::new(ContentLoader); let module = engine.add_root_source( "main.dcdl", "main.dcdl", r#" let post = import "./post.md"; in { title = post.frontmatter.title; draft = post.frontmatter.draft; body = post.body; } "#, )?; let value = engine.eval_module(module)?; let data = engine.materialize(&value)?; let Data::Object(fields) = data else { panic!("expected imported Markdown to produce an object") }; assert_eq!(fields[0].value, Data::String(String::from("Hello"))); assert_eq!(fields[1].value, Data::Bool(false)); assert!(matches!(fields[2].value, Data::String(_))); Ok(()) }