use crate::{Bindings, SubplotError, TemplateSpec}; use std::collections::HashMap; use std::fmt::Debug; use std::ops::Deref; use std::path::{Path, PathBuf}; use pandoc_ast::{Inline, Map, MetaValue, Pandoc}; use log::trace; /// Metadata of a document, as needed by Subplot. #[derive(Debug)] pub struct Metadata { title: String, date: Option, bindings_filenames: Vec, bindings: Bindings, impls: HashMap, bibliographies: Vec, /// Extra class names which should be considered 'correct' for this document classes: Vec, } #[derive(Debug)] pub struct DocumentImpl { spec: TemplateSpec, functions: Vec, } impl Metadata { /// Construct a Metadata from a Document, if possible. pub fn new

( basedir: P, doc: &Pandoc, template: Option<&str>, ) -> Result where P: AsRef + Debug, { let title = get_title(&doc.meta); let date = get_date(&doc.meta); let bindings_filenames = get_bindings_filenames(&doc.meta); let bibliographies = get_bibliographies(basedir.as_ref(), &doc.meta); let classes = get_classes(&doc.meta); trace!("Loaded basic metadata"); let mut impls = HashMap::new(); if let Some(raw_impls) = doc.meta.get("impls") { match raw_impls { MetaValue::MetaMap(raw_impls) => { for (impl_name, functions_filenames) in raw_impls.iter() { let template_spec = load_template_spec(impl_name)?; let filenames = pathbufs("", functions_filenames); let docimpl = DocumentImpl::new(template_spec, filenames); impls.insert(impl_name.to_string(), docimpl); } } _ => { trace!("Ignoring unknown raw implementation value"); } } } let template = template.or_else(|| impls.keys().next().map(String::as_str)); let mut bindings = Bindings::new(); get_bindings(&bindings_filenames, &mut bindings, template)?; trace!("Loaded all metadata successfully"); Ok(Metadata { title, date, bindings_filenames, bindings, impls, bibliographies, classes, }) } /// Return title of document. pub fn title(&self) -> &str { &self.title } /// Return date of document, if any. pub fn date(&self) -> Option<&str> { self.date.as_deref() } /// Return filename where bindings are specified. pub fn bindings_filenames(&self) -> Vec<&Path> { self.bindings_filenames.iter().map(|f| f.as_ref()).collect() } /// Return the document implementation (filenames, spec, etc) for the given template name pub fn document_impl(&self, template: &str) -> Option<&DocumentImpl> { self.impls.get(template) } /// Return the templates the document expects to implement pub fn templates(&self) -> impl Iterator { self.impls.keys().map(String::as_str) } /// Return the bindings. pub fn bindings(&self) -> &Bindings { &self.bindings } /// Return the bibliographies. pub fn bibliographies(&self) -> Vec<&Path> { self.bibliographies.iter().map(|x| x.as_path()).collect() } /// The classes which this document also claims are valid pub fn classes(&self) -> impl Iterator { self.classes.iter().map(Deref::deref) } } impl DocumentImpl { fn new(spec: TemplateSpec, functions: Vec) -> Self { Self { spec, functions } } pub fn functions_filenames(&self) -> impl Iterator { self.functions.iter().map(PathBuf::as_path) } pub fn spec(&self) -> &TemplateSpec { &self.spec } } type Mapp = Map; fn get_title(map: &Mapp) -> String { if let Some(s) = get_string(map, "title") { s } else { "".to_string() } } fn get_date(map: &Mapp) -> Option { get_string(map, "date") } fn get_bindings_filenames(map: &Mapp) -> Vec { get_paths("", map, "bindings") } fn load_template_spec(template: &str) -> Result { let mut spec_path = PathBuf::from(template); spec_path.push("template"); spec_path.push("template.yaml"); TemplateSpec::from_file(&spec_path) } fn get_paths

(basedir: P, map: &Mapp, field: &str) -> Vec where P: AsRef, { match map.get(field) { None => vec![], Some(v) => pathbufs(basedir, v), } } fn get_string(map: &Mapp, field: &str) -> Option { let v = match map.get(field) { None => return None, Some(s) => s, }; let v = match v { pandoc_ast::MetaValue::MetaString(s) => s.to_string(), pandoc_ast::MetaValue::MetaInlines(vec) => join(vec), _ => panic!("don't know how to handle: {:?}", v), }; Some(v) } fn get_bibliographies

(basedir: P, map: &Mapp) -> Vec where P: AsRef, { let v = match map.get("bibliography") { None => return vec![], Some(s) => s, }; pathbufs(basedir, v) } fn pathbufs

(basedir: P, v: &MetaValue) -> Vec where P: AsRef, { let mut bufs = vec![]; push_pathbufs(basedir, v, &mut bufs); bufs } fn get_classes(map: &Mapp) -> Vec { let mut ret = Vec::new(); if let Some(classes) = map.get("classes") { push_strings(classes, &mut ret); } ret } fn push_strings(v: &MetaValue, strings: &mut Vec) { match v { MetaValue::MetaString(s) => strings.push(s.to_string()), MetaValue::MetaInlines(vec) => strings.push(join(vec)), MetaValue::MetaList(values) => { for value in values { push_strings(value, strings); } } _ => panic!("don't know how to handle: {:?}", v), }; } fn push_pathbufs

(basedir: P, v: &MetaValue, bufs: &mut Vec) where P: AsRef, { match v { MetaValue::MetaString(s) => bufs.push(basedir.as_ref().join(Path::new(s))), MetaValue::MetaInlines(vec) => bufs.push(basedir.as_ref().join(Path::new(&join(vec)))), MetaValue::MetaList(values) => { for value in values { push_pathbufs(basedir.as_ref(), value, bufs); } } _ => panic!("don't know how to handle: {:?}", v), }; } fn join(vec: &[Inline]) -> String { let mut buf = String::new(); join_into_buffer(vec, &mut buf); buf } fn join_into_buffer(vec: &[Inline], buf: &mut String) { for item in vec { match item { pandoc_ast::Inline::Str(s) => buf.push_str(s), pandoc_ast::Inline::Code(_, s) => buf.push_str(s), pandoc_ast::Inline::Emph(v) => join_into_buffer(v, buf), pandoc_ast::Inline::Strong(v) => join_into_buffer(v, buf), pandoc_ast::Inline::Strikeout(v) => join_into_buffer(v, buf), pandoc_ast::Inline::Superscript(v) => join_into_buffer(v, buf), pandoc_ast::Inline::Subscript(v) => join_into_buffer(v, buf), pandoc_ast::Inline::SmallCaps(v) => join_into_buffer(v, buf), pandoc_ast::Inline::Space => buf.push(' '), pandoc_ast::Inline::SoftBreak => buf.push(' '), pandoc_ast::Inline::LineBreak => buf.push(' '), pandoc_ast::Inline::Quoted(qtype, v) => { let quote = match qtype { pandoc_ast::QuoteType::SingleQuote => '\'', pandoc_ast::QuoteType::DoubleQuote => '"', }; buf.push(quote); join_into_buffer(v, buf); buf.push(quote); } _ => panic!("unknown pandoc_ast::Inline component {:?}", item), } } } #[cfg(test)] mod test_join { use super::join; use pandoc_ast::{Inline, QuoteType}; #[test] fn join_all_kinds() { let v = vec![ Inline::Str("a".to_string()), Inline::Emph(vec![Inline::Str("b".to_string())]), Inline::Strong(vec![Inline::Str("c".to_string())]), Inline::Strikeout(vec![Inline::Str("d".to_string())]), Inline::Superscript(vec![Inline::Str("e".to_string())]), Inline::Subscript(vec![Inline::Str("f".to_string())]), Inline::SmallCaps(vec![Inline::Str("g".to_string())]), Inline::Space, Inline::SoftBreak, Inline::Quoted(QuoteType::SingleQuote, vec![Inline::Str("h".to_string())]), Inline::LineBreak, Inline::Quoted(QuoteType::DoubleQuote, vec![Inline::Str("i".to_string())]), ]; assert_eq!(join(&v), r#"abcdefg 'h' "i""#); } } fn get_bindings

( filenames: &[P], bindings: &mut Bindings, template: Option<&str>, ) -> Result<(), SubplotError> where P: AsRef + Debug, { for filename in filenames { bindings.add_from_file(filename, template)?; } Ok(()) }