summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-11-29 12:49:12 +0200
committerLars Wirzenius <liw@liw.fi>2021-11-29 18:31:17 +0200
commita73ab1321d0646655e3415094aa46a36da67d315 (patch)
treee82a2133619e9db7945336484d8a1450d4b7e5d5
parent1d982b22f51468d87d9577d06007d107b67fe532 (diff)
downloadpandoc-filter-diagram-a73ab1321d0646655e3415094aa46a36da67d315.tar.gz
feat: add basic features
Add rendering of dot, plantuml, pikchr, roadmap, and raw SVG, from fenced code blocks into SVG. Sponsored-by: pep.foundation
-rw-r--r--Cargo.lock460
-rw-r--r--Cargo.toml16
-rw-r--r--README.md65
-rw-r--r--src/bin/pandoc-filter-diagram.rs18
-rw-r--r--src/lib.rs307
-rw-r--r--src/main.rs3
6 files changed, 866 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1c1ef48
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,460 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc78c299ae753905840c5d3ba036c51f61ce5a98a83f98d9c9d29dffd427f71"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap 0.11.0",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "pandoc-filter-diagram"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "base64",
+ "pandoc_ast",
+ "pikchr",
+ "roadmap",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "pandoc_ast"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a940e63c65b34a7be2f847de6847b4bc9b68d74e3f7a5c648ca2fa5317f3bd06"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "pikchr"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c0060934b0227a96428cbe79a42ad6d88cfbbc8490027bde64d12348948a6d2"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "roadmap"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700a8364fa11a9381dad24240223dfa00fc29083b5a70ebec51a9284ce30980"
+dependencies = [
+ "anyhow",
+ "serde_yaml",
+ "structopt",
+ "textwrap 0.14.2",
+ "thiserror",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af"
+dependencies = [
+ "dtoa",
+ "indexmap",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "smawk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+dependencies = [
+ "smawk",
+ "unicode-linebreak",
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 07b3b4d..1c3d0c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,23 @@
name = "pandoc-filter-diagram"
version = "0.1.0"
edition = "2021"
+authors = [
+ "Lars Wirzenius <liw@liw.fi>",
+ "Daniel Silverstone <dsilvers@digital-scurf.org>",
+]
+license = "MIT-0"
+description = '''render diagram markup in a Pandoc abstract syntax tree as SVG'''
+repository = "https://gitlab.com/larswirzenius/pandoc-filter-diagram"
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow = "1.0.50"
+base64 = "0.13.0"
+pandoc_ast = "0.8.0"
+pikchr = "0.1.1"
+roadmap = "0.3.0"
+serde = { version = "1.0.101", features = ["derive"] }
+serde_json = "1.0.72"
+thiserror = "1.0.30"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dbb4ac9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,65 @@
+# pandoc-filter-diagram -- render diagram markup in a Pandoc AST as SVG
+
+[Pandoc][] is a powerful tool for processing documents in various
+formats. It parses each of its supported input formats into an
+in-memory abstract syntax tree, and then renders any supported output
+format from such a tree. In between, it can run an arbitrary program
+to "filter" the tree, and the program can make any arbitrary changes
+to the tree.
+
+This Rust crate provides a library to render as SVG diagrams in
+various markup languages, embedded in Markdown as [fenced code blocks][]
+that are marked with the appropriate class.
+
+~~~~~~~~markdown
+# Example
+
+The following is a simplistic diagram using Graphviz dot language.
+
+~~~dot
+digraph "example" { thing -> other }
+~~~
+~~~~~~~~
+
+This crate also provides a command line program,
+`pandoc-filter-diagram`, which can be used with the Pandoc `--filter`
+command line option.
+
+## Languages
+
+The library supports the following languages:
+
+- [pikchr](https://pikchr.org/home/doc/trunk/homepage.md)
+- [Graphviz dot](https://graphviz.org/doc/info/lang.html)
+- [PlantUML](https://plantuml.com/)
+- [roadmap](https://gitlab.com/larswirzenius/roadmap)
+- [raw SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)
+
+
+# Licence
+
+## The MIT License (MIT)
+
+Copyright 2019-2021 Lars Wirzenius, Daniel Silverstone
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Fork this project to create your own MIT license that you can always
+link to.
diff --git a/src/bin/pandoc-filter-diagram.rs b/src/bin/pandoc-filter-diagram.rs
new file mode 100644
index 0000000..80c8ba9
--- /dev/null
+++ b/src/bin/pandoc-filter-diagram.rs
@@ -0,0 +1,18 @@
+use pandoc_filter_diagram::DiagramFilter;
+use std::io::{Read, Write};
+
+fn main() {
+ if let Err(err) = real_main() {
+ eprintln!("ERROR: {}", err);
+ std::process::exit(1);
+ }
+}
+
+fn real_main() -> anyhow::Result<()> {
+ let mut df = DiagramFilter::new();
+ let mut json = String::new();
+ std::io::stdin().read_to_string(&mut json)?;
+ let json = pandoc_ast::filter(json, |doc| df.filter(doc));
+ std::io::stdout().write_all(json.as_bytes())?;
+ Ok(())
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..471a42e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,307 @@
+//! Render diagram markup in a Pandoc AST into SVG
+//!
+//! Process a Pandoc abstract syntax tree and convert fenced code
+//! blocks with diagram markup into embedded SVG images. Supported
+//! diagram markup languages:
+//!
+//! - [pikchr](https://pikchr.org/home/doc/trunk/homepage.md)
+//! - [Graphviz dot](https://graphviz.org/doc/info/lang.html)
+//! - [PlantUML](https://plantuml.com/)
+//! - [roadmap](https://gitlab.com/larswirzenius/roadmap)
+//! - [raw SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)
+//!
+//! The Pandoc AST has represents input as `Block` and other types of
+//! nodes. This crate turns `Block::CodeBlock` nodes that carry a
+//! class attribute that specifies one of the supported diagram markup
+//! languages into blocks with embedded SVG graphics.
+//!
+//! Note that this library does not do any parsing. The
+//! `pandoc_ast::filter` function does that.
+
+use pandoc_ast::{Block, Inline, MutVisitor, Pandoc};
+use std::env;
+use std::ffi::OsString;
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+
+/// Represent the diagram filter.
+#[derive(Debug)]
+pub struct DiagramFilter {
+ dot: PathBuf,
+ roadmap_width: usize,
+ java_path: PathBuf,
+ plantuml_jar: PathBuf,
+}
+
+/// Possible errors for diagram filtering.
+#[derive(Debug, thiserror::Error)]
+pub enum DiagramError {
+ /// When rendering a pikchr, something went wrong.
+ #[error("failure rendering pikchr diagram: {0}")]
+ PikchrRenderError(String),
+
+ /// When rendering a roadmap, something went wrong.
+ #[error("failure rendering roadmap diagram: {0}")]
+ Roadmap(#[from] roadmap::RoadmapError),
+
+ /// Failed to invoke a program.
+ ///
+ /// This Pandoc filter uses some helper programs to do some of its
+ /// work. It failed to invoke such a helper program.
+ #[error("failed to invoke helper program {0} (as {1}): {2}")]
+ InvokeFailed(String, PathBuf, String),
+
+ /// A helper program failed
+ ///
+ /// The filter uses some helper programs to implement some of its
+ /// functionality, for example the GraphViz dot program. This
+ /// error means that the helper program failed (exit code was not
+ /// zero).
+ ///
+ /// This probably implies there's something wrong in the filter.
+ /// Please report this error.
+ #[error("helper program failed: {0} (as {1}): {2}")]
+ HelperFailed(String, PathBuf, String, String),
+
+ /// I/O error
+ ///
+ /// The filter did some I/O, and it failed. This is a generic
+ /// wrapper for any kind of I/O error.
+ #[error(transparent)]
+ IoError(#[from] std::io::Error),
+}
+
+type Svg = Vec<u8>;
+
+impl DiagramFilter {
+ pub fn new() -> Self {
+ Self {
+ dot: PathBuf::from("dot"),
+ roadmap_width: 50,
+ java_path: PathBuf::from("java"),
+ plantuml_jar: PathBuf::from("/usr/share/plantuml/plantuml.jar"),
+ }
+ }
+
+ pub fn dot_path<P>(&mut self, path: P) -> &mut Self
+ where
+ P: AsRef<Path>,
+ {
+ self.dot = path.as_ref().to_path_buf();
+ self
+ }
+
+ pub fn java_path<P>(&mut self, path: P) -> &mut Self
+ where
+ P: AsRef<Path>,
+ {
+ self.java_path = path.as_ref().to_path_buf();
+ self
+ }
+
+ pub fn plantuml_jar<P>(&mut self, path: P) -> &mut Self
+ where
+ P: AsRef<Path>,
+ {
+ self.plantuml_jar = path.as_ref().to_path_buf();
+ self
+ }
+
+ pub fn roadmap_width(&mut self, w: usize) -> &mut Self {
+ self.roadmap_width = w;
+ self
+ }
+
+ pub fn filter(&mut self, mut doc: Pandoc) -> Pandoc {
+ self.walk_pandoc(&mut doc);
+ doc
+ }
+
+ fn error_block(&self, error: DiagramError) -> Block {
+ let msg = format!("ERROR: {}", error.to_string());
+ let msg = Inline::Str(String::from(msg));
+ let msg = vec![Inline::Strong(vec![msg])];
+ Block::Para(msg)
+ }
+
+ fn svg_block(&self, svg: &[u8]) -> Block {
+ let url = self.svg_as_data_url(svg);
+ let attr = ("".to_string(), vec![], vec![]);
+ let img = Inline::Image(attr, vec![], (url, "".to_string()));
+ Block::Para(vec![img])
+ }
+
+ fn svg_as_data_url(&self, svg: &[u8]) -> String {
+ let svg = base64::encode(&svg);
+ format!("data:image/svg+xml;base64,{}", svg)
+ }
+
+ fn pikchr_to_svg(&self, markup: &str, _class: Option<&str>) -> Result<Svg, DiagramError> {
+ let mut flags = pikchr::PikchrFlags::default();
+ flags.generate_plain_errors();
+ let image =
+ pikchr::Pikchr::render(markup, None, flags).map_err(DiagramError::PikchrRenderError)?;
+ Ok(image.as_bytes().to_vec())
+ }
+
+ fn dot_to_svg(&self, markup: &str) -> Result<Svg, DiagramError> {
+ filter_via(markup, "dot", &self.dot, &["-Tsvg"], None)
+ }
+
+ fn roadmap_to_svg(&self, markup: &str) -> Result<Svg, DiagramError> {
+ match roadmap::from_yaml(markup) {
+ Ok(ref mut roadmap) => {
+ roadmap.set_missing_statuses();
+ match roadmap.format_as_dot(self.roadmap_width) {
+ Ok(dot) => self.dot_to_svg(&dot),
+ Err(e) => Err(DiagramError::Roadmap(e)),
+ }
+ }
+ Err(err) => Err(DiagramError::Roadmap(err)),
+ }
+ }
+
+ fn plantuml_to_svg(&self, markup: &str) -> Result<Svg, DiagramError> {
+ let args = &[
+ "-Djava.awt.headless=true",
+ "-jar",
+ self.plantuml_jar.to_str().unwrap(),
+ "--",
+ "-pipe",
+ "-tsvg",
+ "-v",
+ "-graphvizdot",
+ self.dot.to_str().unwrap(),
+ ];
+ filter_via(markup, "java", &self.java_path, args, build_java_path())
+ }
+}
+
+// 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<OsString> {
+ 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()
+}
+
+fn filter_via(
+ markup: &str,
+ name: &str,
+ argv0: &Path,
+ args: &[&str],
+ cmdpath: Option<OsString>,
+) -> Result<Svg, DiagramError> {
+ // Start program without waiting for it to finish.
+ let mut cmd = Command::new(argv0);
+ cmd.args(args)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
+ if let Some(p) = cmdpath {
+ cmd.env("PATH", p);
+ }
+ let mut child = cmd.spawn()?;
+
+ if let Some(stdin) = child.stdin.as_mut() {
+ stdin.write_all(markup.as_bytes())?;
+ let output = child.wait_with_output()?;
+ if output.status.success() {
+ Ok(output.stdout)
+ } else {
+ let status = if let Some(code) = output.status.code() {
+ format!("{}", code)
+ } else {
+ format!("terminated by signal")
+ };
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ Err(DiagramError::HelperFailed(
+ name.to_string(),
+ argv0.to_path_buf(),
+ status,
+ stderr.into_owned(),
+ ))
+ }
+ } else {
+ // Something went wrong when we invoked the program. Read stderr.
+ let mut error_message = vec![];
+ if let Some(mut stderr) = child.stderr {
+ stderr.read_to_end(&mut error_message)?;
+ }
+ Err(DiagramError::InvokeFailed(
+ name.to_string(),
+ argv0.to_path_buf(),
+ String::from_utf8_lossy(&error_message).into_owned(),
+ ))
+ }
+}
+
+impl MutVisitor for DiagramFilter {
+ fn visit_block(&mut self, block: &mut Block) {
+ match block {
+ Block::CodeBlock((_id, classes, _kv), text) => match DiagramKind::from(&classes) {
+ DiagramKind::GraphvizDot => match self.dot_to_svg(text) {
+ Err(err) => *block = self.error_block(err),
+ Ok(svg) => *block = self.svg_block(&svg),
+ },
+ DiagramKind::Roadmap => match self.roadmap_to_svg(text) {
+ Err(err) => *block = self.error_block(err),
+ Ok(svg) => *block = self.svg_block(&svg),
+ },
+ DiagramKind::Plantuml => match self.plantuml_to_svg(text) {
+ Err(err) => *block = self.error_block(err),
+ Ok(svg) => *block = self.svg_block(&svg),
+ },
+ DiagramKind::Pikchr => match self.pikchr_to_svg(text, None) {
+ Err(err) => *block = self.error_block(err),
+ Ok(svg) => *block = self.svg_block(&svg),
+ },
+ DiagramKind::Svg => *block = self.svg_block(text.as_bytes()),
+ DiagramKind::Other => (),
+ },
+ _ => {
+ self.walk_block(block);
+ }
+ }
+ }
+}
+
+enum DiagramKind {
+ GraphvizDot,
+ Pikchr,
+ Plantuml,
+ Roadmap,
+ Svg,
+ Other,
+}
+
+impl DiagramKind {
+ fn from(classes: &Vec<String>) -> Self {
+ if has_class(classes, "pikchr") {
+ Self::Pikchr
+ } else if has_class(classes, "dot") {
+ Self::GraphvizDot
+ } else if has_class(classes, "roadmap") {
+ Self::Roadmap
+ } else if has_class(classes, "plantuml") {
+ Self::Plantuml
+ } else if has_class(classes, "svg") {
+ Self::Svg
+ } else {
+ Self::Other
+ }
+ }
+}
+
+fn has_class(classes: &Vec<String>, wanted: &str) -> bool {
+ classes.iter().any(|s| s == wanted)
+}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}