Skip to main content

causal_hub/io/dot/
attributes.rs

1//! Lightweight, model-agnostic DOT attributes.
2
3use crate::types::Map;
4
5/// Graph-level attributes.
6#[derive(Clone, Debug, Default, PartialEq, Eq)]
7pub struct GraphAttributes(pub Map<String, String>);
8
9/// Vertex (node) attributes.
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct VertexAttributes(pub Map<String, String>);
12
13/// Edge attributes.
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
15pub struct EdgeAttributes(pub Map<String, String>);
16
17impl GraphAttributes {
18    /// Insert a raw `key = value` pair, unquoting the value if necessary.
19    #[inline]
20    pub fn insert_raw_parts(&mut self, key: &str, value: &str) {
21        self.0.insert(key.to_string(), unquote(value));
22    }
23
24    /// Get the value associated with a key, if any.
25    #[inline]
26    pub fn get(&self, key: &str) -> Option<&String> {
27        self.0.get(key)
28    }
29}
30
31impl VertexAttributes {
32    /// Insert a raw `key = value` pair, unquoting the value if necessary.
33    #[inline]
34    pub fn insert_raw_parts(&mut self, key: &str, value: &str) {
35        self.0.insert(key.to_string(), unquote(value));
36    }
37}
38
39impl EdgeAttributes {
40    /// Insert a raw `key = value` pair, unquoting the value if necessary.
41    #[inline]
42    pub fn insert_raw_parts(&mut self, key: &str, value: &str) {
43        self.0.insert(key.to_string(), unquote(value));
44    }
45}
46
47/// Quote a value if it contains spaces or special characters.
48pub(crate) fn quote(stats: &str) -> String {
49    if stats.is_empty() || stats.contains(' ') || stats.contains('"') {
50        format!("\"{}\"", stats.replace('"', "\\\""))
51    } else {
52        stats.to_string()
53    }
54}
55
56/// Remove surrounding double quotes and unescape `\"`.
57pub(crate) fn unquote(stats: &str) -> String {
58    let stats = stats.trim();
59    if stats.len() >= 2 && stats.starts_with('"') && stats.ends_with('"') {
60        stats[1..stats.len() - 1].replace("\\\"", "\"")
61    } else {
62        stats.to_string()
63    }
64}
65
66impl From<GraphAttributes> for String {
67    fn from(a: GraphAttributes) -> String {
68        a.0.iter()
69            .map(|(k, v)| format!("{k}={}", quote(v)))
70            .collect::<Vec<_>>()
71            .join(" ")
72    }
73}
74
75impl From<VertexAttributes> for String {
76    fn from(a: VertexAttributes) -> String {
77        a.0.iter()
78            .map(|(k, v)| format!("{k}={}", quote(v)))
79            .collect::<Vec<_>>()
80            .join(" ")
81    }
82}
83
84impl From<EdgeAttributes> for String {
85    fn from(a: EdgeAttributes) -> String {
86        a.0.iter()
87            .map(|(k, v)| format!("{k}={}", quote(v)))
88            .collect::<Vec<_>>()
89            .join(" ")
90    }
91}