1use std::fs;
2use std::path::Path;
3
4use archidoc_types::{
5 C4Level, FileEntry, HealthStatus, PatternStatus, Relationship,
6};
7
8pub fn archidoc_from_file(path: &Path) -> Option<String> {
12 let content = fs::read_to_string(path).ok()?;
13
14 let doc_lines: Vec<&str> = content
15 .lines()
16 .take_while(|line| {
17 let trimmed = line.trim();
18 trimmed.starts_with("//!") || trimmed.is_empty()
19 })
20 .filter(|line| line.trim().starts_with("//!"))
21 .map(|line| {
22 let trimmed = line.trim();
23 if trimmed == "//!" {
24 ""
25 } else if let Some(rest) = trimmed.strip_prefix("//! ") {
26 rest
27 } else {
28 trimmed.strip_prefix("//!").unwrap_or("")
29 }
30 })
31 .collect();
32
33 if doc_lines.is_empty() {
34 None
35 } else {
36 Some(doc_lines.join("\n"))
37 }
38}
39
40pub fn extract_c4_level(content: &str) -> C4Level {
44 if content.contains("@c4 container") {
45 C4Level::Container
46 } else if content.contains("@c4 component") {
47 C4Level::Component
48 } else {
49 C4Level::Unknown
50 }
51}
52
53pub fn extract_pattern(content: &str) -> String {
58 let patterns = [
59 "Mediator",
60 "Observer",
61 "Strategy",
62 "Facade",
63 "Adapter",
64 "Repository",
65 "Singleton",
66 "Factory",
67 "Builder",
68 "Decorator",
69 "Active Object",
70 "Memento",
71 "Command",
72 "Chain of Responsibility",
73 "Registry",
74 "Composite",
75 "Interpreter",
76 "Flyweight",
77 "Publisher",
78 ];
79
80 for name in patterns {
81 if content.contains(name) {
82 return name.to_string();
83 }
84 }
85
86 "--".to_string()
87}
88
89pub fn extract_pattern_status(content: &str) -> PatternStatus {
93 if content.contains("(verified)") {
94 PatternStatus::Verified
95 } else {
96 PatternStatus::Planned
97 }
98}
99
100pub fn extract_description(content: &str) -> String {
102 content
103 .lines()
104 .find(|l| {
105 let trimmed = l.trim();
106 !trimmed.is_empty()
107 && !trimmed.starts_with('#')
108 && !trimmed.starts_with("@c4 ")
109 && !trimmed.starts_with('|')
110 && !trimmed.starts_with("GoF:")
111 })
112 .unwrap_or("*No description*")
113 .trim()
114 .to_string()
115}
116
117pub fn extract_parent_container(module_path: &str) -> Option<String> {
122 if module_path.contains('.') {
123 Some(
124 module_path
125 .split('.')
126 .next()
127 .unwrap_or(module_path)
128 .to_string(),
129 )
130 } else {
131 None
132 }
133}
134
135pub fn extract_file_table(content: &str) -> Vec<FileEntry> {
144 let mut entries = Vec::new();
145 let mut in_table = false;
146 let mut header_seen = false;
147
148 for line in content.lines() {
149 let trimmed = line.trim();
150
151 if !in_table {
152 if trimmed.starts_with('|')
154 && (trimmed.contains("File") || trimmed.contains("file"))
155 && (trimmed.contains("Pattern") || trimmed.contains("pattern"))
156 {
157 in_table = true;
158 continue;
159 }
160 } else if !header_seen {
161 if trimmed.starts_with('|') && trimmed.contains("---") {
163 header_seen = true;
164 continue;
165 }
166 } else {
167 if !trimmed.starts_with('|') {
169 break; }
171
172 let cells: Vec<&str> = trimmed
173 .split('|')
174 .filter(|s| !s.trim().is_empty())
175 .map(|s| s.trim())
176 .collect();
177
178 if cells.len() >= 4 {
179 let filename = cells[0]
180 .trim_matches('`')
181 .trim()
182 .to_string();
183
184 let (pattern, pattern_status) = parse_pattern_field(cells[1]);
185 let purpose = cells[2].trim().to_string();
186 let health = HealthStatus::parse(cells[3]);
187
188 entries.push(FileEntry {
189 name: filename,
190 pattern,
191 pattern_status,
192 purpose,
193 health,
194 });
195 }
196 }
197 }
198
199 entries
200}
201
202fn parse_pattern_field(field: &str) -> (String, PatternStatus) {
204 let trimmed = field.trim();
205
206 if let Some(idx) = trimmed.find('(') {
207 let pattern = trimmed[..idx].trim().to_string();
208 let status_str = trimmed[idx + 1..]
209 .trim_end_matches(')')
210 .trim();
211 (pattern, PatternStatus::parse(status_str))
212 } else {
213 (trimmed.to_string(), PatternStatus::Planned)
214 }
215}
216
217pub fn extract_relationships(content: &str) -> Vec<Relationship> {
219 let mut rels = Vec::new();
220
221 for line in content.lines() {
222 let trimmed = line.trim();
223 if let Some(rest) = trimmed.strip_prefix("@c4 uses ") {
224 if let Some(quote_start) = rest.find('"') {
227 let target = rest[..quote_start].trim().to_string();
228 let quoted_part = &rest[quote_start..];
229 let quotes: Vec<&str> = quoted_part
230 .split('"')
231 .filter(|s| !s.trim().is_empty())
232 .collect();
233 if quotes.len() >= 2 {
234 rels.push(Relationship {
235 target,
236 label: quotes[0].to_string(),
237 protocol: quotes[1].to_string(),
238 });
239 }
240 }
241 }
242 }
243
244 rels
245}