archunit/graph/rendering/
dot_renderer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
use crate::graph::{GraphReportEdge, GraphReportSnapshot};

use super::escaping::quoted;

/// Renders a Graphviz DOT directed graph from a completed snapshot.
#[derive(Debug, Clone, Copy, Default)]
pub struct DotRenderer;

impl DotRenderer {
    /// Returns deterministic DOT source.
    #[must_use]
    pub fn render(snapshot: &GraphReportSnapshot) -> String {
        let mut lines = vec![
            "digraph dependencies {".to_owned(),
            "  rankdir=LR;".to_owned(),
            format!("  label={};", quoted(&snapshot.title)),
            "  labelloc=t;".to_owned(),
        ];
        lines.extend(
            snapshot
                .nodes
                .iter()
                .map(|node| format!("  {};", quoted(&node.label))),
        );
        lines.extend(snapshot.edges.iter().map(edge_line));
        lines.push("}".to_owned());
        lines.join("\n")
    }
}

fn edge_line(edge: &GraphReportEdge) -> String {
    let mut attributes = Vec::new();
    if edge.count > 1 {
        attributes.push(format!("label={}", quoted(&edge.count.to_string())));
    }
    if edge.external {
        attributes.push("style=dashed".to_owned());
    }
    if !edge.import_kinds.is_empty() {
        attributes.push(format!(
            "tooltip={}",
            quoted(
                &edge
                    .import_kinds
                    .iter()
                    .map(|kind| kind.as_str())
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        ));
    }
    let suffix = if attributes.is_empty() {
        String::new()
    } else {
        format!(" [{}]", attributes.join(", "))
    };

    format!(
        "  {} -> {}{suffix};",
        quoted(&edge.source),
        quoted(&edge.target)
    )
}