summaryrefslogtreecommitdiff
path: root/src/typeset.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/typeset.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/typeset.rs')
-rw-r--r--src/typeset.rs181
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)
+}