dot_writer/
writer.rs

1use std::io::Write;
2
3use super::scope::Scope;
4
5const INDENT_STEP: usize = 2;
6
7/// The entry point struct for writing a DOT graph.
8/// See the examples on the index for how to use it.
9/// This struct must live for the livetime of writing the graph,
10/// and also outlive the [`Write`] struct it points to,
11/// because it's [`Drop`] trait will finish writing graph.
12pub struct DotWriter<'w> {
13    writer: &'w mut dyn Write,
14    indent: usize,
15    next_id: usize,
16    digraph: bool,
17    pretty_print: bool,
18}
19
20/// This is the only constructor, which requires a [`Write`]
21/// to borrow and write the DOT output to.
22/// Defaults to pretty printing the output (see [`DotWriter::set_pretty_print`]).
23impl<'w, W: Write> From<&'w mut W> for DotWriter<'w> {
24    fn from(writer: &'w mut W) -> Self {
25        Self {
26            writer,
27            indent: 0,
28            next_id: 0,
29            digraph: false,
30            pretty_print: true,
31        }
32    }
33}
34
35impl<'w> DotWriter<'w> {
36    /// If set to true, then output will have additional whitespace
37    /// including newlines and indentation to make the output more
38    /// human friendly. If false then this will be left out and the output
39    /// will be more compact. Defaults to true. For example disabled:
40    ///
41    /// ```
42    /// use dot_writer::DotWriter;
43    ///
44    /// let mut bytes = Vec::new();
45    /// let mut writer = DotWriter::from(&mut bytes);
46    /// writer.set_pretty_print(false);
47    /// writer.graph().subgraph().cluster().subgraph();
48    /// println!("{}", std::str::from_utf8(&bytes).unwrap());
49    /// ```
50    ///
51    /// Gives you:
52    ///
53    /// `
54    /// graph {subgraph {subgraph cluster_0 {subgraph {}}}}
55    /// `
56    ///
57    /// While enabled:
58    ///
59    /// ```
60    /// use dot_writer::DotWriter;
61    ///
62    /// let mut bytes = Vec::new();
63    /// let mut writer = DotWriter::from(&mut bytes);
64    /// // writer.set_pretty_print(false); defaults to true anyway
65    /// writer.graph().subgraph().cluster().subgraph();
66    /// println!("{}", std::str::from_utf8(&bytes).unwrap());
67    /// ```
68    ///
69    /// Gives you:
70    ///
71    /// ```txt
72    /// graph {
73    ///   subgraph {
74    ///     subgraph cluster_0 {
75    ///       subgraph {}
76    ///     }
77    ///   }
78    /// }
79    ///```
80    pub fn set_pretty_print(&mut self, pretty_print: bool) {
81        self.pretty_print = pretty_print;
82    }
83
84    /// Start a new undirection graph. This uses the edge operator `--`
85    /// automatically, which means edges should be rendered without any
86    /// arrowheads by default.    
87    /// ```
88    /// use dot_writer::DotWriter;
89    ///
90    /// let mut bytes = Vec::new();
91    /// let mut writer = DotWriter::from(&mut bytes);
92    /// writer.set_pretty_print(false);
93    /// writer.graph().edge("a", "b");
94    /// assert_eq!(
95    ///     std::str::from_utf8(&bytes).unwrap(),
96    ///     "graph{a--b;}"
97    /// );
98    /// ```
99    pub fn graph(&mut self) -> Scope<'_, 'w> {
100        self.digraph = false;
101        Scope::new(self, b"graph")
102    }
103
104    /// Start a new directed graph. This uses the edge operator `->`
105    /// automatically, which means edges should be redered with an
106    /// arrow head pointing to the head (end) node.    
107    /// ```
108    /// use dot_writer::DotWriter;
109    ///
110    /// let mut bytes = Vec::new();
111    /// let mut writer = DotWriter::from(&mut bytes);
112    /// writer.set_pretty_print(false);
113    /// writer.digraph().edge("a", "b");
114    /// assert_eq!(
115    ///     std::str::from_utf8(&bytes).unwrap(),
116    ///     "digraph{a->b;}"
117    /// );
118    /// ```
119    pub fn digraph(&mut self) -> Scope<'_, 'w> {
120        self.digraph = true;
121        Scope::new(self, b"digraph")
122    }
123
124    /// Uses a callback to write DOT to a [`String`]. This is useful
125    /// if you just want to write your dot code to a string rather than
126    /// a file or stdout, and want less boiler plate setup code.
127    /// It's used internally for unit testing, so the output is not
128    /// pretty printed by default (but you can overwrite that by calling
129    /// [`DotWriter::set_pretty_print`] from within the callback).
130    ///
131    /// ```
132    /// use dot_writer::DotWriter;
133    ///
134    /// let output = DotWriter::write_string(|writer| {
135    ///     let mut graph = writer.graph();
136    ///     graph.edge("a", "b");
137    /// });
138    /// assert_eq!(output, "graph{a--b;}");
139    /// ```
140    pub fn write_string<F: Fn(&mut DotWriter)>(builder: F) -> String {
141        let mut bytes = Vec::new();
142        let mut writer = DotWriter::from(&mut bytes);
143        writer.set_pretty_print(false);
144        (builder)(&mut writer);
145        String::from_utf8(bytes).unwrap()
146    }
147}
148
149impl<'w> DotWriter<'w> {
150    pub(crate) fn write(&mut self, bytes: &[u8]) {
151        if self.pretty_print {
152            self.writer.write_all(bytes).unwrap();
153        } else {
154            for b in bytes.iter().filter(|b| !b.is_ascii_whitespace()) {
155                self.writer.write_all(&[*b]).unwrap();
156            }
157        }
158    }
159
160    pub(crate) fn write_with_whitespace(&mut self, bytes: &[u8]) {
161        self.writer.write_all(bytes).unwrap();
162    }
163
164    pub(crate) fn write_quoted(&mut self, bytes: &[u8]) {
165        self.writer.write_all(b"\"").unwrap();
166        self.write_with_whitespace(bytes);
167        self.writer.write_all(b"\"").unwrap();
168    }
169
170    pub(crate) fn write_indent(&mut self) {
171        for _ in 0..self.indent {
172            self.writer.write_all(b" ").unwrap();
173        }
174    }
175
176    pub(crate) fn write_edge_operator(&mut self) {
177        match self.digraph {
178            true => self.write(b" -> "),
179            false => self.write(b" -- "),
180        }
181    }
182
183    pub(crate) fn indent(&mut self) {
184        if self.pretty_print {
185            self.indent += INDENT_STEP;
186        }
187    }
188
189    pub(crate) fn unindent(&mut self) {
190        if self.pretty_print {
191            self.indent -= INDENT_STEP;
192        }
193    }
194
195    pub(crate) fn next_id(&mut self) -> usize {
196        let next_id = self.next_id;
197        self.next_id += 1;
198        next_id
199    }
200}
201
202//
203// Statement
204//
205
206pub(crate) struct Statement<'d, 'w> {
207    line: Line<'d, 'w>,
208}
209
210impl<'d, 'w> Statement<'d, 'w> {
211    pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self {
212        Self {
213            line: Line::new(writer),
214        }
215    }
216}
217
218impl<'d, 'w> std::ops::DerefMut for Statement<'d, 'w> {
219    fn deref_mut(&mut self) -> &mut DotWriter<'w> {
220        &mut *self.line
221    }
222}
223
224impl<'d, 'w> std::ops::Deref for Statement<'d, 'w> {
225    type Target = DotWriter<'w>;
226    fn deref(&self) -> &DotWriter<'w> {
227        &*self.line
228    }
229}
230
231impl<'d, 'w> Drop for Statement<'d, 'w> {
232    fn drop(&mut self) {
233        self.line.write(b";");
234    }
235}
236
237//
238// Line
239//
240
241pub(crate) struct Line<'d, 'w> {
242    writer: &'d mut DotWriter<'w>,
243}
244
245impl<'d, 'w> Line<'d, 'w> {
246    pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self {
247        writer.write_indent();
248        Self { writer }
249    }
250}
251
252impl<'d, 'w> std::ops::DerefMut for Line<'d, 'w> {
253    fn deref_mut(&mut self) -> &mut DotWriter<'w> {
254        self.writer
255    }
256}
257
258impl<'d, 'w> std::ops::Deref for Line<'d, 'w> {
259    type Target = DotWriter<'w>;
260    fn deref(&self) -> &DotWriter<'w> {
261        self.writer
262    }
263}
264
265impl<'a, 'b: 'a> Drop for Line<'a, 'b> {
266    fn drop(&mut self) {
267        if self.writer.pretty_print {
268            self.writer.write(b"\n");
269        }
270    }
271}