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.path().to_string_lossy().to_string();
59 do_exec(path, 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 Err(std::io::Error::new(ErrorKind::Other, mes))
63 } else {
64 Ok(o.stdout)
65 }
66 })
67 })
68}
69
70fn do_exec(input: String, args: Vec<String>) -> io::Result<Output> {
71 let mut command = Command::new("dot");
72
73 for arg in args {
74 command.arg(arg);
75 }
76 command.arg(input).output()
77}
78
79fn temp_file(ctx: String) -> io::Result<NamedTempFile> {
80 let mut file = NamedTempFile::new()?;
81 file.write_all(ctx.as_bytes()).map(|_x| file)
82}
83
84/// Commandline arguments that can be passed to executable.
85///
86/// The list of possible commands can be found here:
87/// <https://graphviz.org/doc/info/command.html>.
88pub enum CommandArg {
89 /// any custom argument.
90 ///
91 /// _Note_: it does not manage any prefixes and thus '-' or the prefix must
92 /// be passed as well.
93 Custom(String),
94 /// Regulates the output file with -o prefix
95 Output(String),
96 /// [`Layouts`] in cmd
97 ///
98 /// [`Layouts`]: https://graphviz.org/docs/layouts/
99 Layout(Layout),
100 /// [`Output`] formats in cmd
101 ///
102 /// [`Output`]:https://graphviz.org/docs/outputs/
103 Format(Format),
104}
105
106impl From<Layout> for CommandArg {
107 fn from(value: Layout) -> Self {
108 CommandArg::Layout(value)
109 }
110}
111
112impl From<Format> for CommandArg {
113 fn from(value: Format) -> Self {
114 CommandArg::Format(value)
115 }
116}
117
118impl CommandArg {
119 fn prepare(&self) -> String {
120 match self {
121 CommandArg::Custom(s) => s.clone(),
122 CommandArg::Output(p) => format!("-o{}", p),
123 CommandArg::Layout(l) => format!("-K{}", format!("{:?}", l).to_lowercase()),
124 CommandArg::Format(f) => {
125 let str = match f {
126 Format::Xdot12 => "xdot1.2".to_string(),
127 Format::Xdot14 => "xdot1.4".to_string(),
128 Format::ImapNp => "imap_np".to_string(),
129 Format::CmapxNp => "cmapx_np".to_string(),
130 Format::DotJson => "dot_json".to_string(),
131 Format::XdotJson => "xdot_json".to_string(),
132 Format::PlainExt => "plain-ext".to_string(),
133 _ => format!("{:?}", f).to_lowercase(),
134 };
135 format!("-T{}", str)
136 }
137 }
138 }
139}
140
141/// Various algorithms for projecting abstract graphs into a space for
142/// visualization
143///
144/// <https://graphviz.org/docs/layouts/>
145#[derive(Debug, Copy, Clone)]
146pub enum Layout {
147 Dot,
148 Neato,
149 Twopi,
150 Circo,
151 Fdp,
152 Asage,
153 Patchwork,
154 Sfdp,
155}
156
157/// Various graphic and data formats for end user, web, documents and other
158/// applications.
159///
160/// <https://graphviz.org/docs/outputs/>
161#[derive(Debug, Copy, Clone)]
162pub enum Format {
163 Bmp,
164 Cgimage,
165 Canon,
166 Dot,
167 Gv,
168 Xdot,
169 Xdot12,
170 Xdot14,
171 Eps,
172 Exr,
173 Fig,
174 Gd,
175 Gd2,
176 Gif,
177 Gtk,
178 Ico,
179 Cmap,
180 Ismap,
181 Imap,
182 Cmapx,
183 ImapNp,
184 CmapxNp,
185 Jpg,
186 Jpeg,
187 Jpe,
188 Jp2,
189 Json,
190 Json0,
191 DotJson,
192 XdotJson,
193 Pdf,
194 Pic,
195 Pct,
196 Pict,
197 Plain,
198 PlainExt,
199 Png,
200 Pov,
201 Ps,
202 Ps2,
203 Psd,
204 Sgi,
205 Svg,
206 Svgz,
207 Tga,
208 Tif,
209 Tiff,
210 Tk,
211 Vml,
212 Vmlz,
213 Vrml,
214 Vbmp,
215 Webp,
216 Xlib,
217 X11,
218}
219
220#[cfg(test)]
221mod tests {
222 use dot_generator::*;
223 use dot_structures::*;
224
225 use crate::printer::{DotPrinter, PrinterContext};
226
227 use super::{exec, CommandArg, Format};
228
229 #[test]
230 fn error_test() {
231 let g = graph!(id!("id"));
232 let mut ctx = PrinterContext::default();
233 ctx.always_inline();
234 let empty = exec(
235 g.print(&mut ctx),
236 vec![
237 Format::Svg.into(),
238 CommandArg::Output("missing/1.svg".to_string()),
239 ],
240 );
241 assert!(empty.is_err())
242 }
243}