render_as_tree/lib.rs
1#![warn(missing_docs)]
2
3//! A library that allows you to visualize tree data structures in Rust with
4//! output like `tree(1)`, like so:
5//!
6//! ```text
7//! Parent
8//! ├── Child 1
9//! ├── Child 2
10//! │ ├── Grandchild 1
11//! │ └── Grandchild 2
12//! └── Child 3
13//! ```
14//!
15//! This crate was extracted from [ruut](https://github.com/hibachrach/ruut), a CLI intended for doing the same
16//! thing. See that repo if you're interested in executing a tree visualizer from
17//! the commandline or for something that can process common serialized data types
18//! (e.g. JSON).
19
20/// Represents a node in the tree.
21///
22/// Important as [render] takes a [`Node`] as its only parameter.
23///
24/// # Example
25///
26/// ```
27/// use render_as_tree::Node;
28/// struct BasicNode {
29/// pub name: String,
30/// pub children: Vec<BasicNode>,
31/// }
32///
33/// impl BasicNode {
34/// pub fn new(name: String) -> BasicNode {
35/// BasicNode {
36/// name,
37/// children: Vec::new(),
38/// }
39/// }
40/// }
41///
42/// impl Node for BasicNode {
43/// type Iter<'a> = std::slice::Iter<'a, Self>;
44///
45/// fn name(&self) -> &str {
46/// &self.name
47/// }
48/// fn children(&self) -> Self::Iter<'_> {
49/// self.children.iter()
50/// }
51/// }
52/// ```
53pub trait Node {
54 /// An iterator over the children of this node
55 type Iter<'a>: DoubleEndedIterator<Item = &'a Self>
56 where
57 Self: 'a;
58
59 /// What is displayed for this node when rendered
60 fn name(&self) -> &str;
61 /// The immediate children of this node in the tree
62 fn children(&self) -> Self::Iter<'_>;
63}
64
65/// Renders the given [`Node`] in a human-readable format
66///
67/// Here's an example:
68/// ```no_run
69/// vec![
70/// String::from("root - selena"),
71/// String::from("├── child 1 - sam"),
72/// String::from("│ ├── grandchild 1A - burt"),
73/// String::from("│ ├── grandchild 1B - crabbod"),
74/// String::from("│ └── grandchild 1C - mario"),
75/// String::from("└── child 2 - dumptruck"),
76/// String::from(" ├── grandchild 2A - tilly"),
77/// String::from(" └── grandchild 2B - curling iron"),
78/// ];
79/// ```
80pub fn render<T: Node>(node: &T) -> Vec<String> {
81 let mut lines = vec![node.name().to_owned()];
82 let mut children = node.children();
83 let maybe_last_child = children.next_back();
84 let non_last_children: Vec<&T> = children.collect();
85 if let Some(last_child) = maybe_last_child {
86 let child_node_lines = non_last_children.iter().flat_map(|child| {
87 render(*child)
88 .iter()
89 .enumerate()
90 .map(|(idx, child_line)| {
91 if idx == 0 {
92 format!("├── {}", child_line)
93 } else {
94 format!("│ {}", child_line)
95 }
96 })
97 .collect::<Vec<String>>()
98 });
99 let last_child_node_lines = render(last_child);
100 let formatted_last_child_node_lines_iter =
101 last_child_node_lines
102 .iter()
103 .enumerate()
104 .map(|(idx, child_line)| {
105 if idx == 0 {
106 format!("└── {}", child_line)
107 } else {
108 format!(" {}", child_line)
109 }
110 });
111 let children_lines = child_node_lines.chain(formatted_last_child_node_lines_iter);
112 lines.extend(children_lines);
113 }
114 lines
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[derive(Debug, PartialEq)]
122 struct BasicNode {
123 pub name: String,
124 pub children: Vec<BasicNode>,
125 }
126
127 impl BasicNode {
128 pub fn new(name: String) -> BasicNode {
129 BasicNode {
130 name,
131 children: Vec::new(),
132 }
133 }
134 }
135
136 impl Node for BasicNode {
137 type Iter<'a> = std::slice::Iter<'a, Self>;
138
139 fn name(&self) -> &str {
140 &self.name
141 }
142 fn children(&self) -> Self::Iter<'_> {
143 self.children.iter()
144 }
145 }
146
147 #[test]
148 fn trivial_case() {
149 assert_eq!(
150 render(&BasicNode::new(String::from("beans"))),
151 vec![String::from("beans")]
152 )
153 }
154
155 #[test]
156 fn simple_case() {
157 let root = BasicNode {
158 name: String::from("root - selena"),
159 children: vec![
160 BasicNode {
161 name: String::from("child 1 - sam"),
162 children: vec![
163 BasicNode::new(String::from("grandchild 1A - burt")),
164 BasicNode::new(String::from("grandchild 1B - crabbod")),
165 BasicNode::new(String::from("grandchild 1C - mario")),
166 ],
167 },
168 BasicNode {
169 name: String::from("child 2 - dumptruck"),
170 children: vec![
171 BasicNode::new(String::from("grandchild 2A - tilly")),
172 BasicNode::new(String::from("grandchild 2B - curling iron")),
173 ],
174 },
175 ],
176 };
177 assert_eq!(
178 render(&root),
179 vec![
180 String::from("root - selena"),
181 String::from("├── child 1 - sam"),
182 String::from("│ ├── grandchild 1A - burt"),
183 String::from("│ ├── grandchild 1B - crabbod"),
184 String::from("│ └── grandchild 1C - mario"),
185 String::from("└── child 2 - dumptruck"),
186 String::from(" ├── grandchild 2A - tilly"),
187 String::from(" └── grandchild 2B - curling iron"),
188 ]
189 );
190 }
191}