diff options
author | Lars Wirzenius <liw@liw.fi> | 2020-08-08 18:43:57 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2020-08-08 20:47:22 +0300 |
commit | d1651321ec1cd1e02fb93fe6e31ab4de115356c9 (patch) | |
tree | f5966966f0cb910659a62afa63c7f089913e9937 /src/typeset.rs | |
parent | c3e8c88f3294338e8ea4678fb5493c96150a4e3c (diff) | |
download | subplot-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/typeset.rs')
-rw-r--r-- | src/typeset.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/typeset.rs b/src/typeset.rs new file mode 100644 index 0000000..32956c6 --- /dev/null +++ b/src/typeset.rs @@ -0,0 +1,181 @@ +use crate::parser::parse_scenario_snippet; +use crate::Bindings; +use crate::PartialStep; +use crate::ScenarioStep; +use crate::StepKind; +use crate::SubplotError; +use crate::{DotMarkup, GraphMarkup, PlantumlMarkup}; + +use pandoc_ast::Attr; +use pandoc_ast::Block; +use pandoc_ast::Inline; + +/// 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 string as an inline element. +pub fn inlinestr(s: &str) -> Inline { + Inline::Str(String::from(s)) +} + +/// 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]) +} + +// 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. +pub 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. +pub 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. +pub 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) +} |