1use std::{
14 fs::{create_dir_all, File},
15 io::Write,
16 path::{Path, PathBuf},
17 str::FromStr,
18 thread,
19 time::Duration,
20};
21
22use crate::error::{Error, FsError, ParseError};
23
24use super::Source;
25
26pub fn exists_dir<P>(path: P) -> bool
27where
28 P: AsRef<Path>,
29{
30 let path = path.as_ref();
31 path.exists() && path.is_dir()
32}
33
34pub fn exists<P>(path: P) -> bool
40where
41 P: AsRef<Path>,
42{
43 path.as_ref().exists()
44}
45pub fn try_exists<P>(path: P) -> Result<bool, Error>
50where
51 P: AsRef<Path>,
52{
53 path.as_ref()
54 .try_exists()
55 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))
56}
57pub fn read<P>(path: P) -> Result<String, Error>
61where
62 P: AsRef<Path>,
63{
64 std::fs::read_to_string(path.as_ref()).map_err(|e| {
65 Error::Fs(FsError::Read {
66 path: path.as_ref().to_path_buf(),
67 reason: e.to_string(),
68 })
69 })
70}
71pub fn write<P>(path: P, content: &str) -> Result<(), Error>
79where
80 P: AsRef<Path>,
81{
82 if !path.as_ref().exists() {
84 create_file(path.as_ref())?;
85 }
86
87 std::fs::write(path.as_ref(), content).map_err(|e| {
88 Error::Fs(FsError::Write {
89 path: path.as_ref().to_path_buf(),
90 reason: e.to_string(),
91 })
92 })
93}
94pub fn append<P>(path: P, content: &str) -> Result<(), Error>
98where
99 P: AsRef<Path>,
100{
101 std::fs::OpenOptions::new()
102 .append(true)
103 .create(true)
104 .open(path.as_ref())
105 .and_then(|mut file| file.write_all(content.as_bytes()))
106 .map_err(|e| {
107 Error::Fs(FsError::Write {
108 path: path.as_ref().to_path_buf(),
109 reason: e.to_string(),
110 })
111 })
112}
113
114pub fn create_dir<P>(path: P) -> Result<(), Error>
117where
118 P: AsRef<Path>,
119{
120 let path = path.as_ref().to_path_buf();
121 std::fs::create_dir_all(path.as_path()).map_err(|e| {
122 FsError::Create {
123 path,
124 reason: e.to_string(),
125 }
126 .into()
127 })
128}
129
130pub fn exists_or_create_dir<P>(path: P) -> Result<(), Error>
131where
132 P: AsRef<Path>,
133{
134 if exists_dir(path.as_ref()) {
135 Ok(())
136 } else {
137 create_dir(path)
138 }
139}
140
141pub fn create<P>(path: P) -> Result<(), Error>
148where
149 P: AsRef<Path>,
150{
151 std::fs::File::create(path.as_ref())
152 .map(|_| ())
153 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))
154}
155pub fn delete<P>(path: P) -> Result<bool, Error>
160where
161 P: AsRef<Path>,
162{
163 std::fs::remove_file(path.as_ref()).map_or_else(
164 |e| {
165 if e.kind() == std::io::ErrorKind::NotFound {
166 Ok(false)
167 } else {
168 Err(Error::Fs(FsError::UnExpected(e.to_string())))
169 }
170 },
171 |()| Ok(true),
172 )
173}
174
175pub fn delete_dir<P>(path: P) -> Result<(), Error>
177where
178 P: AsRef<Path>,
179{
180 if exists_dir(path.as_ref()) {
181 std::fs::remove_dir_all(path.as_ref())
182 .map(|_| ())
183 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))
184 } else {
185 Ok(())
186 }
187}
188
189pub fn move_to<P, Q>(from: P, to: Q) -> Result<(), Error>
192where
193 P: AsRef<Path>,
194 Q: AsRef<Path>,
195{
196 copy(from.as_ref(), to.as_ref())?;
197 delete_dir(from)
198}
199
200pub fn copy<P, Q>(from: P, to: Q) -> Result<(), Error>
202where
203 P: AsRef<Path>,
204 Q: AsRef<Path>,
205{
206 for entry in walkdir::WalkDir::new(from.as_ref())
207 .into_iter()
208 .filter_map(|e| e.ok())
209 {
210 let path = entry.path();
211 let relative = path.strip_prefix(from.as_ref()).unwrap();
212 let target = to.as_ref().join(relative);
213 if path.is_dir() {
214 std::fs::create_dir_all(target)
215 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))?;
216 } else {
217 std::fs::copy(path, target)
218 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))?;
219 }
220 }
221 Ok(())
222}
223
224pub fn create_new<P>(path: P) -> Result<(), Error>
231where
232 P: AsRef<Path>,
233{
234 match delete(path.as_ref()) {
235 Ok(_) => create(path),
236 Err(e) => Err(e),
237 }
238}
239pub fn parse_to<T, P>(path: P) -> Result<T, Error>
243where
244 T: FromStr,
245 P: AsRef<Path>,
246{
247 read(path).and_then(|content| {
248 content
249 .parse::<T>()
250 .map_err(|_| ParseError::template(std::any::type_name::<T>()).into())
251 })
252}
253
254pub fn create_file<P>(path: P) -> Result<File, Error>
272where
273 P: AsRef<Path>,
274{
275 if let Some(parent_dir) = path.as_ref().parent() {
276 if !try_exists(parent_dir)? {
277 match create_dir_all(parent_dir) {
278 Ok(_) => {}
279 Err(e) => {
280 return Err(Error::Fs(FsError::Create {
281 path: parent_dir.to_path_buf(),
282 reason: e.to_string(),
283 }))
284 }
285 };
286 }
287 } else {
288 return Err(Error::Fs(FsError::UnExpected(
289 "Path has no parent directory".to_string(),
290 )));
291 }
292
293 File::create(path.as_ref()).map_err(|e| {
294 Error::Fs(FsError::Create {
295 path: path.as_ref().to_path_buf(),
296 reason: e.to_string(),
297 })
298 })
299}
300
301pub fn path_to_str<P>(path: P) -> String
305where
306 P: AsRef<Path>,
307{
308 path.as_ref()
309 .to_str()
310 .unwrap()
311 .replace("\\", "/")
312 .replace("//?/", "")
313}
314
315pub trait GenUIFs {
316 fn is_gen_file(&self) -> bool;
318 fn to_compiled<P>(
323 &self,
324 prefix: P,
325 source: P,
326 target: P,
327 is_delete: bool,
328 ) -> Result<PathBuf, Error>
329 where
330 P: AsRef<Path>;
331 fn to_compiled_from_source(&self, source: &Source) -> Result<PathBuf, Error>;
333 fn to_compiled_from_delete(&self, source: &Source) -> Result<PathBuf, Error>;
335 fn back_gen(&self) -> PathBuf;
337
338 fn widget_source(&self, source: &Source) -> Result<Source, Error>;
339}
340
341impl<P> GenUIFs for P
342where
343 P: AsRef<Path>,
344{
345 fn is_gen_file(&self) -> bool {
346 self.as_ref().is_file() && self.as_ref().extension().map_or(false, |ext| ext == "gen")
348 }
349
350 fn to_compiled<PT>(
351 &self,
352 prefix: PT,
353 source: PT,
354 target: PT,
355 is_delete: bool,
356 ) -> Result<PathBuf, Error>
357 where
358 PT: AsRef<Path>,
359 {
360 let mut path = self.as_ref().to_path_buf();
361 let mut compiled = prefix.as_ref().join(target.as_ref());
362
363 let flag = if is_delete {
364 self.as_ref().extension().map_or(false, |ext| ext == "gen")
365 } else {
366 self.is_gen_file()
367 };
368
369 if flag {
370 compiled = compiled.join("src");
372 path = path.with_extension("rs");
373 }
374
375 Ok(compiled.join(
376 path.strip_prefix(prefix.as_ref().join(source.as_ref()))
377 .map_err(|e| Error::Fs(FsError::UnExpected(e.to_string())))?,
378 ))
379 }
380
381 fn to_compiled_from_source(&self, source: &Source) -> Result<PathBuf, Error> {
382 self.to_compiled(&source.path, &source.from, &source.to, false)
383 }
384
385 fn to_compiled_from_delete(&self, source: &Source) -> Result<PathBuf, Error> {
386 self.to_compiled(&source.path, &source.from, &source.to, true)
387 }
388
389 fn back_gen(&self) -> PathBuf {
390 if self.as_ref().is_file() && self.as_ref().extension().map_or(false, |ext| ext == "rs") {
391 return self.as_ref().with_extension("gen");
392 }
393 self.as_ref().to_path_buf()
394 }
395
396 fn widget_source(&self, source: &Source) -> Result<Source, Error> {
397 let compiled_path = self.as_ref().to_compiled_from_source(source)?;
398 Ok(Source::new(
399 source.path.as_path(),
400 self.as_ref(),
401 compiled_path.as_path(),
402 ))
403 }
404}
405
406#[derive(Debug, Clone, Copy, PartialEq)]
411pub enum FileState {
412 Unchanged,
413 Modified,
414 Created,
415 Deleted,
416 Renamed,
417}
418
419impl FileState {
420 pub fn modify_then<T, F>(&self, default: T, f: F) -> Result<T, Error>
424 where
425 F: FnOnce() -> Result<T, Error>,
426 {
427 match self {
428 FileState::Modified | FileState::Created | FileState::Renamed | FileState::Deleted => {
429 f()
430 }
431 _ => Ok(default),
432 }
433 }
434 pub fn then<F>(&self, f: F) -> Result<(), Error>
435 where
436 F: FnOnce(&Self) -> Result<(), Error>,
437 {
438 f(&self)
439 }
440 pub fn is_modify(&self) -> bool {
441 !matches!(self, FileState::Unchanged)
442 }
443}
444
445pub fn copy_file<P, Q>(from: P, to: Q) -> Result<(), Error>
447where
448 P: AsRef<Path>,
449 Q: AsRef<Path>,
450{
451 if let Some(parent_dir) = to.as_ref().parent() {
453 if !parent_dir.exists() {
455 create_dir_all(parent_dir).map_err(|e| {
457 Error::Fs(FsError::Create {
458 path: parent_dir.to_path_buf(),
459 reason: e.to_string(),
460 })
461 })?;
462 }
463 }
464
465 copy_with_retries(from, to, 5, Duration::from_millis(200))
468}
469
470fn copy_with_retries<P, Q>(
472 from: P,
473 to: Q,
474 max_attempts: usize,
475 delay: Duration,
476) -> Result<(), Error>
477where
478 P: AsRef<Path>,
479 Q: AsRef<Path>,
480{
481 let mut attempts = 0;
482 loop {
483 match std::fs::copy(from.as_ref(), to.as_ref()) {
484 Ok(_) => return Ok(()),
485 Err(_) if attempts < max_attempts => {
486 attempts += 1;
487 thread::sleep(delay);
488 }
489 Err(e) => {
490 return Err(FsError::UnExpected(format!(
491 "Failed to copy file to compiled project: {}",
492 e.to_string()
493 ))
494 .into())
495 }
496 }
497 }
498}
499
500pub fn relative_with_prefix<P1, P2>(prefix: P1, path: P2) -> PathBuf
501where
502 P1: AsRef<Path>,
503 P2: AsRef<Path>,
504{
505 let path = path.as_ref();
506 if path.is_relative() {
507 prefix.as_ref().join(path)
508 } else {
509 path.to_path_buf()
510 }
511}
512
513#[cfg(test)]
514mod test_fs {
515 use std::path::PathBuf;
516
517 use super::*;
518
519 #[test]
520 fn test_create_file() {
521 let _res = create_file(
522 "E:/Rust/try/makepad/Gen-UI/examples/gen_makepad_simple/src_gen/src/views/root.rs",
523 );
524 }
525
526 #[test]
527 fn test_exists() {
528 let res = exists(PathBuf::new());
529 assert!(!res);
530 }
531 #[test]
532 fn test_try_exists() {
533 let res = try_exists(PathBuf::new());
534 assert!(res.is_err());
535 }
536}