From 3422999e5a4fc6b08637d00cae11f523c747f036 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 26 Apr 2022 07:48:22 +0300 Subject: refactor: use "diagram" instead of "graph" Change everywhere to use the word "diagram" instead of "graph. Diagram seems like the better word. It's mostly used in comments and documentation, but there was one filename and one trait name that needed changing. I used "rg -iw graph" and "find -iname '*graph*'" to find everywhere that needed changing. Sponsored-by: author --- src/diagrams.rs | 224 +++++++++++++++++++++++++++++++++++++++++++++ src/graphmarkup.rs | 224 --------------------------------------------- src/lib.rs | 4 +- src/typeset.rs | 20 ++-- src/visitor/typesetting.rs | 4 +- subplot.md | 24 ++--- 6 files changed, 250 insertions(+), 250 deletions(-) create mode 100644 src/diagrams.rs delete mode 100644 src/graphmarkup.rs diff --git a/src/diagrams.rs b/src/diagrams.rs new file mode 100644 index 0000000..96f5d70 --- /dev/null +++ b/src/diagrams.rs @@ -0,0 +1,224 @@ +use crate::{Result, SubplotError}; + +use std::env; +use std::ffi::OsString; +use std::io::prelude::*; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::sync::Mutex; + +use lazy_static::lazy_static; +use structopt::StructOpt; + +/// Resources used to configure paths for dot, plantuml.jar, and friends + +#[allow(missing_docs)] +#[derive(Debug, StructOpt)] +pub struct MarkupOpts { + #[structopt( + long = "dot", + help = "Path to the `dot` binary.", + name = "DOTPATH", + env = "SUBPLOT_DOT_PATH" + )] + dot_path: Option, + #[structopt( + long = "plantuml-jar", + help = "Path to the `plantuml.jar` file.", + name = "PLANTUMLJARPATH", + env = "SUBPLOT_PLANTUML_JAR_PATH" + )] + plantuml_jar_path: Option, + #[structopt( + long = "java", + help = "Path to Java executable (note, effectively overrides JAVA_HOME if set to an absolute path)", + name = "JAVA_PATH", + env = "SUBPLOT_JAVA_PATH" + )] + java_path: Option, +} + +impl MarkupOpts { + /// Handle CLI arguments and environment variables for markup binaries + pub fn handle(&self) { + if let Some(dotpath) = &self.dot_path { + *DOT_PATH.lock().unwrap() = dotpath.clone(); + } + if let Some(plantuml_path) = &self.plantuml_jar_path { + *PLANTUML_JAR_PATH.lock().unwrap() = plantuml_path.clone(); + } + if let Some(java_path) = &self.java_path { + *JAVA_PATH.lock().unwrap() = java_path.clone(); + } + } +} + +lazy_static! { + static ref DOT_PATH: Mutex = Mutex::new(env!("BUILTIN_DOT_PATH").into()); + static ref PLANTUML_JAR_PATH: Mutex = + Mutex::new(env!("BUILTIN_PLANTUML_JAR_PATH").into()); + static ref JAVA_PATH: Mutex = Mutex::new(env!("BUILTIN_JAVA_PATH").into()); +} + +/// A code block with markup for a diagram. +/// +/// The code block will be converted to an SVG image using an external +/// filter such as Graphviz dot or plantuml. SVG is the chosen image +/// format as it's suitable for all kinds of output formats from +/// typesetting. +/// +/// This trait defines the interface for different kinds of markup +/// conversions. There's only one function that needs to be defined +/// for the trait. +pub trait DiagramMarkup { + /// Convert the markup into an SVG. + fn as_svg(&self) -> Result>; +} + +/// A code block with pikchr markup. +/// +/// ~~~~ +/// use subplot::{DiagramMarkup, PikchrMarkup}; +/// let markup = r#"line; box "Hello," "World!"; arrow"#; +/// let svg = PikchrMarkup::new(markup, None).as_svg().unwrap(); +/// assert!(svg.len() > 0); +/// ~~~~ +pub struct PikchrMarkup { + markup: String, + class: Option, +} + +impl PikchrMarkup { + /// Create a new Pikchr Markup holder + pub fn new(markup: &str, class: Option<&str>) -> PikchrMarkup { + PikchrMarkup { + markup: markup.to_owned(), + class: class.map(str::to_owned), + } + } +} + +impl DiagramMarkup for PikchrMarkup { + fn as_svg(&self) -> Result> { + let mut flags = pikchr::PikchrFlags::default(); + flags.generate_plain_errors(); + let image = pikchr::Pikchr::render(&self.markup, self.class.as_deref(), flags) + .map_err(SubplotError::PikchrRenderError)?; + Ok(image.as_bytes().to_vec()) + } +} + +/// A code block with Dot markup. +/// +/// ~~~~ +/// use subplot::{DiagramMarkup, DotMarkup}; +/// let markup = r#"digraph "foo" { a -> b }"#; +/// let svg = DotMarkup::new(&markup).as_svg().unwrap(); +/// assert!(svg.len() > 0); +/// ~~~~ +pub struct DotMarkup { + markup: String, +} + +impl DotMarkup { + /// Create a new DotMarkup. + pub fn new(markup: &str) -> DotMarkup { + DotMarkup { + markup: markup.to_owned(), + } + } +} + +impl DiagramMarkup for DotMarkup { + fn as_svg(&self) -> Result> { + let mut child = Command::new(DOT_PATH.lock().unwrap().clone()) + .arg("-Tsvg") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + if let Some(stdin) = child.stdin.as_mut() { + stdin.write_all(self.markup.as_bytes())?; + let output = child.wait_with_output()?; + if output.status.success() { + Ok(output.stdout) + } else { + Err(SubplotError::child_failed("dot", &output)) + } + } else { + Err(SubplotError::ChildNoStdin) + } + } +} + +/// A code block with PlantUML markup. +/// +/// ~~~~ +/// use subplot::{DiagramMarkup, PlantumlMarkup}; +/// let markup = "@startuml\nAlice -> Bob\n@enduml"; +/// let svg = PlantumlMarkup::new(&markup).as_svg().unwrap(); +/// assert!(svg.len() > 0); +/// ~~~~ +pub struct PlantumlMarkup { + markup: String, +} + +impl PlantumlMarkup { + /// Create a new PlantumlMarkup. + pub fn new(markup: &str) -> PlantumlMarkup { + PlantumlMarkup { + markup: markup.to_owned(), + } + } + + // If JAVA_HOME is set, and PATH is set, then: + // Check if JAVA_HOME/bin is in PATH, if not, prepend it and return a new + // PATH + fn build_java_path() -> Option { + let java_home = env::var_os("JAVA_HOME")?; + let cur_path = env::var_os("PATH")?; + let cur_path: Vec<_> = env::split_paths(&cur_path).collect(); + let java_home = PathBuf::from(java_home); + let java_bin = java_home.join("bin"); + if cur_path.iter().any(|v| v.as_os_str() == java_bin) { + // No need to add JAVA_HOME/bin it's already on-path + return None; + } + env::join_paths(Some(java_bin).iter().chain(cur_path.iter())).ok() + } + + // Acquire path to JAR for pandoc +} + +impl DiagramMarkup for PlantumlMarkup { + fn as_svg(&self) -> Result> { + let mut cmd = Command::new(JAVA_PATH.lock().unwrap().clone()); + cmd.arg("-Djava.awt.headless=true") + .arg("-jar") + .arg(PLANTUML_JAR_PATH.lock().unwrap().clone()) + .arg("--") + .arg("-pipe") + .arg("-tsvg") + .arg("-v") + .arg("-graphvizdot") + .arg(DOT_PATH.lock().unwrap().clone()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(path) = Self::build_java_path() { + cmd.env("PATH", path); + } + let mut child = cmd.spawn()?; + if let Some(stdin) = child.stdin.as_mut() { + stdin.write_all(self.markup.as_bytes())?; + let output = child.wait_with_output()?; + if output.status.success() { + Ok(output.stdout) + } else { + Err(SubplotError::child_failed("plantuml", &output)) + } + } else { + Err(SubplotError::ChildNoStdin) + } + } +} diff --git a/src/graphmarkup.rs b/src/graphmarkup.rs deleted file mode 100644 index dc0a37e..0000000 --- a/src/graphmarkup.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::{Result, SubplotError}; - -use std::env; -use std::ffi::OsString; -use std::io::prelude::*; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use std::sync::Mutex; - -use lazy_static::lazy_static; -use structopt::StructOpt; - -/// Resources used to configure paths for dot, plantuml.jar, and friends - -#[allow(missing_docs)] -#[derive(Debug, StructOpt)] -pub struct MarkupOpts { - #[structopt( - long = "dot", - help = "Path to the `dot` binary.", - name = "DOTPATH", - env = "SUBPLOT_DOT_PATH" - )] - dot_path: Option, - #[structopt( - long = "plantuml-jar", - help = "Path to the `plantuml.jar` file.", - name = "PLANTUMLJARPATH", - env = "SUBPLOT_PLANTUML_JAR_PATH" - )] - plantuml_jar_path: Option, - #[structopt( - long = "java", - help = "Path to Java executable (note, effectively overrides JAVA_HOME if set to an absolute path)", - name = "JAVA_PATH", - env = "SUBPLOT_JAVA_PATH" - )] - java_path: Option, -} - -impl MarkupOpts { - /// Handle CLI arguments and environment variables for markup binaries - pub fn handle(&self) { - if let Some(dotpath) = &self.dot_path { - *DOT_PATH.lock().unwrap() = dotpath.clone(); - } - if let Some(plantuml_path) = &self.plantuml_jar_path { - *PLANTUML_JAR_PATH.lock().unwrap() = plantuml_path.clone(); - } - if let Some(java_path) = &self.java_path { - *JAVA_PATH.lock().unwrap() = java_path.clone(); - } - } -} - -lazy_static! { - static ref DOT_PATH: Mutex = Mutex::new(env!("BUILTIN_DOT_PATH").into()); - static ref PLANTUML_JAR_PATH: Mutex = - Mutex::new(env!("BUILTIN_PLANTUML_JAR_PATH").into()); - static ref JAVA_PATH: Mutex = Mutex::new(env!("BUILTIN_JAVA_PATH").into()); -} - -/// A code block with markup for a graph. -/// -/// The code block will be converted to an SVG image using an external -/// filter such as Graphviz dot or plantuml. SVG is the chosen image -/// format as it's suitable for all kinds of output formats from -/// typesetting. -/// -/// This trait defines the interface for different kinds of markup -/// conversions. There's only one function that needs to be defined -/// for the trait. -pub trait GraphMarkup { - /// Convert the markup into an SVG. - fn as_svg(&self) -> Result>; -} - -/// A code block with pikchr markup. -/// -/// ~~~~ -/// use subplot::{GraphMarkup, PikchrMarkup}; -/// let markup = r#"line; box "Hello," "World!"; arrow"#; -/// let svg = PikchrMarkup::new(markup, None).as_svg().unwrap(); -/// assert!(svg.len() > 0); -/// ~~~~ -pub struct PikchrMarkup { - markup: String, - class: Option, -} - -impl PikchrMarkup { - /// Create a new Pikchr Markup holder - pub fn new(markup: &str, class: Option<&str>) -> PikchrMarkup { - PikchrMarkup { - markup: markup.to_owned(), - class: class.map(str::to_owned), - } - } -} - -impl GraphMarkup for PikchrMarkup { - fn as_svg(&self) -> Result> { - let mut flags = pikchr::PikchrFlags::default(); - flags.generate_plain_errors(); - let image = pikchr::Pikchr::render(&self.markup, self.class.as_deref(), flags) - .map_err(SubplotError::PikchrRenderError)?; - Ok(image.as_bytes().to_vec()) - } -} - -/// A code block with Dot markup. -/// -/// ~~~~ -/// use subplot::{GraphMarkup, DotMarkup}; -/// let markup = r#"digraph "foo" { a -> b }"#; -/// let svg = DotMarkup::new(&markup).as_svg().unwrap(); -/// assert!(svg.len() > 0); -/// ~~~~ -pub struct DotMarkup { - markup: String, -} - -impl DotMarkup { - /// Create a new DotMarkup. - pub fn new(markup: &str) -> DotMarkup { - DotMarkup { - markup: markup.to_owned(), - } - } -} - -impl GraphMarkup for DotMarkup { - fn as_svg(&self) -> Result> { - let mut child = Command::new(DOT_PATH.lock().unwrap().clone()) - .arg("-Tsvg") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - if let Some(stdin) = child.stdin.as_mut() { - stdin.write_all(self.markup.as_bytes())?; - let output = child.wait_with_output()?; - if output.status.success() { - Ok(output.stdout) - } else { - Err(SubplotError::child_failed("dot", &output)) - } - } else { - Err(SubplotError::ChildNoStdin) - } - } -} - -/// A code block with PlantUML markup. -/// -/// ~~~~ -/// use subplot::{GraphMarkup, PlantumlMarkup}; -/// let markup = "@startuml\nAlice -> Bob\n@enduml"; -/// let svg = PlantumlMarkup::new(&markup).as_svg().unwrap(); -/// assert!(svg.len() > 0); -/// ~~~~ -pub struct PlantumlMarkup { - markup: String, -} - -impl PlantumlMarkup { - /// Create a new PlantumlMarkup. - pub fn new(markup: &str) -> PlantumlMarkup { - PlantumlMarkup { - markup: markup.to_owned(), - } - } - - // If JAVA_HOME is set, and PATH is set, then: - // Check if JAVA_HOME/bin is in PATH, if not, prepend it and return a new - // PATH - fn build_java_path() -> Option { - let java_home = env::var_os("JAVA_HOME")?; - let cur_path = env::var_os("PATH")?; - let cur_path: Vec<_> = env::split_paths(&cur_path).collect(); - let java_home = PathBuf::from(java_home); - let java_bin = java_home.join("bin"); - if cur_path.iter().any(|v| v.as_os_str() == java_bin) { - // No need to add JAVA_HOME/bin it's already on-path - return None; - } - env::join_paths(Some(java_bin).iter().chain(cur_path.iter())).ok() - } - - // Acquire path to JAR for pandoc -} - -impl GraphMarkup for PlantumlMarkup { - fn as_svg(&self) -> Result> { - let mut cmd = Command::new(JAVA_PATH.lock().unwrap().clone()); - cmd.arg("-Djava.awt.headless=true") - .arg("-jar") - .arg(PLANTUML_JAR_PATH.lock().unwrap().clone()) - .arg("--") - .arg("-pipe") - .arg("-tsvg") - .arg("-v") - .arg("-graphvizdot") - .arg(DOT_PATH.lock().unwrap().clone()) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - if let Some(path) = Self::build_java_path() { - cmd.env("PATH", path); - } - let mut child = cmd.spawn()?; - if let Some(stdin) = child.stdin.as_mut() { - stdin.write_all(self.markup.as_bytes())?; - let output = child.wait_with_output()?; - if output.status.success() { - Ok(output.stdout) - } else { - Err(SubplotError::child_failed("plantuml", &output)) - } - } else { - Err(SubplotError::ChildNoStdin) - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 4bef83a..d253765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,8 +21,8 @@ pub use error::Warnings; pub mod resource; -mod graphmarkup; -pub use graphmarkup::{DotMarkup, GraphMarkup, MarkupOpts, PikchrMarkup, PlantumlMarkup}; +mod diagrams; +pub use diagrams::{DiagramMarkup, DotMarkup, MarkupOpts, PikchrMarkup, PlantumlMarkup}; mod datafiles; pub use datafiles::DataFile; diff --git a/src/typeset.rs b/src/typeset.rs index bc86ef8..18e5a44 100644 --- a/src/typeset.rs +++ b/src/typeset.rs @@ -4,7 +4,7 @@ use crate::PartialStep; use crate::ScenarioStep; use crate::StepKind; use crate::SubplotError; -use crate::{DotMarkup, GraphMarkup, PikchrMarkup, PlantumlMarkup}; +use crate::{DiagramMarkup, DotMarkup, PikchrMarkup, PlantumlMarkup}; use crate::{Warning, Warnings}; use pandoc_ast::Attr; @@ -147,10 +147,10 @@ pub fn link_as_note(attr: Attr, text: Vec, target: Target) -> Inline { Inline::Span(attr, text) } -/// Take a pikchr graph, render it as SVG, and return an AST block element. +/// Take a pikchr diagram, 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 referencing external entities. +/// The `Block` will contain the SVG data. This allows the diagram to +/// be rendered without referencing external entities. /// /// If the code block which contained the pikchr contains other classes, they /// can be added to the SVG for use in later typesetting etc. @@ -164,9 +164,9 @@ pub fn pikchr_to_block(pikchr: &str, class: Option<&str>, warnings: &mut Warning } } -// 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. +// Take a dot diagram, render it as SVG, and return an AST Block +// element. The Block will contain the SVG data. This allows the +// diagram to be rendered without referending external entities. pub fn dot_to_block(dot: &str, warnings: &mut Warnings) -> Block { match DotMarkup::new(dot).as_svg() { Ok(svg) => typeset_svg(svg), @@ -177,9 +177,9 @@ pub fn dot_to_block(dot: &str, warnings: &mut Warnings) -> Block { } } -// 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. +// Take a PlantUML diagram, render it as SVG, and return an AST Block +// element. The Block will contain the SVG data. This allows the +// diagram to be rendered without referending external entities. pub fn plantuml_to_block(markup: &str, warnings: &mut Warnings) -> Block { match PlantumlMarkup::new(markup).as_svg() { Ok(svg) => typeset_svg(svg), diff --git a/src/visitor/typesetting.rs b/src/visitor/typesetting.rs index 6f82c24..8d73f3e 100644 --- a/src/visitor/typesetting.rs +++ b/src/visitor/typesetting.rs @@ -30,8 +30,8 @@ impl<'a> TypesettingVisitor<'a> { // 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. +// paragraph. Also, replace fenced code blocks with known diagram +// markup with the rendered SVG image. impl<'a> MutVisitor for TypesettingVisitor<'a> { fn visit_vec_block(&mut self, vec_block: &mut Vec) { use panhelper::is_class; diff --git a/subplot.md b/subplot.md index 18fe8d5..3d4fb91 100644 --- a/subplot.md +++ b/subplot.md @@ -87,7 +87,7 @@ Subplot also generates a test program, in Python, that verifies the acceptance criteria are met, for developers and testers and auditors to verify the system under test meets its acceptance criteria. The generated program uses code written by the Subplot user to implement -the verification steps. The graph below illustrates this and shows how +the verification steps. The diagram below illustrates this and shows how data flows through the system. ```dot @@ -588,7 +588,7 @@ syntax highlighting, if the language is specified: `.markdown`, Subplot also understands the `dot` and `roadmap` tags, and can use the Graphviz dot program, or the [roadmap][] Rust crate, to produce -graphs. These can useful for describing things visually. +diagrams. These can useful for describing things visually. When typesetting files, Subplot will automatically number the lines in the file so that documentation prose can refer to sections of embedded @@ -2922,9 +2922,9 @@ title: Two embedded files ~~~~~~ -## Embedded graphs +## Embedded diagrams -Subplot allows embedding markup to generate graphs into the Markdown document. +Subplot allows embedding markup to generate diagrams into the Markdown document. ### Pikchr @@ -2942,7 +2942,7 @@ arrow <-> down 70% from last box.s box same "Pikchr" "Formatter" "(docs.rs/pikchr)" fit ~~~ -The scenario checks that a graph is generated and embedded into the HTML output, +The scenario checks that a diagram is generated and embedded into the HTML output, and is not referenced as an external image. ~~~scenario @@ -2972,7 +2972,7 @@ box same "Pikchr" "Formatter" "(docs.rs/pikchr)" fit [Graphviz]: http://www.graphviz.org/ Dot is a program from the [Graphviz][] suite to generate directed -graphs, such as this one. +diagrams, such as this one. ~~~dot digraph "example" { @@ -2980,7 +2980,7 @@ thing -> other } ~~~ -The scenario checks that a graph is generated and embedded into the +The scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image. ~~~scenario @@ -2994,7 +2994,7 @@ then file dot.html matches regex /img src="data:image/svg\+xml;base64,/ The sample input file **dot.md**: ~~~~~~~~{#dot.md .file .markdown .numberLines} -This is an example Markdown file, which embeds a graph using dot markup. +This is an example Markdown file, which embeds a diagram using dot markup. ~~~dot digraph "example" { @@ -3009,7 +3009,7 @@ thing -> other [PlantUML]: https://plantuml.com/ -[PlantUML][] is a program to generate various kinds of graphs for +[PlantUML][] is a program to generate various kinds of diagrams for describing software, such as this one: ~~~plantuml @@ -3022,7 +3022,7 @@ Alice <-- Bob: Another authentication Response @enduml ~~~ -The scenario below checks that a graph is generated and embedded into +The scenario below checks that a diagram is generated and embedded into the HTML output, not referenced as an external image. ~~~scenario @@ -3036,7 +3036,7 @@ then file plantuml.html matches regex /img src="data:image/svg\+xml;base64,/ The sample input file **plantuml.md**: ~~~~~~~~{#plantuml.md .file .markdown .numberLines} -This is an example Markdown file, which embeds a graph using +This is an example Markdown file, which embeds a diagram using PlantUML markup. ~~~plantuml @@ -3103,7 +3103,7 @@ blocked: - next ~~~ -This scenario checks that a graph is generated and embedded into the +This scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image. ~~~scenario -- cgit v1.2.1