irox_dot/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! DOT Graph Description Language writer, compatible with GraphViz
6//!
7//! ### Example:
8//! ```
9//! # use irox_dot::*;
10//! # use std::fs::File;
11//! #
12//! # fn main() -> Result<(), irox_bits::Error>{
13//!     let mut graph = Graph::named("TestGraph");
14//!     graph.graph_type = GraphType::Digraph;
15//!
16//!     // add a top-level graph attribute
17//!     graph.add_graph_attr("landscape", "true");
18//!
19//!     // add a basic node with no attributes
20//!     graph.add_node(Node::new("Node 1"));
21//!
22//!     // add an edge
23//!     graph.add_edge(Edge::new(&graph, "Node 1", "Node 2"));
24//!
25//!     let mut out = String::with_capacity(256);
26//!     graph.write_to(&mut out)?;
27//!     println!("{out}");
28//!     assert_eq!(out, "\
29//!digraph TestGraph {\n\
30//!\tlandscape=true\n\
31//!\t\"Node 1\" \n\
32//!\t\"Node 1\" -> \"Node 2\" \n\
33//!}\n"
34//! );
35//! # Ok(())
36//! # }
37//! ```
38//!
39
40#![forbid(unsafe_code)]
41
42use irox_bits::{BitsError, FormatBits, MutBits};
43use std::collections::HashSet;
44use std::fmt::Write;
45use std::hash::{Hash, Hasher};
46use std::string::String;
47use std::vec::Vec;
48
49pub trait DotLine {
50    fn get_line(&self) -> String;
51}
52
53#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
54pub enum GraphType {
55    #[default]
56    Graph,
57    Digraph,
58}
59
60impl GraphType {
61    pub fn get_arrow(&self) -> &'static str {
62        match self {
63            GraphType::Graph => "--",
64            GraphType::Digraph => "->",
65        }
66    }
67    pub fn get_name(&self) -> &'static str {
68        match self {
69            GraphType::Graph => "graph",
70            GraphType::Digraph => "digraph",
71        }
72    }
73}
74
75#[derive(Debug, Clone, Eq, PartialEq, Hash)]
76pub enum Element {
77    Node(Node),
78    NodeAttr(Attribute),
79    Edge(Edge),
80    EdgeAttr(Attribute),
81    Attribute(Attribute),
82    Subgraph(Subgraph),
83}
84
85impl DotLine for Element {
86    fn get_line(&self) -> String {
87        match self {
88            Element::Node(n) => n.get_line(),
89            Element::Edge(e) => e.get_line(),
90            Element::Attribute(a) => a.get_line(),
91            _ => {
92                todo!()
93            }
94        }
95    }
96}
97
98#[derive(Default, Debug, Clone, Eq, PartialEq)]
99pub struct Graph {
100    pub is_strict: bool,
101    pub graph_type: GraphType,
102    pub id: Option<String>,
103    pub elements: Vec<Element>,
104    pub known_nodes: HashSet<String>,
105}
106impl Hash for Graph {
107    fn hash<H: Hasher>(&self, state: &mut H) {
108        self.is_strict.hash(state);
109        self.graph_type.hash(state);
110        self.id.hash(state);
111        self.elements.hash(state);
112    }
113}
114
115impl Graph {
116    pub fn named(name: &str) -> Self {
117        Self {
118            id: Some(name.to_string()),
119            ..Default::default()
120        }
121    }
122    pub fn add_node(&mut self, node: Node) {
123        self.elements.push(Element::Node(node));
124    }
125    pub fn add_edge(&mut self, edge: Edge) {
126        self.elements.push(Element::Edge(edge));
127    }
128    pub fn add_graph_attr(&mut self, key: &str, val: &str) {
129        self.elements
130            .push(Element::Attribute(Attribute::new(key, val)))
131    }
132    pub fn write_to<T: MutBits>(&self, out: &mut T) -> Result<(), BitsError> {
133        let mut out = FormatBits(out);
134        if self.is_strict {
135            write!(out, "strict ")?;
136        }
137        write!(out, "{} ", self.graph_type.get_name())?;
138        if let Some(name) = &self.id {
139            write!(out, "{name} ")?;
140        }
141        writeln!(out, "{{")?;
142        for elem in &self.elements {
143            writeln!(out, "\t{}", elem.get_line())?;
144        }
145        writeln!(out, "}}")?;
146        Ok(())
147    }
148}
149
150#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
151pub struct Subgraph {
152    pub id: Option<String>,
153    pub elements: Vec<Element>,
154}
155
156#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
157pub struct Attribute {
158    pub name: String,
159    pub value: String,
160}
161impl Attribute {
162    pub fn new(key: &str, val: &str) -> Self {
163        Attribute {
164            name: key.to_string(),
165            value: val.to_string(),
166        }
167    }
168}
169impl DotLine for Attribute {
170    fn get_line(&self) -> String {
171        format!("{}={}", self.name, self.value)
172    }
173}
174
175#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
176pub struct AttrList(pub Vec<Attribute>);
177
178impl DotLine for AttrList {
179    fn get_line(&self) -> String {
180        if self.0.is_empty() {
181            String::default()
182        } else {
183            format!(
184                "[{}]",
185                self.0
186                    .iter()
187                    .map(DotLine::get_line)
188                    .collect::<Vec<_>>()
189                    .join("; ")
190            )
191        }
192    }
193}
194
195#[derive(Debug, Clone, Eq, PartialEq, Hash)]
196pub struct Node {
197    pub id: String,
198    pub attributes: AttrList,
199}
200
201impl Node {
202    pub fn new(id: &str) -> Node {
203        Node {
204            id: id.to_string(),
205            attributes: AttrList::default(),
206        }
207    }
208}
209
210impl DotLine for Node {
211    fn get_line(&self) -> String {
212        format!("\"{}\" {}", self.id, self.attributes.get_line())
213    }
214}
215
216#[derive(Debug, Clone, Eq, PartialEq, Hash)]
217pub struct Edge {
218    pub edge_type: GraphType,
219    pub first_node: String,
220    pub second_node: String,
221    pub attributes: AttrList,
222}
223impl Edge {
224    pub fn new(graph: &Graph, first: &str, second: &str) -> Self {
225        Edge {
226            edge_type: graph.graph_type,
227            first_node: first.to_string(),
228            second_node: second.to_string(),
229            attributes: AttrList::default(),
230        }
231    }
232}
233
234impl DotLine for Edge {
235    fn get_line(&self) -> String {
236        format!(
237            "\"{}\" {} \"{}\" {}",
238            self.first_node,
239            self.edge_type.get_arrow(),
240            self.second_node,
241            self.attributes.get_line()
242        )
243    }
244}