vizz/
graph.rs

1use std::io::{Result, Write};
2
3use crate::Visualize;
4
5#[derive(Debug, Clone)]
6/// A struct for building a graph
7///
8/// This is the main tool end users will want to use to generate graph visualizations.
9///
10/// # Example
11///
12/// ```
13/// use vizz::Graph;
14/// use std::fs::File;
15///
16/// struct MyStruct<'a> {
17///     my_u8: u8,
18///     my_string: String,
19///     my_ref: &'a String,
20/// }
21///
22/// # use vizz::Visualize;
23/// # use vizz::DataDescription;
24/// #
25/// # impl<'a> Visualize for MyStruct<'a> {
26/// #     fn associated_data(&self) -> Option<Vec<DataDescription>> {
27/// #         Some(vec![
28/// #             DataDescription::from(&self.my_u8).with_label("my_u8"),
29/// #             DataDescription::from(&self.my_string).with_label("my_string"),
30/// #             DataDescription::from(&self.my_ref).with_label("my_ref"),
31/// #         ])
32/// #     }
33/// # }
34/// #
35/// // create some data
36/// let unowned_string = String::from("this is referenced!");
37/// let my_struct = MyStruct {
38///     my_u8: 42,
39///     my_string: "this is owned!".into(),
40///     my_ref: &unowned_string,
41/// };
42///
43/// // create file
44/// let mut dot_file = File::create("/tmp/my_struct.dot").unwrap();
45///
46/// // create graph
47/// Graph::new()
48///     .set_id("my_test_graph")
49///     .add_node(&my_struct)
50///     .add_node(&unowned_string)
51///     .write_to(&mut dot_file)
52///     .unwrap();
53/// ```
54pub struct Graph {
55    /// The ID of the graph in the DOT language grammar
56    id: String,
57    /// The string containing the dot file contents, to eventually be written to a file
58    buffer: String,
59}
60
61impl Graph {
62    /// Create a new graph
63    pub fn new() -> Graph {
64        Graph {
65            id: String::from("visualization"),
66            buffer: String::new(),
67        }
68    }
69
70    /// Set the ID for the graph
71    ///
72    /// See the DOT language grammar ID for more info: <https://graphviz.org/doc/info/lang.html>
73    pub fn set_id(self, new_id: impl Into<String>) -> Graph {
74        Graph {
75            id: new_id.into(),
76            ..self
77        }
78    }
79
80    /// Add a data structure that implements [Visualize] to the [Graph]
81    pub fn add_node<V>(self, node: &V) -> Graph
82    where
83        V: Visualize,
84    {
85        Graph {
86            buffer: self.buffer + &node.render_node(),
87            ..self
88        }
89    }
90
91    /// Create the full DOT graph file contents as a [String]
92    pub fn render(&self) -> String {
93        format!(
94            r#"digraph {} {{
95{}
96}}"#,
97            self.id, self.buffer
98        )
99    }
100
101    /// Write the DOT file to the filesystem
102    pub fn write_to<W: Write>(self, writer: &mut W) -> Result<()> {
103        write!(writer, "{}", self.render())
104    }
105}
106
107impl Default for Graph {
108    fn default() -> Self {
109        Graph::new()
110    }
111}
112
113#[cfg(test)]
114mod test {
115    use super::*;
116    use crate::util;
117
118    #[test]
119    fn test_render_graph() {
120        let target = 8u8;
121        let target_address_string = util::address_of(&target);
122        let graph_id = "test_generate_graph";
123        let graph = Graph::new().set_id(graph_id).add_node(&target);
124        assert_eq!(graph.render(), format!("digraph {1} {{\n  node [shape=plaintext]\n    \"{0}\" [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\"><TR><TD PORT=\"{0}-address\"><I>{0}</I></TD><TD PORT=\"{0}-type\"><B>u8</B></TD><TD PORT=\"{0}-value\">8</TD></TR></TABLE>>];\n    \n}}", target_address_string, graph_id));
125    }
126}