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}