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}