summaryrefslogtreecommitdiff
path: root/src/ast.rs
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-08-08 18:43:57 +0300
committerLars Wirzenius <liw@liw.fi>2020-08-08 20:47:22 +0300
commitd1651321ec1cd1e02fb93fe6e31ab4de115356c9 (patch)
treef5966966f0cb910659a62afa63c7f089913e9937 /src/ast.rs
parentc3e8c88f3294338e8ea4678fb5493c96150a4e3c (diff)
downloadsubplot-d1651321ec1cd1e02fb93fe6e31ab4de115356c9.tar.gz
refactor: split stuff from src/ast.rs into smaller modules
This only moves things around, to avoid huge source code modules. It doesn't rename functions, add unit tests, or similar. * src/datafiles.rs: DataFile, DataFiles * src/metata.rs: Metadata * src/panhelper.rs: functions for querying Pandoc Attrs * src/policy.rs: the get_basedir_from function; place for later policy functions * src/typeset.rs: functions to produce Pandoc AST nodes * srv/visitor/*: various MutVisitor implementations for traversing ASTs, and their helper functions
Diffstat (limited to 'src/ast.rs')
-rw-r--r--src/ast.rs749
1 files changed, 16 insertions, 733 deletions
diff --git a/src/ast.rs b/src/ast.rs
index fedacc2..41a1461 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -1,19 +1,19 @@
use crate::parser::parse_scenario_snippet;
-use crate::Bindings;
+use crate::visitor;
+use crate::DataFile;
+use crate::DataFiles;
+use crate::LintingVisitor;
use crate::MatchedScenario;
-use crate::PartialStep;
+use crate::Metadata;
use crate::Scenario;
use crate::ScenarioStep;
-use crate::StepKind;
-use crate::{DotMarkup, GraphMarkup, PlantumlMarkup};
use crate::{Result, SubplotError};
use std::collections::HashSet;
use std::ops::Deref;
use std::path::{Path, PathBuf};
-use pandoc_ast::{Attr, Block, Inline, Map, MetaValue, MutVisitor, Pandoc};
-use serde::{Deserialize, Serialize};
+use pandoc_ast::{MutVisitor, Pandoc};
/// The set of known (special) classes which subplot will always recognise
/// as being valid.
@@ -165,7 +165,7 @@ impl<'a> Document {
names.push(x.to_path_buf());
}
- let mut visitor = ImageVisitor::new();
+ let mut visitor = visitor::ImageVisitor::new();
visitor.walk_pandoc(&mut self.ast);
for x in visitor.images().iter() {
names.push(x.to_path_buf());
@@ -210,7 +210,7 @@ impl<'a> Document {
/// Check that all the block classes in the document are known
fn check_block_classes(&self) -> Result<()> {
- let mut visitor = BlockClassVisitor::default();
+ let mut visitor = visitor::BlockClassVisitor::default();
// Irritatingly we can't immutably visit the AST for some reason
// This clone() is expensive and unwanted, but I'm not sure how
// to get around it for now
@@ -242,13 +242,13 @@ impl<'a> Document {
/// Typeset a Subplot document.
pub fn typeset(&mut self) {
- let mut visitor = TypesettingVisitor::new(&self.meta.bindings);
+ let mut visitor = visitor::TypesettingVisitor::new(&self.meta.bindings());
visitor.walk_pandoc(&mut self.ast);
}
/// Return all scenarios in a document.
pub fn scenarios(&mut self) -> Result<Vec<Scenario>> {
- let mut visitor = StructureVisitor::new();
+ let mut visitor = visitor::StructureVisitor::new();
visitor.walk_pandoc(&mut self.ast);
let mut scenarios: Vec<Scenario> = vec![];
@@ -276,20 +276,20 @@ impl<'a> Document {
}
}
-fn extract_scenario(e: &[Element]) -> Result<(Option<Scenario>, usize)> {
+fn extract_scenario(e: &[visitor::Element]) -> Result<(Option<Scenario>, usize)> {
if e.is_empty() {
// If we get here, it's a programming error.
panic!("didn't expect empty list of elements");
}
match &e[0] {
- Element::Snippet(_) => Err(SubplotError::ScenarioBeforeHeading),
- Element::Heading(title, level) => {
+ visitor::Element::Snippet(_) => Err(SubplotError::ScenarioBeforeHeading),
+ visitor::Element::Heading(title, level) => {
let mut scen = Scenario::new(&title);
let mut prevkind = None;
for (i, item) in e.iter().enumerate().skip(1) {
match item {
- Element::Heading(_, level2) => {
+ visitor::Element::Heading(_, level2) => {
let is_subsection = *level2 > *level;
if is_subsection {
if scen.has_steps() {
@@ -302,7 +302,7 @@ fn extract_scenario(e: &[Element]) -> Result<(Option<Scenario>, usize)> {
return Ok((None, i));
}
}
- Element::Snippet(text) => {
+ visitor::Element::Snippet(text) => {
for line in parse_scenario_snippet(&text) {
let step = ScenarioStep::new_from_str(line, prevkind)?;
scen.add(&step);
@@ -323,7 +323,7 @@ fn extract_scenario(e: &[Element]) -> Result<(Option<Scenario>, usize)> {
#[cfg(test)]
mod test_extract {
use super::extract_scenario;
- use super::Element;
+ use super::visitor::Element;
use crate::Result;
use crate::Scenario;
use crate::SubplotError;
@@ -426,720 +426,3 @@ mod test_extract {
}
}
}
-
-/// Metadata of a document, as needed by Subplot.
-#[derive(Debug)]
-pub struct Metadata {
- title: String,
- date: Option<String>,
- bindings_filenames: Vec<PathBuf>,
- bindings: Bindings,
- functions_filenames: Vec<PathBuf>,
- template: Option<String>,
- bibliographies: Vec<PathBuf>,
- /// Extra class names which should be considered 'correct' for this document
- classes: Vec<String>,
-}
-
-impl Metadata {
- /// Construct a Metadata from a Document, if possible.
- pub fn new<P>(basedir: P, doc: &Pandoc) -> Result<Metadata>
- where
- P: AsRef<Path>,
- {
- let title = get_title(&doc.meta)?;
- let date = get_date(&doc.meta);
- let bindings_filenames = get_bindings_filenames(basedir.as_ref(), &doc.meta);
- let functions_filenames = get_functions_filenames(basedir.as_ref(), &doc.meta);
- let template = get_template_name(&doc.meta)?;
- let mut bindings = Bindings::new();
-
- let bibliographies = get_bibliographies(basedir.as_ref(), &doc.meta);
- let classes = get_classes(&doc.meta);
-
- get_bindings(&bindings_filenames, &mut bindings)?;
- Ok(Metadata {
- title,
- date,
- bindings_filenames,
- bindings,
- functions_filenames,
- template,
- 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> {
- if let Some(date) = &self.date {
- Some(&date)
- } else {
- None
- }
- }
-
- /// Return filename where bindings are specified.
- pub fn bindings_filenames(&self) -> Vec<&Path> {
- self.bindings_filenames.iter().map(|f| f.as_ref()).collect()
- }
-
- /// Return filename where functions are specified.
- pub fn functions_filenames(&self) -> Vec<&Path> {
- self.functions_filenames
- .iter()
- .map(|f| f.as_ref())
- .collect()
- }
-
- /// Return the name of the code template, if specified.
- pub fn template_name(&self) -> Option<&str> {
- match &self.template {
- Some(x) => Some(&x),
- None => None,
- }
- }
-
- /// 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<Item = &str> {
- self.classes.iter().map(Deref::deref)
- }
-}
-
-type Mapp = Map<String, MetaValue>;
-
-fn get_title(map: &Mapp) -> Result<String> {
- if let Some(s) = get_string(map, "title") {
- Ok(s)
- } else {
- Ok("".to_string())
- }
-}
-
-fn get_date(map: &Mapp) -> Option<String> {
- if let Some(s) = get_string(map, "date") {
- Some(s)
- } else {
- None
- }
-}
-
-fn get_bindings_filenames<P>(basedir: P, map: &Mapp) -> Vec<PathBuf>
-where
- P: AsRef<Path>,
-{
- get_paths(basedir, map, "bindings")
-}
-
-fn get_functions_filenames<P>(basedir: P, map: &Mapp) -> Vec<PathBuf>
-where
- P: AsRef<Path>,
-{
- get_paths(basedir, map, "functions")
-}
-
-fn get_template_name(map: &Mapp) -> Result<Option<String>> {
- match get_string(map, "template") {
- Some(s) => Ok(Some(s)),
- None => Ok(None),
- }
-}
-
-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::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_str(" "),
- pandoc_ast::Inline::SoftBreak => buf.push_str(" "),
- pandoc_ast::Inline::LineBreak => buf.push_str(" "),
- _ => panic!("unknown pandoc_ast::Inline component {:?}", item),
- }
- }
-}
-
-#[cfg(test)]
-mod test_join {
- use super::join;
- use pandoc_ast::Inline;
-
- #[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::LineBreak,
- ];
- assert_eq!(join(&v), "abcdefg ");
- }
-}
-
-fn get_bindings<P>(filenames: &[P], bindings: &mut Bindings) -> Result<()>
-where
- P: AsRef<Path>,
-{
- for filename in filenames {
- bindings.add_from_file(filename)?;
- }
- Ok(())
-}
-
-/// Visitor for the pandoc AST.
-///
-/// This includes rendering stuff which we find as we go
-struct TypesettingVisitor<'a> {
- bindings: &'a Bindings,
-}
-
-impl<'a> TypesettingVisitor<'a> {
- fn new(bindings: &'a Bindings) -> Self {
- TypesettingVisitor { bindings }
- }
-}
-
-// Visit interesting parts of the Pandoc abstract syntax tree. The
-// document top level is a vector of blocks and we visit that and
-// replace any fenced code block with the scenario tag with a typeset
-// paragraph. Also, replace fenced code blocks with known graph markup
-// with the rendered SVG image.
-impl<'a> MutVisitor for TypesettingVisitor<'a> {
- fn visit_vec_block(&mut self, vec_block: &mut Vec<Block>) {
- for block in vec_block {
- match block {
- Block::CodeBlock(attr, s) => {
- if is_class(attr, "scenario") {
- *block = scenario_snippet(&self.bindings, s)
- } else if is_class(attr, "file") {
- *block = file_block(attr, s)
- } else if is_class(attr, "dot") {
- *block = dot_to_block(s)
- } else if is_class(attr, "plantuml") {
- *block = plantuml_to_block(s)
- } else if is_class(attr, "roadmap") {
- *block = roadmap_to_block(s)
- }
- }
- _ => {
- self.visit_block(block);
- }
- }
- }
- }
-}
-
-// Is a code block marked as being of a given type?
-fn is_class(attr: &Attr, class: &str) -> bool {
- let (_id, classes, _kvpairs) = attr;
- classes.iter().any(|s| s == class)
-}
-
-/// Typeset an error as a Pandoc AST Block element.
-pub fn error(err: SubplotError) -> Block {
- let msg = format!("ERROR: {}", err.to_string());
- Block::Para(error_msg(&msg))
-}
-
-// Typeset an error message a vector of inlines.
-pub fn error_msg(msg: &str) -> Vec<Inline> {
- vec![Inline::Strong(vec![inlinestr(msg)])]
-}
-
-/// Typeset a code block tagged as a file.
-pub fn file_block(attr: &Attr, text: &str) -> Block {
- let filename = inlinestr(&attr.0);
- let filename = Inline::Strong(vec![filename]);
- let intro = Block::Para(vec![inlinestr("File:"), space(), filename]);
- let codeblock = Block::CodeBlock(attr.clone(), text.to_string());
- let noattr = ("".to_string(), vec![], vec![]);
- Block::Div(noattr, vec![intro, codeblock])
-}
-
-/// Typeset a scenario snippet as a Pandoc AST Block.
-///
-/// Typesetting here means producing the Pandoc abstract syntax tree
-/// nodes that result in the desired output, when Pandoc processes
-/// them.
-///
-/// The snippet is given as a text string, which is parsed. It need
-/// not be a complete scenario, but it should consist of complete steps.
-pub fn scenario_snippet(bindings: &Bindings, snippet: &str) -> Block {
- let lines = parse_scenario_snippet(snippet);
- let mut steps = vec![];
- let mut prevkind: Option<StepKind> = None;
-
- for line in lines {
- let (this, thiskind) = step(bindings, line, prevkind);
- steps.push(this);
- prevkind = thiskind;
- }
- Block::LineBlock(steps)
-}
-
-// Typeset a single scenario step as a sequence of Pandoc AST Inlines.
-fn step(
- bindings: &Bindings,
- text: &str,
- defkind: Option<StepKind>,
-) -> (Vec<Inline>, Option<StepKind>) {
- let step = ScenarioStep::new_from_str(text, defkind);
- if step.is_err() {
- return (
- error_msg(&format!("Could not parse step: {}", text)),
- defkind,
- );
- }
- let step = step.unwrap();
-
- let m = match bindings.find(&step) {
- Ok(m) => m,
- Err(e) => {
- eprintln!("Could not select binding: {:?}", e);
- return (
- error_msg(&format!("Could not select binding for: {}", text)),
- defkind,
- );
- }
- };
-
- let mut inlines = Vec::new();
-
- inlines.push(keyword(&step));
- inlines.push(space());
-
- for part in m.parts() {
- #[allow(unused_variables)]
- match part {
- PartialStep::UncapturedText(s) => inlines.push(uncaptured(s.text())),
- PartialStep::CapturedText { name, text } => inlines.push(captured(text)),
- }
- }
-
- (inlines, Some(step.kind()))
-}
-
-// Typeset first word, which is assumed to be a keyword, of a scenario
-// step.
-fn keyword(step: &ScenarioStep) -> Inline {
- let word = inlinestr(step.keyword());
- Inline::Emph(vec![word])
-}
-
-fn inlinestr(s: &str) -> Inline {
- Inline::Str(String::from(s))
-}
-
-// Typeset a space between words.
-fn space() -> Inline {
- Inline::Space
-}
-
-// Typeset an uncaptured part of a step.
-fn uncaptured(s: &str) -> Inline {
- inlinestr(s)
-}
-
-// Typeset a captured part of a step.
-fn captured(s: &str) -> Inline {
- Inline::Strong(vec![inlinestr(s)])
-}
-
-// Take a dot graph, render it as SVG, and return an AST Block
-// element. The Block will contain the SVG data. This allows the graph
-// to be rendered without referending external entities.
-fn dot_to_block(dot: &str) -> Block {
- match DotMarkup::new(dot).as_svg() {
- Ok(svg) => typeset_svg(svg),
- Err(err) => {
- eprintln!("dot failed: {}", err);
- error(err)
- }
- }
-}
-
-// Take a PlantUML graph, render it as SVG, and return an AST Block
-// element. The Block will contain the SVG data. This allows the graph
-// to be rendered without referending external entities.
-fn plantuml_to_block(markup: &str) -> Block {
- match PlantumlMarkup::new(markup).as_svg() {
- Ok(svg) => typeset_svg(svg),
- Err(err) => {
- eprintln!("plantuml failed: {}", err);
- error(err)
- }
- }
-}
-
-/// Typeset a project roadmap expressed as textual YAML, and render it
-/// as an SVG image.
-fn roadmap_to_block(yaml: &str) -> Block {
- match roadmap::from_yaml(yaml) {
- Ok(ref mut roadmap) => {
- roadmap.set_missing_statuses();
- let width = 50;
- match roadmap.format_as_dot(width) {
- Ok(dot) => dot_to_block(&dot),
- Err(e) => Block::Para(vec![inlinestr(&e.to_string())]),
- }
- }
- Err(e) => Block::Para(vec![inlinestr(&e.to_string())]),
- }
-}
-
-// Typeset an SVG, represented as a byte vector, as a Pandoc AST Block
-// element.
-fn typeset_svg(svg: Vec<u8>) -> Block {
- let url = svg_as_data_url(svg);
- let attr = ("".to_string(), vec![], vec![]);
- let img = Inline::Image(attr, vec![], (url, "".to_string()));
- Block::Para(vec![img])
-}
-
-// Convert an SVG, represented as a byte vector, into a data: URL,
-// which can be inlined so the image can be rendered without
-// referencing external files.
-fn svg_as_data_url(svg: Vec<u8>) -> String {
- let svg = base64::encode(&svg);
- format!("data:image/svg+xml;base64,{}", svg)
-}
-
-// A structure element in the document: a heading or a scenario snippet.
-#[derive(Debug)]
-enum Element {
- // Headings consist of the text and the level of the heading.
- Heading(String, i64),
-
- // Scenario snippets consist just of the unparsed text.
- Snippet(String),
-}
-
-impl Element {
- pub fn heading(text: &str, level: i64) -> Element {
- Element::Heading(text.to_string(), level)
- }
-
- pub fn snippet(text: &str) -> Element {
- Element::Snippet(text.to_string())
- }
-}
-
-// A MutVisitor for extracting document structure.
-struct StructureVisitor {
- elements: Vec<Element>,
-}
-
-impl StructureVisitor {
- pub fn new() -> Self {
- Self { elements: vec![] }
- }
-}
-
-impl MutVisitor for StructureVisitor {
- fn visit_vec_block(&mut self, vec_block: &mut Vec<Block>) {
- for block in vec_block {
- match block {
- Block::Header(level, _attr, inlines) => {
- let text = join(inlines);
- let heading = Element::heading(&text, *level);
- self.elements.push(heading);
- }
- Block::CodeBlock(attr, s) => {
- if is_class(attr, "scenario") {
- let snippet = Element::snippet(s);
- self.elements.push(snippet);
- }
- }
- _ => {
- self.visit_block(block);
- }
- }
- }
- }
-}
-
-/// A data file embedded in the document.
-#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
-pub struct DataFile {
- filename: String,
- contents: String,
-}
-
-impl DataFile {
- fn new(filename: String, contents: String) -> DataFile {
- DataFile { filename, contents }
- }
-
- /// Return name of embedded file.
- pub fn filename(&self) -> &str {
- &self.filename
- }
-
- /// Return contents of embedded file.
- pub fn contents(&self) -> &str {
- &self.contents
- }
-}
-
-/// A collection of data files embedded in document.
-#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
-struct DataFiles {
- files: Vec<DataFile>,
-}
-
-impl DataFiles {
- fn new(ast: &mut Pandoc) -> DataFiles {
- let mut files = DataFiles { files: vec![] };
- files.walk_pandoc(ast);
- files
- }
-
- fn files(&self) -> &[DataFile] {
- &self.files
- }
-}
-
-impl MutVisitor for DataFiles {
- fn visit_vec_block(&mut self, vec_block: &mut Vec<Block>) {
- for block in vec_block {
- match block {
- Block::CodeBlock(attr, contents) => {
- if is_class(attr, "file") {
- let add_newline = match find_attr_kv(&attr, "add-newline").next() {
- None | Some("auto") => !contents.ends_with('\n'),
- Some("yes") => true,
- Some("no") => false,
- _ => unreachable!(),
- };
- let contents = if add_newline {
- format!("{}\n", contents)
- } else {
- contents.clone()
- };
- self.files.push(DataFile::new(get_filename(attr), contents));
- }
- }
- _ => {
- self.visit_block(block);
- }
- }
- }
- }
-}
-
-fn get_filename(attr: &Attr) -> String {
- attr.0.to_string()
-}
-
-struct ImageVisitor {
- images: Vec<PathBuf>,
-}
-
-impl ImageVisitor {
- fn new() -> Self {
- ImageVisitor { images: vec![] }
- }
-
- fn images(&self) -> Vec<PathBuf> {
- self.images.clone()
- }
-}
-
-impl MutVisitor for ImageVisitor {
- fn visit_inline(&mut self, inline: &mut Inline) {
- if let Inline::Image(_attr, _inlines, target) = inline {
- self.images.push(PathBuf::from(&target.0));
- }
- }
-}
-
-#[derive(Default)]
-struct BlockClassVisitor {
- classes: HashSet<String>,
-}
-
-impl MutVisitor for BlockClassVisitor {
- fn visit_vec_block(&mut self, vec_block: &mut Vec<Block>) {
- for block in vec_block {
- match block {
- Block::CodeBlock(attr, _) => {
- for class in &attr.1 {
- self.classes.insert(class.to_string());
- }
- }
- _ => {
- self.visit_block(block);
- }
- }
- }
- }
-}
-
-#[derive(Default)]
-struct LintingVisitor {
- issues: Vec<SubplotError>,
-}
-
-impl MutVisitor for LintingVisitor {
- fn visit_vec_block(&mut self, vec_block: &mut Vec<Block>) {
- for block in vec_block {
- match block {
- Block::CodeBlock(attr, _) => {
- if is_class(attr, "file") {
- let newlines: Vec<_> = find_attr_kv(&attr, "add-newline").collect();
- match newlines.len() {
- 0 => {}
- 1 => match newlines[0].to_ascii_lowercase().as_ref() {
- "auto" | "yes" | "no" => {}
- _ => self.issues.push(SubplotError::UnrecognisedAddNewline(
- get_filename(&attr),
- newlines[0].to_owned(),
- )),
- },
- _ => self.issues.push(SubplotError::RepeatedAddNewlineAttribute(
- get_filename(&attr),
- )),
- }
- }
- }
- _ => {
- self.visit_block(block);
- }
- }
- }
- }
-}
-
-/// Get the base directory given the name of the markdown file.
-///
-/// All relative filename, such as bindings files, are resolved
-/// against the base directory.
-pub fn get_basedir_from(filename: &Path) -> Result<PathBuf> {
- let dirname = match filename.parent() {
- None => return Err(SubplotError::BasedirError(filename.to_path_buf())),
- Some(x) => x.to_path_buf(),
- };
- Ok(dirname)
-}
-
-/// Utility function to find key/value pairs from an attribute
-fn find_attr_kv<'a>(attr: &'a Attr, key: &'static str) -> impl Iterator<Item = &'a str> {
- attr.2.iter().flat_map(move |(key_, value)| {
- if key == key_ {
- Some(value.as_ref())
- } else {
- None
- }
- })
-}