summaryrefslogtreecommitdiff
path: root/src/ast.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ast.rs')
-rw-r--r--src/ast.rs149
1 files changed, 60 insertions, 89 deletions
diff --git a/src/ast.rs b/src/ast.rs
index eb10efc..f02fdd1 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -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