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}