diff options
Diffstat (limited to 'src/metadata.rs')
-rw-r--r-- | src/metadata.rs | 174 |
1 files changed, 172 insertions, 2 deletions
diff --git a/src/metadata.rs b/src/metadata.rs index e88c732..b840633 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,11 +1,181 @@ -use crate::{Bindings, SubplotError, TemplateSpec, YamlMetadata}; +use crate::{Bindings, SubplotError, TemplateSpec}; +use lazy_static::lazy_static; use log::trace; -use std::collections::HashMap; +use regex::Regex; +use serde::Deserialize; +use serde_yaml::Value; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::ops::Deref; use std::path::{Path, PathBuf}; +lazy_static! { + // Pattern that recognises a YAML block at the beginning of a file. + static ref LEADING_YAML_PATTERN: Regex = Regex::new(r"^(?:\S*\n)*(?P<yaml>-{3,}\n([^.].*\n)*\.{3,}\n)(?P<text>(.*\n)*)$").unwrap(); + + + // Pattern that recognises a YAML block at the end of a file. + static ref TRAILING_YAML_PATTERN: Regex = Regex::new(r"(?P<text>(.*\n)*)\n*(?P<yaml>-{3,}\n([^.].*\n)*\.{3,}\n)(?:\S*\n)*$").unwrap(); +} + +/// Errors from Markdown parsing. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Regex(#[from] regex::Error), + + #[error("Markdown doesn't contain a YAML block for document metadata")] + NoMetadata, + + #[error(transparent)] + Yaml(#[from] serde_yaml::Error), +} + +/// Document metadata. +/// +/// This is expressed in the Markdown input file as an embedded YAML +/// block. +/// +/// Note that this structure needs to be able to capture any metadata +/// block we can work with, in any input file. By being strict here we +/// make it easier to tell the user when a metadata block has, say, a +/// misspelled field. +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct YamlMetadata { + title: String, + subtitle: Option<String>, + authors: Option<Vec<String>>, + date: Option<String>, + classes: Option<Vec<String>>, + bibliography: Option<Vec<PathBuf>>, + markdowns: Vec<PathBuf>, + bindings: Option<Vec<PathBuf>>, + documentclass: Option<String>, + #[serde(default)] + impls: BTreeMap<String, Vec<PathBuf>>, + pandoc: Option<HashMap<String, Value>>, +} + +impl YamlMetadata { + #[cfg(test)] + fn new(yaml_text: &str) -> Result<Self, Error> { + let meta: Self = serde_yaml::from_str(yaml_text)?; + Ok(meta) + } + + /// Name of file with the Markdown for the subplot document. + pub fn markdown(&self) -> &Path { + &self.markdowns[0] + } + + /// Title. + pub fn title(&self) -> &str { + &self.title + } + + /// Subtitle. + pub fn subtitle(&self) -> Option<&str> { + self.subtitle.as_deref() + } + + /// Date. + pub fn date(&self) -> Option<&str> { + self.date.as_deref() + } + + /// Authors. + pub fn authors(&self) -> Option<&[String]> { + self.authors.as_deref() + } + + /// Names of bindings files. + pub fn bindings_filenames(&self) -> Option<&[PathBuf]> { + self.bindings.as_deref() + } + + /// Impls section. + pub fn impls(&self) -> &BTreeMap<String, Vec<PathBuf>> { + &self.impls + } + + /// Bibliographies. + pub fn bibliographies(&self) -> Option<&[PathBuf]> { + self.bibliography.as_deref() + } + + /// Classes.. + pub fn classes(&self) -> Option<&[String]> { + self.classes.as_deref() + } + + /// Documentclass. + pub fn documentclass(&self) -> Option<&str> { + self.documentclass.as_deref() + } + + /// Pandoc metadata. + pub fn pandoc(&self) -> Option<&HashMap<String, Value>> { + if let Some(x) = &self.pandoc { + Some(x) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::YamlMetadata; + use std::path::{Path, PathBuf}; + + #[test] + fn full_meta() { + let meta = YamlMetadata::new( + "\ +title: Foo Bar +date: today +classes: [json, text] +impls: + python: + - foo.py + - bar.py +bibliography: +- foo.bib +- bar.bib +markdowns: +- test.md +bindings: +- foo.yaml +- bar.yaml +", + ) + .unwrap(); + assert_eq!(meta.title, "Foo Bar"); + assert_eq!(meta.date.unwrap(), "today"); + assert_eq!(meta.classes.unwrap(), &["json", "text"]); + assert_eq!( + meta.bibliography.unwrap(), + &[path("foo.bib"), path("bar.bib")] + ); + assert_eq!(meta.markdowns, vec![Path::new("test.md")]); + assert_eq!( + meta.bindings.unwrap(), + &[path("foo.yaml"), path("bar.yaml")] + ); + assert!(!meta.impls.is_empty()); + for (k, v) in meta.impls.iter() { + assert_eq!(k, "python"); + assert_eq!(v, &[path("foo.py"), path("bar.py")]); + } + } + + fn path(s: &str) -> PathBuf { + PathBuf::from(s) + } +} + /// Metadata of a document, as needed by Subplot. #[derive(Debug)] pub struct Metadata { |