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;
#[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<PathBuf>,
#[structopt(
long = "plantuml-jar",
help = "Path to the `plantuml.jar` file.",
name = "PLANTUMLJARPATH",
env = "SUBPLOT_PLANTUML_JAR_PATH"
)]
plantuml_jar_path: Option<PathBuf>,
#[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<PathBuf>,
}
impl MarkupOpts {
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<PathBuf> = Mutex::new(env!("BUILTIN_DOT_PATH").into());
static ref PLANTUML_JAR_PATH: Mutex<PathBuf> =
Mutex::new(env!("BUILTIN_PLANTUML_JAR_PATH").into());
static ref JAVA_PATH: Mutex<PathBuf> = Mutex::new(env!("BUILTIN_JAVA_PATH").into());
}
pub trait GraphMarkup {
fn as_svg(&self) -> Result<Vec<u8>>;
}
pub struct PikchrMarkup {
markup: String,
class: Option<String>,
}
impl PikchrMarkup {
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<Vec<u8>> {
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())
}
}
pub struct DotMarkup {
markup: String,
}
impl DotMarkup {
pub fn new(markup: &str) -> DotMarkup {
DotMarkup {
markup: markup.to_owned(),
}
}
}
impl GraphMarkup for DotMarkup {
fn as_svg(&self) -> Result<Vec<u8>> {
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)
}
}
}
pub struct PlantumlMarkup {
markup: String,
}
impl PlantumlMarkup {
pub fn new(markup: &str) -> PlantumlMarkup {
PlantumlMarkup {
markup: markup.to_owned(),
}
}
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) {
return None;
}
env::join_paths(Some(java_bin).iter().chain(cur_path.iter())).ok()
}
}
impl GraphMarkup for PlantumlMarkup {
fn as_svg(&self) -> Result<Vec<u8>> {
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)
}
}
}