1use std::fs;
2use std::io::{self, BufRead};
3use std::path::Path;
4
5#[cfg(test)]
6mod tests;
7
8#[derive(Debug, PartialEq)]
9struct TreeNode {
10 name: String,
11 indent_level: usize,
12}
13
14#[derive(Debug)]
15struct TreeStructure {
16 nodes: Vec<TreeNode>,
17 #[allow(dead_code)] indent_width: usize,
19}
20
21impl TreeStructure {
22 pub fn from_string(input: &str) -> io::Result<Self> {
24 if input.is_empty() {
25 return Err(io::Error::new(io::ErrorKind::InvalidData, "Input is empty"));
26 }
27
28 let first_line = input.lines().next().unwrap_or("");
29 let leading_spaces = first_line.chars().take_while(|c| c.is_whitespace()).count();
30
31 if leading_spaces > 0 {
32 return Err(io::Error::new(
33 io::ErrorKind::InvalidData,
34 "Root directory (line 1) should not be indented",
35 ));
36 }
37
38 if is_ascii_tree(input) {
39 Self::from_ascii_tree(input)
40 } else {
41 Self::from_indented(input)
42 }
43 }
44
45 fn from_ascii_tree(input: &str) -> io::Result<Self> {
47 let mut nodes: Vec<TreeNode> = Vec::new();
48
49 for line in input.lines() {
50 if line.trim().is_empty() {
51 continue;
52 }
53
54 let prefixes = line
56 .chars()
57 .take_while(|&c| c == ' ' || c == '│' || c == '├' || c == '└')
58 .count();
59
60 let indent_level = if prefixes == 0 { 0 } else { (prefixes + 3) / 4 };
62
63 let name = line
65 .trim_start_matches(|c: char| {
66 c.is_whitespace() || c == '│' || c == '├' || c == '└' || c == '─'
67 })
68 .to_string();
69
70 nodes.push(TreeNode { name, indent_level });
71 }
72
73 if nodes.is_empty() {
74 return Err(io::Error::new(
75 io::ErrorKind::InvalidData,
76 "No valid nodes found",
77 ));
78 }
79
80 Ok(Self {
81 nodes,
82 indent_width: 2, })
84 }
85
86 fn from_indented(input: &str) -> io::Result<Self> {
88 let mut nodes: Vec<TreeNode> = Vec::new();
89 let mut indent_width = None;
90
91 for (line_num, line) in input.lines().enumerate() {
92 if line.trim().is_empty() {
93 continue;
94 }
95
96 let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
97 let name = line.trim().to_string();
98
99 if spaces > 0 && indent_width.is_none() {
101 indent_width = Some(spaces);
102 }
103
104 let indent_width = indent_width.unwrap_or(2);
105
106 if spaces % indent_width != 0 {
108 return Err(io::Error::new(
109 io::ErrorKind::InvalidData,
110 format!(
111 "Inconsistent indentation at line {}. Expected a multiple of {} spaces (found {} spaces)",
112 line_num + 1, indent_width, spaces
113 )
114 ));
115 }
116
117 let indent_level = spaces / indent_width;
118
119 if let Some(prev_node) = nodes.last() {
121 if indent_level > prev_node.indent_level + 1 {
122 return Err(io::Error::new(
123 io::ErrorKind::InvalidData,
124 format!(
125 "Invalid indentation at line {}. Indentation can only increase by one level at a time",
126 line_num + 1
127 )
128 ));
129 }
130 }
131
132 nodes.push(TreeNode { name, indent_level });
133 }
134
135 if nodes.is_empty() {
136 return Err(io::Error::new(
137 io::ErrorKind::InvalidData,
138 "No valid nodes found",
139 ));
140 }
141
142 Ok(Self {
143 nodes,
144 indent_width: indent_width.unwrap_or(2),
145 })
146 }
147
148 fn to_ascii_tree(&self) -> String {
150 let mut result = Vec::new();
151
152 let mut level_has_next = [false; 32]; for (i, node) in self.nodes.iter().enumerate() {
156 let current_level = node.indent_level;
157
158 if let Some(next_node) = self.nodes.get(i + 1) {
160 if next_node.indent_level == current_level {
161 level_has_next[current_level] = true;
162 }
163 }
164 }
165
166 for node in self.nodes.iter() {
168 let mut prefix = String::new();
169
170 prefix.extend(
172 level_has_next
173 .iter()
174 .take(node.indent_level)
175 .skip(1)
176 .map(|&has_next| if has_next { "│ " } else { " " }),
177 );
178
179 if node.indent_level > 0 {
181 prefix.push_str(if level_has_next[node.indent_level] {
182 "├── "
183 } else {
184 "└── "
185 });
186 }
187
188 result.push(format!("{}{}", prefix, node.name));
189 }
190
191 result.join("\n")
192 }
193}
194
195fn is_ascii_tree(input: &str) -> bool {
197 input.contains("├──") || input.contains("└──") || input.contains("│")
198}
199
200pub fn create_tree(input: &str, base_path: &Path, force: bool) -> io::Result<()> {
201 let tree = TreeStructure::from_string(input)?;
203
204 let ascii_tree = tree.to_ascii_tree();
206
207 let reader = io::BufReader::new(ascii_tree.as_bytes());
208 let mut lines = reader.lines().peekable();
209
210 let root_name = if let Some(Ok(first_line)) = lines.next() {
212 first_line.trim_end_matches('/').to_string()
213 } else {
214 return Err(io::Error::new(io::ErrorKind::InvalidData, "Input is empty"));
215 };
216
217 let base_path = base_path.join(&root_name);
218 if base_path.exists() {
219 if force {
220 if base_path.is_file() {
221 fs::remove_file(&base_path)?;
222 } else {
223 fs::remove_dir_all(&base_path)?;
224 }
225 fs::create_dir_all(&base_path)?;
226 println!("Overwrote existing directory: {:?}", base_path);
227 } else {
228 println!("Directory already exists: {:?}", base_path);
229 }
230 } else {
231 fs::create_dir_all(&base_path)?;
232 println!("Created root directory: {:?}", base_path);
233 }
234
235 let mut current_depth = 0;
236 let mut path_stack = vec![base_path.clone()];
237
238 for line in lines {
239 let line = line?;
240 let depth = line
241 .chars()
242 .take_while(|&c| c == ' ' || c == '│' || c == '└' || c == '├')
243 .count()
244 / 4;
245 let name = line
246 .trim_start_matches(|c: char| {
247 c.is_whitespace() || c == '│' || c == '└' || c == '├' || c == '─'
248 })
249 .to_string();
250
251 while depth < current_depth && !path_stack.is_empty() {
253 path_stack.pop();
254 current_depth -= 1;
255 }
256 current_depth = depth;
257
258 let mut full_path = path_stack
259 .last()
260 .cloned()
261 .unwrap_or_else(|| base_path.clone());
262 full_path.push(&name);
263
264 if name.ends_with('/') {
265 if full_path.exists() {
266 if force {
267 if full_path.is_file() {
268 fs::remove_file(&full_path)?;
269 fs::create_dir_all(&full_path)?;
270 println!("Overwrote file with directory: {:?}", full_path);
271 } else {
272 println!("Using existing directory: {:?}", full_path);
274 }
275 } else {
276 println!("Directory already exists: {:?}", full_path);
277 }
278 } else {
279 fs::create_dir_all(&full_path)?;
280 println!("Created directory: {:?}", full_path);
281 }
282 path_stack.push(full_path);
283 } else {
284 if let Some(parent) = full_path.parent() {
285 fs::create_dir_all(parent)?;
286 }
287 if full_path.exists() {
288 if force {
289 if full_path.is_dir() {
290 fs::remove_dir_all(&full_path)?;
291 }
292 fs::write(&full_path, "")?;
293 println!("Overwrote existing file: {:?}", full_path);
294 } else {
295 println!("File already exists: {:?}", full_path);
296 }
297 } else {
298 fs::File::create(&full_path)?;
299 println!("Created file: {:?}", full_path);
300 }
301 }
302 }
303
304 Ok(())
305}