use std::{
io::{self, ErrorKind, Write},
process::{Command, Output},
};
use tempfile::NamedTempFile;
pub(crate) fn exec(graph: String, args: Vec<CommandArg>) -> io::Result<Vec<u8>> {
let args = args.into_iter().map(|a| a.prepare()).collect();
temp_file(graph).and_then(|f| {
let path = f.into_temp_path();
do_exec(path.to_string_lossy().to_string(), args).and_then(|o| {
if o.status.code().map(|c| c != 0).unwrap_or(true) {
let mes = String::from_utf8_lossy(&o.stderr).to_string();
path.close()?;
Err(std::io::Error::new(ErrorKind::Other, mes))
} else {
path.close()?;
Ok(o.stdout)
}
})
})
}
fn do_exec(input: String, args: Vec<String>) -> io::Result<Output> {
let mut command = Command::new("dot");
for arg in args {
command.arg(arg);
}
command.arg(input).output()
}
fn temp_file(ctx: String) -> io::Result<NamedTempFile> {
let mut file = NamedTempFile::new()?;
file.write_all(ctx.as_bytes()).map(|_x| file)
}
pub enum CommandArg {
Custom(String),
Output(String),
Layout(Layout),
Format(Format),
}
impl From<Layout> for CommandArg {
fn from(value: Layout) -> Self {
CommandArg::Layout(value)
}
}
impl From<Format> for CommandArg {
fn from(value: Format) -> Self {
CommandArg::Format(value)
}
}
impl CommandArg {
fn prepare(&self) -> String {
match self {
CommandArg::Custom(s) => s.clone(),
CommandArg::Output(p) => format!("-o{}", p),
CommandArg::Layout(l) => format!("-K{}", format!("{:?}", l).to_lowercase()),
CommandArg::Format(f) => {
let str = match f {
Format::Xdot12 => "xdot1.2".to_string(),
Format::Xdot14 => "xdot1.4".to_string(),
Format::ImapNp => "imap_np".to_string(),
Format::CmapxNp => "cmapx_np".to_string(),
Format::DotJson => "dot_json".to_string(),
Format::XdotJson => "xdot_json".to_string(),
Format::PlainExt => "plain-ext".to_string(),
_ => format!("{:?}", f).to_lowercase(),
};
format!("-T{}", str)
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Layout {
Dot,
Neato,
Twopi,
Circo,
Fdp,
Asage,
Patchwork,
Sfdp,
}
#[derive(Debug, Copy, Clone)]
pub enum Format {
Bmp,
Cgimage,
Canon,
Dot,
Gv,
Xdot,
Xdot12,
Xdot14,
Eps,
Exr,
Fig,
Gd,
Gd2,
Gif,
Gtk,
Ico,
Cmap,
Ismap,
Imap,
Cmapx,
ImapNp,
CmapxNp,
Jpg,
Jpeg,
Jpe,
Jp2,
Json,
Json0,
DotJson,
XdotJson,
Pdf,
Pic,
Pct,
Pict,
Plain,
PlainExt,
Png,
Pov,
Ps,
Ps2,
Psd,
Sgi,
Svg,
Svgz,
Tga,
Tif,
Tiff,
Tk,
Vml,
Vmlz,
Vrml,
Vbmp,
Webp,
Xlib,
X11,
}
#[cfg(test)]
mod tests {
use dot_generator::*;
use dot_structures::*;
use crate::printer::{DotPrinter, PrinterContext};
use super::{exec, CommandArg, Format};
#[test]
fn error_test() {
let g = graph!(id!("id"));
let mut ctx = PrinterContext::default();
ctx.always_inline();
let empty = exec(
g.print(&mut ctx),
vec![
Format::Svg.into(),
CommandArg::Output("missing/1.svg".to_string()),
],
);
assert!(empty.is_err())
}
}