graphviz_rust/
printer.rs

1//! Serialize a [Graph] into a string according to the [`graphviz` DOT language].
2//!
3//! # Example:
4//! ```rust
5//!     use dot_generator::*;
6//!     use dot_structures::*;
7//!     use graphviz_rust::printer::{PrinterContext,DotPrinter};
8//!         let mut ctx = PrinterContext::default();
9//!         let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b")));
10//!         assert_eq!(s.print(&mut ctx), "subgraph id {\n  abc\n  a -- b\n}".to_string());
11//! ```
12//!
13//! [`graphviz` DOT language]: https://graphviz.org/doc/info/lang.html
14use std::collections::HashMap;
15
16use dot_structures::{
17    Attribute, Edge, EdgeTy, Graph, GraphAttributes, Id, Node, NodeId, Port, Stmt, Subgraph, Vertex,
18};
19
20/// A function which can be passed to the [PrinterContext] to provide custom printing for attribute values.
21///
22/// # Example:
23/// ```rust
24/// use dot_generator::*;
25/// use dot_structures::*;
26/// use graphviz_rust::printer::{AttributeValuePrinter, DotPrinter, PrinterContext};
27/// fn attr_formatter_test() {
28///     let mut ctx = PrinterContext::default();
29///     let formatter: Box<AttributeValuePrinter> =
30///         Box::new(|value, _line_sep, _indent, _indent_step| format!(r#""**{}**""#, value.trim_matches('"')));
31///     let g = graph!(di id!("attr_formatting");
32///          node!("abc";attr!("custom",esc "Custom Text")),
33///          edge!(node_id!("a") => node_id!("b"))
34///     );
35///     assert_eq!(
36///         "digraph attr_formatting {\n  abc[custom=\"**Custom Text**\"]\n  a -> b\n}",
37///         g.print(
38///             ctx.with_node_mult_attr_s_l()
39///                 .with_attribute_formatter(id!("custom"), formatter)
40///         )
41///     );
42/// }
43/// ```
44pub type AttributeValuePrinter = dyn Fn(&str, &str, &str, usize) -> String;
45
46/// Context allows to customize the output of the file.
47///
48/// # Example:
49/// ```rust
50///     use self::graphviz_rust::printer::PrinterContext;
51///
52///     let mut ctx = PrinterContext::default();
53///     ctx.always_inline();
54///     ctx.with_indent_step(4);
55/// ```
56pub struct PrinterContext {
57    /// internal flag which is decoupled from the graph
58    is_digraph: bool,
59    /// a flag adds a semicolon at the end of the line
60    semi: bool,
61    /// a flag, print multiple node attributes on seperate lines
62    mult_node_attr_on_s_l: bool,
63    /// an initial indent. 0 by default
64    indent: usize,
65    /// a step of the indent. 2 by default
66    indent_step: usize,
67    /// a line separator. can be empty
68    l_s: String,
69    /// a len of the text to keep on one line
70    inline_size: usize,
71    l_s_i: String,
72    l_s_m: String,
73    /// a map of attribute id to AttributeValuePrinters
74    attr_value_printers: HashMap<Id, Box<AttributeValuePrinter>>,
75}
76
77impl PrinterContext {
78    /// Print everything on one line.
79    pub fn always_inline(&mut self) -> &mut PrinterContext {
80        self.l_s_m = self.l_s_i.clone();
81        self.l_s = self.l_s_i.clone();
82        self
83    }
84    /// Add a semicolon at the end of every line.
85    pub fn with_semi(&mut self) -> &mut PrinterContext {
86        self.semi = true;
87        self
88    }
89    /// Print multiple attributes on seperate lines
90    pub fn with_node_mult_attr_s_l(&mut self) -> &mut PrinterContext {
91        self.mult_node_attr_on_s_l = true;
92        self
93    }
94    /// Set a step of the indent.
95    pub fn with_indent_step(&mut self, step: usize) -> &mut PrinterContext {
96        self.indent_step = step;
97        self
98    }
99    /// Set a specific line separator.
100    pub fn with_line_sep(&mut self, sep: String) -> &mut PrinterContext {
101        self.l_s = sep.clone();
102        self.l_s_m = sep;
103        self
104    }
105    /// Set the max line length.
106    ///
107    /// The default value is 90.
108    pub fn with_inline_size(&mut self, inline_s: usize) -> &mut PrinterContext {
109        self.inline_size = inline_s;
110        self
111    }
112    /// Add an attribute printer for a specific attribute id.
113    pub fn with_attr_value_printer(
114        &mut self,
115        attr_id: Id,
116        fmt: Box<AttributeValuePrinter>,
117    ) -> &mut PrinterContext {
118        self.attr_value_printers.insert(attr_id, fmt);
119        self
120    }
121
122    pub fn new(semi: bool, indent_step: usize, line_s: String, inline_size: usize) -> Self {
123        PrinterContext {
124            is_digraph: false,
125            semi,
126            mult_node_attr_on_s_l: false,
127            indent: 0,
128            indent_step,
129            inline_size,
130            l_s: line_s.clone(),
131            l_s_i: line_s,
132            l_s_m: "".to_string(),
133            attr_value_printers: HashMap::new(),
134        }
135    }
136}
137
138impl PrinterContext {
139    fn indent(&self) -> String {
140        if self.is_inline_on() {
141            "".to_string()
142        } else {
143            " ".repeat(self.indent)
144        }
145    }
146    fn indent_grow(&mut self) {
147        if !self.is_inline_on() {
148            self.indent += self.indent_step
149        }
150    }
151    fn indent_shrink(&mut self) {
152        if !self.is_inline_on() {
153            self.indent -= self.indent_step
154        }
155    }
156
157    fn is_inline_on(&self) -> bool {
158        self.l_s == self.l_s_i
159    }
160    fn inline_mode(&mut self) {
161        self.l_s = self.l_s_i.clone()
162    }
163    fn multiline_mode(&mut self) {
164        self.l_s = self.l_s_m.clone()
165    }
166}
167
168impl Default for PrinterContext {
169    fn default() -> Self {
170        PrinterContext {
171            is_digraph: false,
172            mult_node_attr_on_s_l: false,
173            semi: false,
174            indent: 0,
175            indent_step: 2,
176            l_s: "\n".to_string(),
177            inline_size: 90,
178            l_s_i: "".to_string(),
179            l_s_m: "\n".to_string(),
180            attr_value_printers: HashMap::new(),
181        }
182    }
183}
184
185/// The trait for serailizing a [Graph] into the `graphviz` DOT language:
186///
187/// # Example:
188///  ```rust
189///         use dot_generator::*;
190///         use dot_structures::*;
191///         use self::graphviz_rust::printer::PrinterContext;
192///         use self::graphviz_rust::printer::DotPrinter;
193///
194///         let mut ctx =PrinterContext::default();
195///         ctx.always_inline();
196///         ctx.with_indent_step(4);
197///         let graph = graph!(strict di id!("t"));
198///
199///         let string = graph.print(&mut ctx);
200/// ```
201pub trait DotPrinter {
202    fn print(&self, ctx: &mut PrinterContext) -> String;
203}
204
205impl DotPrinter for Id {
206    fn print(&self, _ctx: &mut PrinterContext) -> String {
207        match self {
208            Id::Html(v) | Id::Escaped(v) | Id::Plain(v) => v.clone(),
209            Id::Anonymous(_) => "".to_string(),
210        }
211    }
212}
213
214impl DotPrinter for Port {
215    fn print(&self, ctx: &mut PrinterContext) -> String {
216        match self {
217            Port(Some(id), Some(d)) => format!(":{}:{}", id.print(ctx), d),
218            Port(None, Some(d)) => format!(":{}", d),
219            Port(Some(id), None) => format!(":{}", id.print(ctx)),
220            _ => unreachable!(""),
221        }
222    }
223}
224
225impl DotPrinter for NodeId {
226    fn print(&self, ctx: &mut PrinterContext) -> String {
227        match self {
228            NodeId(id, None) => id.print(ctx),
229            NodeId(id, Some(port)) => [id.print(ctx), port.print(ctx)].join(""),
230        }
231    }
232}
233
234impl DotPrinter for Attribute {
235    fn print(&self, ctx: &mut PrinterContext) -> String {
236        match self {
237            Attribute(l, r) => {
238                let l_val = l.print(ctx);
239                let r_val = r.print(ctx);
240                if let Some(formatter) = ctx.attr_value_printers.get(l) {
241                    format!(
242                        "{}={}",
243                        l_val,
244                        formatter(&r_val, &ctx.l_s, &ctx.indent(), ctx.indent_step)
245                    )
246                } else {
247                    format!("{}={}", l_val, r_val)
248                }
249            }
250        }
251    }
252}
253
254impl DotPrinter for Vec<Attribute> {
255    fn print(&self, ctx: &mut PrinterContext) -> String {
256        let attrs: Vec<String> = self.iter().map(|e| e.print(ctx)).collect();
257        if attrs.is_empty() {
258            "".to_string()
259        } else if attrs.len() > 1 && ctx.mult_node_attr_on_s_l {
260            let indent = ctx.indent();
261            ctx.indent_grow();
262            let r = format!(
263                "[{}{}{}{}{}]",
264                ctx.l_s,
265                ctx.indent(),
266                attrs.join(&format!(",{}{}", ctx.l_s, ctx.indent())),
267                ctx.l_s,
268                indent,
269            );
270            ctx.indent_shrink();
271            r
272        } else {
273            format!("[{}]", attrs.join(","))
274        }
275    }
276}
277
278impl DotPrinter for GraphAttributes {
279    fn print(&self, ctx: &mut PrinterContext) -> String {
280        match self {
281            GraphAttributes::Graph(attrs) => format!("graph{}", attrs.print(ctx)),
282            GraphAttributes::Node(attrs) => format!("node{}", attrs.print(ctx)),
283            GraphAttributes::Edge(attrs) => format!("edge{}", attrs.print(ctx)),
284        }
285    }
286}
287
288impl DotPrinter for Node {
289    fn print(&self, ctx: &mut PrinterContext) -> String {
290        format!("{}{}", self.id.print(ctx), self.attributes.print(ctx))
291    }
292}
293
294impl DotPrinter for Vertex {
295    fn print(&self, ctx: &mut PrinterContext) -> String {
296        match self {
297            Vertex::N(el) => el.print(ctx),
298            Vertex::S(el) => el.print(ctx),
299        }
300    }
301}
302
303impl DotPrinter for Subgraph {
304    fn print(&self, ctx: &mut PrinterContext) -> String {
305        let indent = ctx.indent();
306        ctx.indent_grow();
307        let header = format!("subgraph {} {{{}", self.id.print(ctx), ctx.l_s);
308        let r = format!("{}{}{}{}}}", header, self.stmts.print(ctx), ctx.l_s, indent);
309        ctx.indent_shrink();
310        r
311    }
312}
313
314impl DotPrinter for Graph {
315    fn print(&self, ctx: &mut PrinterContext) -> String {
316        ctx.indent_grow();
317
318        match self {
319            Graph::Graph { id, strict, stmts } if *strict => {
320                ctx.is_digraph = false;
321                let body = stmts.print(ctx);
322                format!(
323                    "strict graph {} {{{}{}{}}}",
324                    id.print(ctx),
325                    ctx.l_s,
326                    body,
327                    ctx.l_s
328                )
329            }
330            Graph::Graph {
331                id,
332                strict: _,
333                stmts,
334            } => {
335                ctx.is_digraph = false;
336                let body = stmts.print(ctx);
337                format!("graph {} {{{}{}{}}}", id.print(ctx), ctx.l_s, body, ctx.l_s)
338            }
339            Graph::DiGraph { id, strict, stmts } if *strict => {
340                ctx.is_digraph = true;
341                let body = stmts.print(ctx);
342                format!(
343                    "strict digraph {} {{{}{}{}}}",
344                    id.print(ctx),
345                    ctx.l_s,
346                    body,
347                    ctx.l_s
348                )
349            }
350            Graph::DiGraph {
351                id,
352                strict: _,
353                stmts,
354            } => {
355                ctx.is_digraph = true;
356                let body = stmts.print(ctx);
357                format!(
358                    "digraph {} {{{}{}{}}}",
359                    id.print(ctx),
360                    ctx.l_s,
361                    body,
362                    ctx.l_s
363                )
364            }
365        }
366    }
367}
368
369impl DotPrinter for Vec<Stmt> {
370    fn print(&self, ctx: &mut PrinterContext) -> String {
371        let attrs: Vec<String> = self.iter().map(|e| e.print(ctx)).collect();
372        attrs.join(ctx.l_s.as_str())
373    }
374}
375
376impl DotPrinter for Stmt {
377    fn print(&self, ctx: &mut PrinterContext) -> String {
378        let end = if ctx.semi { ";" } else { "" };
379        let indent = ctx.indent();
380        match self {
381            Stmt::Node(e) => format!("{}{}{}", indent, e.print(ctx), end),
382            Stmt::Subgraph(e) => format!("{}{}{}", indent, e.print(ctx), end),
383            Stmt::Attribute(e) => format!("{}{}{}", indent, e.print(ctx), end),
384            Stmt::GAttribute(e) => format!("{}{}{}", indent, e.print(ctx), end),
385            Stmt::Edge(e) => format!("{}{}{}", indent, e.print(ctx), end),
386        }
387    }
388}
389
390fn print_edge(edge: &Edge, ctx: &mut PrinterContext) -> String {
391    let bond = if ctx.is_digraph { "->" } else { "--" };
392    match edge {
393        Edge {
394            ty: EdgeTy::Pair(l, r),
395            attributes,
396        } => {
397            if attributes.is_empty() {
398                format!("{} {} {}", l.print(ctx), bond, r.print(ctx))
399            } else {
400                format!(
401                    "{} {} {} {}",
402                    l.print(ctx),
403                    bond,
404                    r.print(ctx),
405                    attributes.print(ctx)
406                )
407            }
408        }
409        Edge {
410            ty: EdgeTy::Chain(vs),
411            attributes,
412        } => {
413            let mut iter = vs.iter();
414            let h = iter.next().unwrap().print(ctx);
415            let mut chain = h;
416            for el in iter {
417                chain = format!("{} {} {}", chain, bond, el.print(ctx))
418            }
419            format!("{}{}", chain, attributes.print(ctx))
420        }
421    }
422}
423
424impl DotPrinter for Edge {
425    fn print(&self, ctx: &mut PrinterContext) -> String {
426        let mut edge_str = print_edge(self, ctx);
427        if edge_str.len() <= ctx.inline_size && !ctx.is_inline_on() {
428            ctx.inline_mode();
429            edge_str = print_edge(self, ctx);
430            ctx.multiline_mode();
431        }
432
433        edge_str
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use dot_generator::{attr, edge, graph, id, node, node_id, port, stmt, subgraph};
440    use dot_structures::*;
441
442    use crate::printer::{DotPrinter, PrinterContext};
443
444    #[test]
445    fn edge_test() {
446        let mut ctx = PrinterContext::default();
447        let edge = edge!(node_id!("abc") => node_id!("bce") => node_id!("cde"); attr!("a",2));
448        assert_eq!(edge.print(&mut ctx), "abc -- bce -- cde[a=2]");
449        ctx.is_digraph = true;
450        assert_eq!(edge.print(&mut ctx), "abc -> bce -> cde[a=2]");
451    }
452
453    #[test]
454    fn node_id_test() {
455        let node_id = NodeId(id!("abc"), Some(port!(id!("abc"), "n")));
456        let mut ctx = PrinterContext::default();
457        assert_eq!(node_id.print(&mut ctx), "abc:abc:n".to_string());
458    }
459
460    #[test]
461    fn node_test() {
462        let mut ctx = PrinterContext::default();
463        assert_eq!(
464            node!("abc";attr!("a",2)).print(&mut ctx),
465            "abc[a=2]".to_string()
466        );
467    }
468
469    #[test]
470    fn attr_test() {
471        let mut ctx = PrinterContext::default();
472        let attr = attr!("a", 2);
473        assert_eq!(attr.print(&mut ctx), "a=2".to_string());
474    }
475
476    #[test]
477    fn graph_attr_test() {
478        let mut ctx = PrinterContext::default();
479        let n_attr = GraphAttributes::Node(vec![attr!("a", 2), attr!("b", 3)]);
480        assert_eq!(n_attr.print(&mut ctx), "node[a=2,b=3]".to_string());
481    }
482
483    #[test]
484    fn subgraph_test() {
485        let mut ctx = PrinterContext::default();
486        let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b")));
487        println!("{}", s.print(&mut ctx));
488        assert_eq!(
489            s.print(&mut ctx),
490            "subgraph id {\n  abc\n  a -- b\n}".to_string()
491        );
492    }
493
494    #[test]
495    fn graph_test() {
496        let mut ctx = PrinterContext::default();
497        ctx.always_inline();
498        let g = graph!(strict di id!("t");
499          node!("aa";attr!("color","green")),
500          subgraph!("v";
501            node!("aa"; attr!("shape","square")),
502            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
503            node!("aaa";attr!("color","red")),
504            edge!(node_id!("aaa") => node_id!("bbb"))
505            ),
506          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
507          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
508        );
509        assert_eq!(
510            r#"strict digraph t {aa[color=green]subgraph v {aa[shape=square]subgraph vv {a2 -> b2}aaa[color=red]aaa -> bbb}aa -> be -> subgraph v {d -> aaa}aa -> aaa -> v}"#,
511            g.print(&mut ctx)
512        );
513    }
514
515    #[test]
516    fn semi_graph_test() {
517        let mut ctx = PrinterContext::default();
518        let g = graph!(strict di id!("t");
519          node!("aa";attr!("color","green")),
520          subgraph!("v";
521            node!("aa"; attr!("shape","square")),
522            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
523            node!("aaa";attr!("color","red")),
524            edge!(node_id!("aaa") => node_id!("bbb"))
525            ),
526          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
527          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
528        );
529        assert_eq!(
530            "strict digraph t {\n  aa[color=green];\n  subgraph v {\n    aa[shape=square];\n    subgraph vv {\n      a2 -> b2;\n    };\n    aaa[color=red];\n    aaa -> bbb;\n  };\n  aa -> be -> subgraph v {d -> aaa;};\n  aa -> aaa -> v;\n}",
531            g.print(ctx.with_semi())
532        );
533    }
534
535    #[test]
536    fn indent_step_graph_test() {
537        let mut ctx = PrinterContext::default();
538        let g = graph!(strict di id!("t");
539          node!("aa";attr!("color","green")),
540          subgraph!("v";
541            node!("aa"; attr!("shape","square")),
542            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
543            node!("aaa";attr!("color","red")),
544            edge!(node_id!("aaa") => node_id!("bbb"))
545            ),
546          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
547          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
548        );
549        assert_eq!(
550            "strict digraph t {\n    aa[color=green]\n    subgraph v {\n        aa[shape=square]\n        subgraph vv {\n            a2 -> b2\n        }\n        aaa[color=red]\n        aaa -> bbb\n    }\n    aa -> be -> subgraph v {d -> aaa}\n    aa -> aaa -> v\n}",
551            g.print(ctx.with_indent_step(4))
552        );
553    }
554
555    #[test]
556    fn mult_attr_l_s_graph_test() {
557        let mut ctx = PrinterContext::default();
558        let g = graph!(di id!("multi");
559          node!("a";attr!("shape","square")),
560          node!("aa";attr!("color","blue"),attr!("shape","Mrecord")),
561          subgraph!("v";
562            node!("aaa"; attr!("shape","square")),
563            node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")),
564            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
565          ),
566          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
567        );
568        assert_eq!(
569            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue,\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red,\n      shape=Mrecord\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [label=TRUE,color=green]\n}",
570            g.print(ctx.with_node_mult_attr_s_l())
571        );
572    }
573
574    #[test]
575    fn attr_formatter_graph_test() {
576        let mut ctx = PrinterContext::default();
577        let g = graph!(di id!("multi");
578          node!("a";attr!("shape","square")),
579          node!("aa";attr!("color","blue"),attr!("custom", esc "Custom Text"),attr!("shape","Mrecord")),
580          subgraph!("v";
581            node!("aaa"; attr!("shape","square")),
582            node!("aaaa";attr!("color","red"),attr!("custom", esc "Custom Text2")),
583            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
584          ),
585          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
586        );
587        assert_eq!(
588            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue,\n    custom=\"**Custom Text**\",\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red,\n      custom=\"**Custom Text2**\"\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [label=TRUE,color=green]\n}",
589            g.print(ctx.with_node_mult_attr_s_l().with_attr_value_printer(id!("custom"), Box::new(|value, _l_s, _indent, _i_s| {
590                format!(r#""**{}**""#, value.trim_matches('"'))
591            })))
592        );
593    }
594}