xml_disassembler/handlers/
disassemble.rs1use crate::builders::build_disassembled_files_unified;
4use crate::types::BuildDisassembledFilesOptions;
5use ignore::gitignore::GitignoreBuilder;
6use std::path::Path;
7use tokio::fs;
8
9pub struct DisassembleXmlFileHandler {
10 ign: Option<ignore::gitignore::Gitignore>,
11}
12
13impl DisassembleXmlFileHandler {
14 pub fn new() -> Self {
15 Self { ign: None }
16 }
17
18 async fn load_ignore_rules(&mut self, ignore_path: &str) {
19 let path = Path::new(ignore_path);
20 if path.exists() {
21 if let Ok(content) = fs::read_to_string(path).await {
22 let root = path.parent().unwrap_or(Path::new("."));
23 let mut builder = GitignoreBuilder::new(root);
24 for line in content.lines() {
25 let _ = builder.add_line(None, line);
26 }
27 if let Ok(gi) = builder.build() {
28 self.ign = Some(gi);
29 }
30 }
31 }
32 }
33
34 fn posix_path(path: &str) -> String {
35 path.replace('\\', "/")
36 }
37
38 fn is_xml_file(file_path: &str) -> bool {
39 file_path.to_lowercase().ends_with(".xml")
40 }
41
42 fn is_ignored(&self, path: &str) -> bool {
43 self.ign
44 .as_ref()
45 .map(|ign| ign.matched(path, false).is_ignore())
46 .unwrap_or(false)
47 }
48
49 #[allow(clippy::too_many_arguments)]
50 pub async fn disassemble(
51 &mut self,
52 file_path: &str,
53 unique_id_elements: Option<&str>,
54 strategy: Option<&str>,
55 pre_purge: bool,
56 post_purge: bool,
57 ignore_path: &str,
58 format: &str,
59 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
60 let strategy = strategy.unwrap_or("unique-id");
61 let strategy = if ["unique-id", "grouped-by-tag"].contains(&strategy) {
62 strategy
63 } else {
64 log::warn!(
65 "Unsupported strategy \"{}\", defaulting to \"unique-id\".",
66 strategy
67 );
68 "unique-id"
69 };
70
71 self.load_ignore_rules(ignore_path).await;
72
73 let path = Path::new(file_path);
74 let meta = fs::metadata(path).await?;
75 let cwd = std::env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
76 let relative_path = path.strip_prefix(&cwd).unwrap_or(path).to_string_lossy();
77 let relative_path = Self::posix_path(&relative_path);
78
79 if meta.is_file() {
80 self.handle_file(
81 file_path,
82 &relative_path,
83 unique_id_elements,
84 strategy,
85 pre_purge,
86 post_purge,
87 format,
88 )
89 .await?;
90 } else if meta.is_dir() {
91 self.handle_directory(
92 file_path,
93 unique_id_elements,
94 strategy,
95 pre_purge,
96 post_purge,
97 format,
98 )
99 .await?;
100 }
101
102 Ok(())
103 }
104
105 #[allow(clippy::too_many_arguments)]
106 async fn handle_file(
107 &self,
108 file_path: &str,
109 relative_path: &str,
110 unique_id_elements: Option<&str>,
111 strategy: &str,
112 pre_purge: bool,
113 post_purge: bool,
114 format: &str,
115 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
116 let resolved = Path::new(file_path)
117 .canonicalize()
118 .unwrap_or_else(|_| Path::new(file_path).to_path_buf());
119 let resolved_str = resolved.to_string_lossy();
120
121 if !Self::is_xml_file(&resolved_str) {
122 log::error!(
123 "The file path provided is not an XML file: {}",
124 resolved_str
125 );
126 return Ok(());
127 }
128
129 if self.is_ignored(relative_path) {
130 log::warn!("File ignored by ignore rules: {}", resolved_str);
131 return Ok(());
132 }
133
134 let dir_path = resolved.parent().unwrap_or(Path::new("."));
135 self.process_file(
136 dir_path.to_str().unwrap_or("."),
137 strategy,
138 &resolved_str,
139 unique_id_elements,
140 pre_purge,
141 post_purge,
142 format,
143 )
144 .await
145 }
146
147 async fn handle_directory(
148 &self,
149 dir_path: &str,
150 unique_id_elements: Option<&str>,
151 strategy: &str,
152 pre_purge: bool,
153 post_purge: bool,
154 format: &str,
155 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
156 let mut entries = fs::read_dir(dir_path).await?;
157 let cwd = std::env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
158
159 while let Some(entry) = entries.next_entry().await? {
160 let sub_path = entry.path();
161 let sub_file_path = sub_path.to_string_lossy();
162 let relative_sub = sub_path
163 .strip_prefix(&cwd)
164 .unwrap_or(&sub_path)
165 .to_string_lossy();
166 let relative_sub = Self::posix_path(&relative_sub);
167
168 if sub_path.is_file() && Self::is_xml_file(&sub_file_path) {
169 if self.is_ignored(&relative_sub) {
170 log::warn!("File ignored by ignore rules: {}", sub_file_path);
171 } else {
172 self.process_file(
173 dir_path,
174 strategy,
175 &sub_file_path,
176 unique_id_elements,
177 pre_purge,
178 post_purge,
179 format,
180 )
181 .await?;
182 }
183 }
184 }
185 Ok(())
186 }
187
188 #[allow(clippy::too_many_arguments)]
189 async fn process_file(
190 &self,
191 dir_path: &str,
192 strategy: &str,
193 file_path: &str,
194 unique_id_elements: Option<&str>,
195 pre_purge: bool,
196 post_purge: bool,
197 format: &str,
198 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
199 log::debug!("Parsing file to disassemble: {}", file_path);
200
201 let file_name = Path::new(file_path)
202 .file_stem()
203 .and_then(|s| s.to_str())
204 .unwrap_or("output");
205 let base_name = file_name.split('.').next().unwrap_or(file_name);
206 let output_path = Path::new(dir_path).join(base_name);
207
208 if pre_purge && output_path.exists() {
209 fs::remove_dir_all(&output_path).await.ok();
210 }
211
212 build_disassembled_files_unified(BuildDisassembledFilesOptions {
213 file_path,
214 disassembled_path: output_path.to_str().unwrap_or("."),
215 base_name: file_name,
216 post_purge,
217 format,
218 unique_id_elements,
219 strategy,
220 })
221 .await
222 }
223}
224
225impl Default for DisassembleXmlFileHandler {
226 fn default() -> Self {
227 Self::new()
228 }
229}