diff options
Diffstat (limited to 'src/ast.rs')
-rw-r--r-- | src/ast.rs | 149 |
1 files changed, 60 insertions, 89 deletions
@@ -25,51 +25,42 @@ lazy_static! { #[derive(Debug)] pub struct AbstractSyntaxTree { blocks: Vec<Block>, - meta: Map<String, MetaValue>, + meta: YamlMetadata, } impl AbstractSyntaxTree { - // Create a new AST. - // - // Note that this is not public. - fn new(meta: Map<String, MetaValue>, blocks: Vec<Block>) -> Self { + /// Create a new AST. + pub fn new(meta: YamlMetadata, markdown: &str) -> Self { + let blocks = parse_blocks(markdown); Self { blocks, meta } } /// Return a Pandoc-compatible AST. pub fn to_pandoc(&self) -> Pandoc { Pandoc { - meta: self.meta.clone(), + meta: self.meta.to_map(), blocks: self.blocks.clone(), pandoc_api_version: vec![1, 20], } } } -impl std::str::FromStr for AbstractSyntaxTree { - type Err = Error; - - /// Create an abstract syntax tree from a string. - fn from_str(markdown: &str) -> Result<Self, Self::Err> { - trace!("Parsing markdown"); - let ast = if let Some((yaml, markdown)) = get_yaml(&LEADING_YAML_PATTERN, markdown) { - trace!("Found leading YAML: {:?}", yaml); - let meta = Metadata::new(yaml)?.to_map(); - let blocks = parse_blocks(markdown); - AbstractSyntaxTree::new(meta, blocks) - } else if let Some((yaml, _markdown)) = get_yaml(&TRAILING_YAML_PATTERN, markdown) { - trace!("Found trailing YAML: {:?}", yaml); - let meta = Metadata::new(yaml)?.to_map(); - let blocks = parse_blocks(markdown); - AbstractSyntaxTree::new(meta, blocks) - } else { - trace!("No YAML to be found"); - let blocks = parse_blocks(markdown); - AbstractSyntaxTree::new(Map::new(), blocks) - }; - trace!("Parsing markdown: OK"); - Ok(ast) - } +/// Extract YAML metadata from a Markdown document. +pub fn extract_metadata(markdown: &str) -> Result<(YamlMetadata, &str), Error> { + trace!("Extracting YAML from Markdown"); + let (yaml, md) = if let Some((yaml, markdown)) = get_yaml(&LEADING_YAML_PATTERN, markdown) { + trace!("Found leading YAML: {:?}", yaml); + (yaml, markdown) + } else if let Some((yaml, _markdown)) = get_yaml(&TRAILING_YAML_PATTERN, markdown) { + trace!("Found trailing YAML: {:?}", yaml); + (yaml, markdown) + } else { + trace!("No YAML to be found"); + return Err(Error::NoMetadata); + }; + let meta = YamlMetadata::new(yaml)?; + trace!("Parsing markdown: OK"); + Ok((meta, md)) } // Extract a YAML metadata block using a given regex. @@ -272,22 +263,25 @@ 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. +/// 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, Deserialize)] #[serde(deny_unknown_fields)] -struct Metadata { +pub struct YamlMetadata { title: String, subtitle: Option<String>, author: Option<String>, @@ -300,7 +294,7 @@ struct Metadata { impls: BTreeMap<String, Vec<PathBuf>>, } -impl Metadata { +impl YamlMetadata { fn new(yaml_text: &str) -> Result<Self, Error> { trace!("Parsing YAML"); let meta: Self = serde_yaml::from_str(yaml_text)?; @@ -310,45 +304,51 @@ impl Metadata { fn to_map(&self) -> Map<String, MetaValue> { trace!("Creating metadata map from parsed YAML"); let mut map: Map<String, MetaValue> = Map::new(); - map.insert(s("title"), meta_string(&self.title)); + + map.insert("title".into(), meta_string(&self.title)); + if let Some(v) = &self.subtitle { - map.insert(s("subtitle"), meta_string(v)); + map.insert("subtitle".into(), meta_string(v)); } + if let Some(v) = &self.author { - map.insert(s("author"), meta_string(v)); + map.insert("author".into(), meta_string(v)); } + if let Some(v) = &self.date { - map.insert(s("date"), meta_string(v)); + map.insert("date".into(), meta_string(v)); } + if let Some(v) = &self.classes { - map.insert(s("classes"), meta_strings(v)); + map.insert("classes".into(), meta_strings(v)); } + if !self.impls.is_empty() { let impls = self .impls .iter() .map(|(k, v)| (k.to_owned(), Box::new(meta_path_bufs(v)))) .collect(); - map.insert(s("impls"), MetaValue::MetaMap(impls)); + map.insert("impls".into(), MetaValue::MetaMap(impls)); } + if let Some(v) = &self.bibliography { - map.insert(s("bibliography"), meta_path_bufs(v)); + map.insert("bibliography".into(), meta_path_bufs(v)); } + if let Some(v) = &self.bindings { - map.insert(s("bindings"), meta_path_bufs(v)); + map.insert("bindings".into(), meta_path_bufs(v)); } + if let Some(v) = &self.documentclass { - map.insert(s("documentclass"), meta_string(v)); + map.insert("documentclass".into(), meta_string(v)); } + trace!("Created metadata map from parsed YAML"); map } } -fn s(s: &str) -> String { - s.to_string() -} - fn meta_string(s: &str) -> MetaValue { MetaValue::MetaString(s.to_string()) } @@ -367,10 +367,8 @@ fn meta_path_bufs(v: &[PathBuf]) -> MetaValue { #[cfg(test)] mod test { - use super::{parse_code_block_attrs, AbstractSyntaxTree, Metadata}; - use super::{Block, Inline}; + use super::{extract_metadata, parse_code_block_attrs, AbstractSyntaxTree, YamlMetadata}; use std::path::PathBuf; - use std::str::FromStr; #[test] fn code_block_attrs() { @@ -394,38 +392,10 @@ mod test { } #[test] - fn empty_input() { - let ast = AbstractSyntaxTree::from_str("").unwrap(); - let doc = ast.to_pandoc(); - assert!(doc.blocks.is_empty()); - assert!(doc.meta.is_empty()); - assert!(!doc.pandoc_api_version.is_empty()); - } - - #[test] - fn simple() { - let ast = AbstractSyntaxTree::from_str( - "\ - # Introduction \n\ - \n\ - First paragraph.\n\ - ", - ) - .unwrap(); - let doc = ast.to_pandoc(); - assert!(doc.meta.is_empty()); - assert!(!doc.pandoc_api_version.is_empty()); - - let attr = ("".to_string(), vec![], vec![]); - let h = Block::Header(1, attr, vec![Inline::Str("Introduction".to_string())]); - let para = Block::Para(vec![Inline::Str("First paragraph.".to_string())]); - assert_eq!(doc.blocks, &[h, para]); - } - - #[test] fn parses_leading_meta() { let markdown = "\n\n---\ntitle: Foo Bar\n...\nfoobar\n"; - let ast = AbstractSyntaxTree::from_str(markdown).unwrap(); + let (meta, markdown) = extract_metadata(markdown).unwrap(); + let ast = AbstractSyntaxTree::new(meta, markdown); let doc = ast.to_pandoc(); let keys: Vec<String> = doc.meta.keys().cloned().collect(); assert_eq!(keys, ["title"]); @@ -434,7 +404,8 @@ mod test { #[test] fn parses_trailing_meta() { let markdown = "foobar\n---\ntitle: Foo Bar\n...\n\n\n"; - let ast = AbstractSyntaxTree::from_str(markdown).unwrap(); + let (meta, markdown) = extract_metadata(markdown).unwrap(); + let ast = AbstractSyntaxTree::new(meta, markdown); let doc = ast.to_pandoc(); let keys: Vec<String> = doc.meta.keys().cloned().collect(); assert_eq!(keys, ["title"]); @@ -442,7 +413,7 @@ mod test { #[test] fn full_meta() { - let meta = Metadata::new( + let meta = YamlMetadata::new( "\ title: Foo Bar date: today |