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}