graphviz_rust/cmd.rs
1//! Utilities for executing the [`dot` command line executable].
2//!
3//! *Important*: users should have the `dot` command line executable installed.
4//! A download can be found here: <https://graphviz.org/download/>.
5//!
6//! Additional information on controlling the output can be found in the `graphviz`
7//! docs on [layouts] and [output formats].
8//!
9//! [layouts]: https://graphviz.org/docs/layouts/
10//! [output formats]:https://graphviz.org/docs/outputs/
11//! # Example:
12//! ```no_run
13//! use dot_structures::*;
14//! use dot_generator::*;
15//! use graphviz_rust::attributes::*;
16//! use graphviz_rust::cmd::{CommandArg, Format};
17//! use graphviz_rust::exec;
18//! use graphviz_rust::printer::{PrinterContext,DotPrinter};
19//!
20//! fn graph_to_output(){
21//! let mut g = graph!(id!("id");
22//! node!("nod"),
23//! subgraph!("sb";
24//! edge!(node_id!("a") => subgraph!(;
25//! node!("n";
26//! NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg))
27//! ))
28//! ),
29//! edge!(node_id!("a1") => node_id!(esc "a2"))
30//! );
31//! let graph_svg = exec(g, &mut PrinterContext::default(), vec![
32//! CommandArg::Format(Format::Svg),
33//! ]).unwrap();
34//! }
35//!
36//! fn graph_to_file(){
37//! let mut g = graph!(id!("id"));
38//! let mut ctx = PrinterContext::default();
39//! ctx.always_inline();
40//! let empty = exec(g, &mut ctx, vec![
41//! CommandArg::Format(Format::Svg),
42//! CommandArg::Output("1.svg".to_string())
43//! ]);
44//! }
45//! ```
46//!
47//! [`dot` command line executable]: https://graphviz.org/doc/info/command.html
48use std::{
49 io::{self, ErrorKind, Write},
50 process::{Command, Output},
51};
52
53use tempfile::NamedTempFile;
54
55pub(crate) fn exec(graph: String, args: Vec<CommandArg>) -> io::Result<Vec<u8>> {
56 let args = args.into_iter().map(|a| a.prepare()).collect();
57 temp_file(graph).and_then(|f| {
58 let path = f.into_temp_path();
59 do_exec(path.to_string_lossy().to_string(), args).and_then(|o| {
60 if o.status.code().map(|c| c != 0).unwrap_or(true) {
61 let mes = String::from_utf8_lossy(&o.stderr).to_string();
62 path.close()?;
63 Err(std::io::Error::new(ErrorKind::Other, mes))
64 } else {
65 path.close()?;
66 Ok(o.stdout)
67 }
68 })
69 })
70}
71
72fn do_exec(input: String, args: Vec<String>) -> io::Result<Output> {
73 let mut command = Command::new("dot");
74
75 for arg in args {
76 command.arg(arg);
77 }
78 command.arg(input).output()
79}
80
81fn temp_file(ctx: String) -> io::Result<NamedTempFile> {
82 let mut file = NamedTempFile::new()?;
83 file.write_all(ctx.as_bytes()).map(|_x| file)
84}
85
86/// Commandline arguments that can be passed to executable.
87///
88/// The list of possible commands can be found here:
89/// <https://graphviz.org/doc/info/command.html>.
90pub enum CommandArg {
91 /// any custom argument.
92 ///
93 /// _Note_: it does not manage any prefixes and thus '-' or the prefix must
94 /// be passed as well.
95 Custom(String),
96 /// Regulates the output file with -o prefix
97 Output(String),
98 /// [`Layouts`] in cmd
99 ///
100 /// [`Layouts`]: https://graphviz.org/docs/layouts/
101 Layout(Layout),
102 /// [`Output`] formats in cmd
103 ///
104 /// [`Output`]:https://graphviz.org/docs/outputs/
105 Format(Format),
106}
107
108impl From<Layout> for CommandArg {
109 fn from(value: Layout) -> Self {
110 CommandArg::Layout(value)
111 }
112}
113
114impl From<Format> for CommandArg {
115 fn from(value: Format) -> Self {
116 CommandArg::Format(value)
117 }
118}
119
120impl CommandArg {
121 fn prepare(&self) -> String {
122 match self {
123 CommandArg::Custom(s) => s.clone(),
124 CommandArg::Output(p) => format!("-o{}", p),
125 CommandArg::Layout(l) => format!("-K{}", format!("{:?}", l).to_lowercase()),
126 CommandArg::Format(f) => {
127 let str = match f {
128 Format::Xdot12 => "xdot1.2".to_string(),
129 Format::Xdot14 => "xdot1.4".to_string(),
130 Format::ImapNp => "imap_np".to_string(),
131 Format::CmapxNp => "cmapx_np".to_string(),
132 Format::DotJson => "dot_json".to_string(),
133 Format::XdotJson => "xdot_json".to_string(),
134 Format::PlainExt => "plain-ext".to_string(),
135 _ => format!("{:?}", f).to_lowercase(),
136 };
137 format!("-T{}", str)
138 }
139 }
140 }
141}
142
143/// Various algorithms for projecting abstract graphs into a space for
144/// visualization
145///
146/// <https://graphviz.org/docs/layouts/>
147#[derive(Debug, Copy, Clone)]
148pub enum Layout {
149 Dot,
150 Neato,
151 Twopi,
152 Circo,
153 Fdp,
154 Asage,
155 Patchwork,
156 Sfdp,
157}
158
159/// Various graphic and data formats for end user, web, documents and other
160/// applications.
161///
162/// <https://graphviz.org/docs/outputs/>
163#[derive(Debug, Copy, Clone)]
164pub enum Format {
165 Bmp,
166 Cgimage,
167 Canon,
168 Dot,
169 Gv,
170 Xdot,
171 Xdot12,
172 Xdot14,
173 Eps,
174 Exr,
175 Fig,
176 Gd,
177 Gd2,
178 Gif,
179 Gtk,
180 Ico,
181 Cmap,
182 Ismap,
183 Imap,
184 Cmapx,
185 ImapNp,
186 CmapxNp,
187 Jpg,
188 Jpeg,
189 Jpe,
190 Jp2,
191 Json,
192 Json0,
193 DotJson,
194 XdotJson,
195 Pdf,
196 Pic,
197 Pct,
198 Pict,
199 Plain,
200 PlainExt,
201 Png,
202 Pov,
203 Ps,
204 Ps2,
205 Psd,
206 Sgi,
207 Svg,
208 Svgz,
209 Tga,
210 Tif,
211 Tiff,
212 Tk,
213 Vml,
214 Vmlz,
215 Vrml,
216 Vbmp,
217 Webp,
218 Xlib,
219 X11,
220}
221
222#[cfg(test)]
223mod tests {
224 use dot_generator::*;
225 use dot_structures::*;
226
227 use crate::printer::{DotPrinter, PrinterContext};
228
229 use super::{exec, CommandArg, Format};
230
231 #[test]
232 fn error_test() {
233 let g = graph!(id!("id"));
234 let mut ctx = PrinterContext::default();
235 ctx.always_inline();
236 let empty = exec(
237 g.print(&mut ctx),
238 vec![
239 Format::Svg.into(),
240 CommandArg::Output("missing/1.svg".to_string()),
241 ],
242 );
243 assert!(empty.is_err())
244 }
245}