use crate::{Bindings, SubplotError, TemplateSpec, YamlMetadata};
use std::collections::HashMap;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use pandoc_ast::{Inline, Map, MetaValue};
use log::trace;
#[derive(Debug)]
pub struct Metadata {
basedir: PathBuf,
title: String,
date: Option<String>,
markdown_filename: PathBuf,
bindings_filenames: Vec<PathBuf>,
bindings: Bindings,
impls: HashMap<String, DocumentImpl>,
bibliographies: Vec<PathBuf>,
classes: Vec<String>,
}
#[derive(Debug)]
pub struct DocumentImpl {
spec: TemplateSpec,
functions: Vec<PathBuf>,
}
impl Metadata {
pub fn new<P>(
basedir: P,
meta: &YamlMetadata,
template: Option<&str>,
) -> Result<Metadata, SubplotError>
where
P: AsRef<Path> + Debug,
{
let map = meta.to_map();
let title = get_title(&map);
let date = get_date(&map);
let bindings_filenames = get_bindings_filenames(&map);
let bibliographies = get_bibliographies(basedir.as_ref(), &map);
let classes = get_classes(&map);
trace!("Loaded basic metadata");
let mut impls = HashMap::new();
if let Some(raw_impls) = map.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 {
basedir: basedir.as_ref().to_path_buf(),
title,
date,
markdown_filename: meta.markdown().into(),
bindings_filenames,
bindings,
impls,
bibliographies,
classes,
})
}
pub fn title(&self) -> &str {
&self.title
}
pub fn date(&self) -> Option<&str> {
self.date.as_deref()
}
pub fn basedir(&self) -> &Path {
&self.basedir
}
pub fn markdown_filename(&self) -> &Path {
&self.markdown_filename
}
pub fn bindings_filenames(&self) -> Vec<&Path> {
self.bindings_filenames.iter().map(|f| f.as_ref()).collect()
}
pub fn document_impl(&self, template: &str) -> Option<&DocumentImpl> {
self.impls.get(template)
}
pub fn templates(&self) -> impl Iterator<Item = &str> {
self.impls.keys().map(String::as_str)
}
pub fn bindings(&self) -> &Bindings {
&self.bindings
}
pub fn bibliographies(&self) -> Vec<&Path> {
self.bibliographies.iter().map(|x| x.as_path()).collect()
}
pub fn classes(&self) -> impl Iterator<Item = &str> {
self.classes.iter().map(Deref::deref)
}
}
impl DocumentImpl {
fn new(spec: TemplateSpec, functions: Vec<PathBuf>) -> Self {
Self { spec, functions }
}
pub fn functions_filenames(&self) -> impl Iterator<Item = &Path> {
self.functions.iter().map(PathBuf::as_path)
}
pub fn spec(&self) -> &TemplateSpec {
&self.spec
}
}
type Mapp = Map<String, MetaValue>;
fn get_title(map: &Mapp) -> String {
if let Some(s) = get_string(map, "title") {
s
} else {
"".to_string()
}
}
fn get_date(map: &Mapp) -> Option<String> {
get_string(map, "date")
}
fn get_bindings_filenames(map: &Mapp) -> Vec<PathBuf> {
get_paths("", map, "bindings")
}
fn load_template_spec(template: &str) -> Result<TemplateSpec, SubplotError> {
let mut spec_path = PathBuf::from(template);
spec_path.push("template");
spec_path.push("template.yaml");
TemplateSpec::from_file(&spec_path)
}
fn get_paths<P>(basedir: P, map: &Mapp, field: &str) -> Vec<PathBuf>
where
P: AsRef<Path>,
{
match map.get(field) {
None => vec![],
Some(v) => pathbufs(basedir, v),
}
}
fn get_string(map: &Mapp, field: &str) -> Option<String> {
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<P>(basedir: P, map: &Mapp) -> Vec<PathBuf>
where
P: AsRef<Path>,
{
let v = match map.get("bibliography") {
None => return vec![],
Some(s) => s,
};
pathbufs(basedir, v)
}
fn pathbufs<P>(basedir: P, v: &MetaValue) -> Vec<PathBuf>
where
P: AsRef<Path>,
{
let mut bufs = vec![];
push_pathbufs(basedir, v, &mut bufs);
bufs
}
fn get_classes(map: &Mapp) -> Vec<String> {
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<String>) {
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<P>(basedir: P, v: &MetaValue, bufs: &mut Vec<PathBuf>)
where
P: AsRef<Path>,
{
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<P>(
filenames: &[P],
bindings: &mut Bindings,
template: Option<&str>,
) -> Result<(), SubplotError>
where
P: AsRef<Path> + Debug,
{
for filename in filenames {
bindings.add_from_file(filename, template)?;
}
Ok(())
}