1use crate::scripts::{ALL_EXTS, ARCHIVE_EXTS};
3use std::fs;
4use std::io;
5use std::io::{Read, Write};
6use std::path::{Path, PathBuf};
7
8pub fn relative_path<P: AsRef<Path>, T: AsRef<Path>>(root: P, target: T) -> PathBuf {
10 let root = root
11 .as_ref()
12 .canonicalize()
13 .unwrap_or_else(|_| root.as_ref().to_path_buf());
14 let target = target
15 .as_ref()
16 .canonicalize()
17 .unwrap_or_else(|_| target.as_ref().to_path_buf());
18
19 let mut root_components: Vec<_> = root.components().collect();
20 let mut target_components: Vec<_> = target.components().collect();
21
22 while !root_components.is_empty()
24 && !target_components.is_empty()
25 && root_components[0] == target_components[0]
26 {
27 root_components.remove(0);
28 target_components.remove(0);
29 }
30
31 let mut result = PathBuf::new();
33 for _ in root_components {
34 result.push("..");
35 }
36
37 for component in target_components {
39 result.push(component);
40 }
41
42 result
43}
44
45pub fn find_files(path: &str, recursive: bool, no_ext_filter: bool) -> io::Result<Vec<String>> {
47 let mut result = Vec::new();
48 let dir_path = Path::new(&path);
49
50 if dir_path.is_dir() {
51 for entry in fs::read_dir(dir_path)? {
52 let entry = entry?;
53 let path = entry.path();
54
55 if path.is_file()
56 && (no_ext_filter
57 || path.file_name().map_or(false, |file| {
58 path.extension().map_or(true, |_| {
59 let file = file.to_string_lossy().to_lowercase();
60 for ext in ALL_EXTS.iter() {
61 if file.ends_with(&format!(".{}", ext)) {
62 return true;
63 }
64 }
65 false
66 })
67 }))
68 {
69 if let Some(path_str) = path.to_str() {
70 result.push(path_str.to_string());
71 }
72 } else if recursive && path.is_dir() {
73 if let Some(path_str) = path.to_str() {
74 let mut sub_files =
75 find_files(&path_str.to_string(), recursive, no_ext_filter)?;
76 result.append(&mut sub_files);
77 }
78 }
79 }
80 }
81
82 Ok(result)
83}
84
85pub fn find_arc_files(path: &str, recursive: bool) -> io::Result<Vec<String>> {
87 let mut result = Vec::new();
88 let dir_path = Path::new(&path);
89
90 if dir_path.is_dir() {
91 for entry in fs::read_dir(dir_path)? {
92 let entry = entry?;
93 let path = entry.path();
94
95 if path.is_file()
96 && path.file_name().map_or(false, |file| {
97 path.extension().map_or(true, |_| {
98 let file = file.to_string_lossy().to_lowercase();
99 for ext in ARCHIVE_EXTS.iter() {
100 if file.ends_with(&format!(".{}", ext)) {
101 return true;
102 }
103 }
104 false
105 })
106 })
107 {
108 if let Some(path_str) = path.to_str() {
109 result.push(path_str.to_string());
110 }
111 } else if recursive && path.is_dir() {
112 if let Some(path_str) = path.to_str() {
113 let mut sub_files = find_arc_files(&path_str.to_string(), recursive)?;
114 result.append(&mut sub_files);
115 }
116 }
117 }
118 }
119
120 Ok(result)
121}
122
123pub fn collect_files(
125 path: &str,
126 recursive: bool,
127 no_ext_filter: bool,
128) -> io::Result<(Vec<String>, bool)> {
129 let pa = Path::new(path);
130 if pa.is_dir() {
131 return Ok((find_files(path, recursive, no_ext_filter)?, true));
132 }
133 if pa.is_file() {
134 return Ok((vec![path.to_string()], false));
135 }
136 Err(io::Error::new(
137 io::ErrorKind::NotFound,
138 format!("Path {} is neither a file nor a directory", pa.display()),
139 ))
140}
141
142pub fn find_ext_files(path: &str, recursive: bool, exts: &[&str]) -> io::Result<Vec<String>> {
144 let mut result = Vec::new();
145 let dir_path = Path::new(&path);
146
147 if dir_path.is_dir() {
148 for entry in fs::read_dir(dir_path)? {
149 let entry = entry?;
150 let path = entry.path();
151
152 if path.is_file()
153 && path.file_name().map_or(false, |file| {
154 path.extension().map_or(true, |_| {
155 let file = file.to_string_lossy().to_lowercase();
156 for ext in exts {
157 if file.ends_with(&format!(".{}", ext)) {
158 return true;
159 }
160 }
161 false
162 })
163 })
164 {
165 if let Some(path_str) = path.to_str() {
166 result.push(path_str.to_string());
167 }
168 } else if recursive && path.is_dir() {
169 if let Some(path_str) = path.to_str() {
170 let mut sub_files = find_ext_files(path_str, recursive, exts)?;
171 result.append(&mut sub_files);
172 }
173 }
174 }
175 }
176
177 Ok(result)
178}
179
180pub fn collect_ext_files(
182 path: &str,
183 recursive: bool,
184 exts: &[&str],
185) -> io::Result<(Vec<String>, bool)> {
186 let pa = Path::new(path);
187 if pa.is_dir() {
188 return Ok((find_ext_files(path, recursive, exts)?, true));
189 }
190 if pa.is_file() {
191 return Ok((vec![path.to_string()], false));
192 }
193 Err(io::Error::new(
194 io::ErrorKind::NotFound,
195 format!("Path {} is neither a file nor a directory", pa.display()),
196 ))
197}
198
199pub fn collect_arc_files(path: &str, recursive: bool) -> io::Result<(Vec<String>, bool)> {
201 let pa = Path::new(path);
202 if pa.is_dir() {
203 return Ok((find_arc_files(path, recursive)?, true));
204 }
205 if pa.is_file() {
206 return Ok((vec![path.to_string()], false));
207 }
208 Err(io::Error::new(
209 io::ErrorKind::NotFound,
210 format!("Path {} is neither a file nor a directory", pa.display()),
211 ))
212}
213
214pub fn read_file<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<Vec<u8>> {
216 let mut content = Vec::new();
217 if f.as_ref() == Path::new("-") {
218 io::stdin().read_to_end(&mut content)?;
219 } else {
220 content = fs::read(f)?;
221 }
222 Ok(content)
223}
224
225pub fn write_file<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<Box<dyn Write>> {
227 Ok(if f.as_ref() == Path::new("-") {
228 Box::new(io::stdout())
229 } else {
230 Box::new(fs::File::create(f)?)
231 })
232}
233
234pub fn make_sure_dir_exists<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<()> {
236 let path = f.as_ref();
237 if let Some(parent) = path.parent() {
238 if !parent.exists() {
239 fs::create_dir_all(parent)?;
240 }
241 }
242 Ok(())
243}
244
245pub fn sanitize_path(path: &str) -> String {
247 if path.is_empty() {
249 return String::new();
250 }
251
252 let invalid_chars: &[char] = &['<', '>', '"', '|', '?', '*'];
253 let mut result = String::with_capacity(path.len());
254
255 let reserved_names: Vec<String> = {
256 let mut v = vec!["CON", "PRN", "AUX", "NUL"]
257 .into_iter()
258 .map(|s| s.to_string())
259 .collect::<Vec<_>>();
260 for i in 1..=9 {
261 v.push(format!("COM{}", i));
262 v.push(format!("LPT{}", i));
263 }
264 v
265 };
266
267 let bytes = path.as_bytes();
268 let len = bytes.len();
269 let mut start = 0usize;
270
271 while start < len {
272 let mut end = start;
274 while end < len && bytes[end] != b'\\' && bytes[end] != b'/' {
275 end += 1;
276 }
277 let seg = &path[start..end];
279
280 let mut s = String::with_capacity(seg.len());
282 for (i, ch) in seg.chars().enumerate() {
283 if ch == ':' {
285 if i == 1 {
286 if seg
288 .chars()
289 .next()
290 .map(|c| c.is_ascii_alphabetic())
291 .unwrap_or(false)
292 {
293 s.push(':');
294 continue;
295 }
296 }
297 s.push('_');
299 continue;
300 }
301 if (ch as u32) < 32 || invalid_chars.contains(&ch) {
304 s.push('_');
305 } else {
306 s.push(ch);
307 }
308 }
309
310 while s.ends_with(' ') || s.ends_with('.') {
312 s.pop();
313 }
314
315 if s.is_empty() {
316 s.push('_');
317 } else {
318 let base = s.split('.').next().unwrap_or("").to_ascii_uppercase();
320 if reserved_names.iter().any(|r| r == &base) {
321 s = format!("_{}", s);
322 }
323 }
324
325 result.push_str(&s);
326
327 if end < len {
329 result.push(path.as_bytes()[end] as char);
331 start = end + 1;
332 } else {
333 start = end;
334 }
335 }
336
337 result
338}