1use std::fmt;
2use std::str::FromStr;
3
4use crate::errors::Error;
5use crate::node::{Node, Nodes};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Filetree {
9 root: Node,
10}
11
12impl Filetree {
13 pub fn new(root: Node) -> Filetree {
14 Filetree { root }
15 }
16
17 pub fn root(&self) -> &Node {
18 &self.root
19 }
20
21 #[cfg(feature = "color")]
22 pub fn color_display(&self) -> Result<String, Error> {
23 let mut s = String::new();
24 for node in self.root.nodes() {
25 s.push_str(&format!("{}\n", node.treeline_color(self)));
26 }
27 Ok(s)
28 }
29}
30
31impl fmt::Display for Filetree {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let mut s = String::new();
34 for node in self.root.nodes() {
35 s.push_str(&format!("{}\n", node.treeline(self)));
36 }
37 write!(f, "{}", s)
38 }
39}
40
41impl FromStr for Filetree {
42 type Err = Error;
43
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 let value: serde_yaml::Value = serde_yaml::from_str(s)?;
46 let mut root = Node::new(".".into(), vec![], true);
47 let mut children: Nodes = value.try_into()?;
48 root.add_children(&mut children.0);
49 Ok(Filetree::new(root))
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use textwrap::dedent;
56 use wasm_bindgen_test::wasm_bindgen_test;
57
58 use super::*;
59
60 macro_rules! test_filetree {
61 ($name:ident, $s:expr, $expected:expr) => {
62 #[wasm_bindgen_test]
63 #[test]
64 fn $name() -> anyhow::Result<()> {
65 let filetree: Filetree = $s.parse()?;
66 println!("{}", filetree);
67 assert_eq!(format!("{}", filetree), format!("{}\n", $expected.trim()));
68 Ok(())
69 }
70 };
71 }
72
73 test_filetree!(empty, "", ".");
74 test_filetree!(
75 flat,
76 "
77 - dir1:
78 - file1.txt
79 - dir2:
80 ",
81 dedent(
82 "
83 .
84 ├── dir1
85 ├── file1.txt
86 └── dir2
87 "
88 )
89 );
90 test_filetree!(
91 nested,
92 "
93 - dir1:
94 - file1.txt
95 - subdir1:
96 - subdir2:
97 - file2.py
98 - dir 2:
99 - subdir3:
100 - subsubdir:
101 - file3.toml
102 - subsubdir2:
103 - subdir4:
104 - config.ini
105 ",
106 dedent(
107 "
108 .
109 ├── dir1
110 │ ├── file1.txt
111 │ ├── subdir1
112 │ └── subdir2
113 │ └── file2.py
114 └── dir 2
115 ├── subdir3
116 │ ├── subsubdir
117 │ │ └── file3.toml
118 │ └── subsubdir2
119 ├── subdir4
120 └── config.ini
121 "
122 )
123 );
124 test_filetree!(
125 deeply_nested,
126 "
127 - dir1:
128 - dir2:
129 - dir3:
130 - file
131 - dir4:
132 - file2
133 - dir5:
134 - file3
135 - dir6:
136 - file4
137 ",
138 dedent(
139 "
140 .
141 ├── dir1
142 │ ├── dir2
143 │ │ ├── dir3
144 │ │ │ └── file
145 │ │ └── dir4
146 │ │ └── file2
147 │ └── dir5
148 │ └── file3
149 └── dir6
150 └── file4
151 "
152 )
153 );
154 test_filetree!(
155 same_filename,
156 "
157 - dir:
158 - file
159 - file
160 ",
161 dedent(
162 "
163 .
164 ├── dir
165 │ └── file
166 └── file
167 "
168 )
169 );
170
171 #[cfg(feature = "color")]
172 #[wasm_bindgen_test]
173 #[test]
174 fn color_display() {
175 use colored::Colorize;
176
177 let s = dedent(
178 "
179 - dir1:
180 - file1.txt
181 - dir2:
182 ",
183 );
184 let expected: String = [
185 ".".cyan().bold().to_string(),
186 format!("├── {}", "dir1".cyan().bold()),
187 "├── file1.txt".into(),
188 format!("└── {}", "dir2".cyan().bold()),
189 ]
190 .into_iter()
191 .collect::<Vec<_>>()
192 .join("\n");
193 let filetree: Filetree = s.parse().unwrap();
194 println!("{}", filetree.color_display().unwrap());
195 assert_eq!(filetree.color_display().unwrap().trim(), expected);
196 }
197}