forge_tree/parser/
tree_parser.rs1use crate::parser::{ItemType, ProjectStructure, StructureItem};
2use crate::{Result, ForgeTreeError};
3use std::collections::HashMap;
4
5pub struct TreeParser;
6
7impl TreeParser {
8 pub fn new() -> Self {
9 Self
10 }
11
12 pub fn parse(&self, input: &str) -> Result<ProjectStructure> {
13 let lines: Vec<&str> = input.lines().collect();
14 if lines.is_empty() {
15 return Err(ForgeTreeError::Parse("Empty input".to_string()));
16 }
17
18 let root_name = self.extract_root_name(&lines)?;
19
20 let child_lines: Vec<&str> = lines[1..].iter()
22 .filter(|line| !line.trim().is_empty())
23 .copied()
24 .collect();
25
26 let items = self.parse_structure(&child_lines)?;
27
28 Ok(ProjectStructure {
29 root: root_name,
30 items,
31 variables: HashMap::new(),
32 })
33 }
34
35 fn extract_root_name(&self, lines: &[&str]) -> Result<String> {
36 let first_line = lines.first()
37 .ok_or_else(|| ForgeTreeError::Parse("No root directory found".to_string()))?;
38
39 let name = first_line.trim().trim_end_matches('/');
40 if name.is_empty() {
41 return Err(ForgeTreeError::Parse("Invalid root directory name".to_string()));
42 }
43
44 Ok(name.to_string())
45 }
46
47 fn parse_structure(&self, lines: &[&str]) -> Result<Vec<StructureItem>> {
48 let mut items = Vec::new();
49 let mut i = 0;
50
51 while i < lines.len() {
52 let line = lines[i];
53 let current_depth = self.get_depth(line);
54
55 let (name, is_directory) = self.parse_line(line)?;
56 let mut item = StructureItem {
57 name: name.clone(),
58 path: name,
59 item_type: if is_directory { ItemType::Directory } else { ItemType::File },
60 template: None,
61 content: None,
62 children: Vec::new(),
63 };
64
65 i += 1;
67 let mut child_lines = Vec::new();
68
69 while i < lines.len() {
70 let child_line = lines[i];
71 let child_depth = self.get_depth(child_line);
72
73 if child_depth <= current_depth {
75 break;
76 }
77
78 child_lines.push(child_line);
79 i += 1;
80 }
81
82 if !child_lines.is_empty() {
84 item.children = self.parse_structure(&child_lines)?;
85 item.item_type = ItemType::Directory; }
87
88 items.push(item);
89 }
90
91 Ok(items)
92 }
93
94 fn get_depth(&self, line: &str) -> usize {
95 let mut depth = 0;
96
97 for ch in line.chars() {
98 match ch {
99 '├' | '└' | '│' => depth += 1,
100 '─' | ' ' => continue, _ => break, }
103 }
104
105 depth
106 }
107
108 fn parse_line(&self, line: &str) -> Result<(String, bool)> {
109 let content = line.chars()
111 .skip_while(|&ch| ch == '│' || ch == '├' || ch == '└' || ch == '─' || ch == ' ')
112 .collect::<String>()
113 .trim()
114 .to_string();
115
116 if content.is_empty() {
117 return Err(ForgeTreeError::Parse(format!("Empty name in line: {}", line)));
118 }
119
120 let is_directory = content.ends_with('/') || !content.contains('.');
122 let clean_name = content.trim_end_matches('/').to_string();
123
124 Ok((clean_name, is_directory))
125 }
126}
127
128impl Default for TreeParser {
129 fn default() -> Self {
130 Self::new()
131 }
132}