Skip to main content

mermaid_text/
mindmap.rs

1//! Data model for Mermaid `mindmap` diagrams.
2//!
3//! A mindmap is an indent-based hierarchical outline with a single root node
4//! and arbitrarily nested children. The nesting level is determined purely by
5//! indentation in the source: deeper indentation means a child of the previous
6//! less-indented node.
7//!
8//! Example source:
9//!
10//! ```text
11//! mindmap
12//!   root((mindmap))
13//!     Origins
14//!       Long history
15//!       Popularisation
16//!         British popular psychology author Tony Buzan
17//!     Research
18//!       On effectiveness and features
19//!     Tools
20//!       Pen and paper
21//!       Mermaid
22//! ```
23//!
24//! Constructed by [`crate::parser::mindmap::parse`] and consumed by
25//! [`crate::render::mindmap::render`].
26//!
27//! ## Phase 1 limitations
28//!
29//! - Node shape variants (`((text))`, `(text)`, `{{text}}`, `))text((`,
30//!   `)text(`) are all stripped to their inner text; all nodes render as
31//!   plain text in the tree.
32//! - `::icon(...)` directives are silently ignored.
33
34/// A single node in a [`Mindmap`] tree.
35///
36/// `text` is the display label (stripped of any shape bracket syntax);
37/// `children` is the ordered list of immediate child nodes.
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39pub struct MindmapNode {
40    pub text: String,
41    pub children: Vec<MindmapNode>,
42}
43
44impl MindmapNode {
45    /// Create a new leaf node with the given text.
46    pub fn new(text: impl Into<String>) -> Self {
47        MindmapNode {
48            text: text.into(),
49            children: Vec::new(),
50        }
51    }
52
53    /// Total number of nodes in this subtree, including `self`.
54    pub fn node_count(&self) -> usize {
55        1 + self
56            .children
57            .iter()
58            .map(MindmapNode::node_count)
59            .sum::<usize>()
60    }
61}
62
63/// A parsed `mindmap` diagram.
64///
65/// Constructed by [`crate::parser::mindmap::parse`] and consumed by
66/// [`crate::render::mindmap::render`]. The diagram has exactly one root node;
67/// all other nodes are children (or descendants) of that root.
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Mindmap {
70    pub root: MindmapNode,
71}
72
73impl Default for Mindmap {
74    fn default() -> Self {
75        Mindmap {
76            root: MindmapNode::new("root"),
77        }
78    }
79}
80
81impl Mindmap {
82    /// Total number of nodes in the diagram (root + all descendants).
83    pub fn node_count(&self) -> usize {
84        self.root.node_count()
85    }
86}
87
88// ---------------------------------------------------------------------------
89// Tests
90// ---------------------------------------------------------------------------
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn default_mindmap_has_one_node() {
98        let m = Mindmap::default();
99        assert_eq!(m.node_count(), 1);
100        assert_eq!(m.root.text, "root");
101        assert!(m.root.children.is_empty());
102    }
103
104    #[test]
105    fn node_count_counts_all_descendants() {
106        let diag = Mindmap {
107            root: MindmapNode {
108                text: "root".to_string(),
109                children: vec![
110                    MindmapNode {
111                        text: "A".to_string(),
112                        children: vec![MindmapNode::new("A1"), MindmapNode::new("A2")],
113                    },
114                    MindmapNode::new("B"),
115                ],
116            },
117        };
118        // root(1) + A(1) + A1(1) + A2(1) + B(1) = 5
119        assert_eq!(diag.node_count(), 5);
120    }
121
122    #[test]
123    fn equality_holds_for_identical_trees() {
124        let a = Mindmap {
125            root: MindmapNode {
126                text: "root".to_string(),
127                children: vec![MindmapNode::new("child")],
128            },
129        };
130        let b = a.clone();
131        assert_eq!(a, b);
132
133        let c = Mindmap {
134            root: MindmapNode {
135                text: "root".to_string(),
136                children: vec![MindmapNode::new("other")],
137            },
138        };
139        assert_ne!(a, c);
140    }
141}