1use async_trait::async_trait;
2
3use crate::{regex::regex2, AnyRes as _, AnyResult, Error};
4use std::{
5 borrow::Cow,
6 fs,
7 io::{self, Write as _},
8 path::{Path, PathBuf},
9};
10pub fn tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
12 let path = PathBuf::from(src.as_ref());
13
14 if path.is_file() {
16 return Ok(vec![path]);
17 }
18
19 let (target_dir, pattern) = if path.is_dir() {
21 (path, None)
22 } else if let Some(parent) = path.parent() {
23 if !parent.is_dir() {
24 return Err(format!("{} 不是一个有效的路径", parent.display()).into());
25 }
26 (parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
27 } else {
28 return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
29 };
30
31 let files = if is_next {
33 tree_folder(&target_dir)?
34 } else {
35 let mut list = Vec::new();
36 for entry in std::fs::read_dir(&target_dir)? {
37 list.push(entry?.path());
38 }
39 list
40 };
41
42 Ok(match pattern {
44 Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
45 None => files,
46 })
47}
48
49pub fn tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
51where
52 P: AsRef<Path>,
53{
54 let path = dir_path.as_ref();
55 if !path.exists() {
56 return Err(format!("Path does not exist: {}", path.display()).into());
57 }
58 let mut result = Vec::new();
59 if path.is_dir() {
60 result.extend(fs::read_dir(path)?.filter_map(|entry| entry.ok()).flat_map(|entry| {
61 let path = entry.path();
62 if path.is_dir() {
63 tree_folder(path).unwrap_or_default()
64 } else {
65 vec![path]
66 }
67 }));
68 } else {
69 result.push(path.to_path_buf());
70 }
71
72 Ok(result)
73}
74
75pub fn tree_from_str(src: impl AsRef<str>) -> Vec<String> {
77 src.as_ref().split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
78}
79pub fn regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
81 Ok(
82 fs::read_dir(src.as_ref())
83 .any()?
84 .filter_map(Result::ok)
85 .filter_map(|entry| {
86 let path = entry.path();
87 path
88 .is_file()
89 .then(|| {
90 let path_str = path.to_string_lossy().to_string();
91 let (is_success, res) = regex2(&path_str, pat);
92 if is_success {
93 res.map(|v| v.to_string())
94 } else {
95 Some(path_str)
96 }
97 })
98 .flatten()
99 })
100 .collect(),
101 )
102}
103pub fn rename_file<P, P2>(src: P, dst: P2) -> AnyResult<()>
105where
106 P: AsRef<Path>,
107 P2: AsRef<Path>,
108{
109 let src = src.as_ref();
110 let dst = dst.as_ref();
111 if src.exists() && src.is_file() {
112 if dst.exists() && !dst.is_file() {
113 Err(format!("目标已存在,并非文件格式 {}", dst.display()).into())
114 } else {
115 fs::rename(src, dst)?;
116 if dst.exists() && dst.is_file() {
117 Ok(())
118 } else {
119 Err(format!("源{} 目标移动失败 {}", src.display(), dst.display()).into())
120 }
121 }
122 } else {
123 Err(format!("原始缓存文件不存在 {}", src.display()).into())
124 }
125}
126pub fn convert_path(path_str: &str) -> String {
128 if cfg!(target_os = "windows") {
129 path_str.replace('/', "\\")
130 } else {
131 String::from(path_str)
132 }
133}
134
135pub fn auto_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> AnyResult<()> {
137 let from_path = from.as_ref();
138 let to_path = to.as_ref();
139
140 if from_path.is_file() {
141 fs::copy(from_path, to_path)?;
143 } else if from_path.is_dir() {
144 for entry in fs::read_dir(from_path)? {
147 let entry = entry?;
148 let from_entry_path = entry.path();
149 let to_entry_path = to_path.join(from_entry_path.file_name().ok_or(Error::Str("无法解析文件名".into()))?);
150 auto_copy(from_entry_path, to_entry_path)?;
152 }
153 } else {
154 return Err("Path is neither a file nor a directory".into());
156 }
157 Ok(())
158}
159
160fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
162 if !is_append && path.exists() {
163 }
165 let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path)?;
166 f.write_all(&bytes)?;
167 if is_sync {
168 f.sync_data()?;
169 }
170 Ok(())
171}
172#[cfg(feature = "encode")]
174pub fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
175 let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
176 if had_errors {
177 return Err("写入GBK失败".into());
178 } else {
179 write(path, bytes, is_sync, is_append)
180 }
181}
182#[cfg(feature = "encode")]
184pub fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
185 let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
186 if had_errors {
187 return Err("写入UTF-8失败".into());
188 } else {
189 write(path, bytes, is_sync, is_append)
190 }
191}
192#[cfg(feature = "tokio")]
194pub mod a_sync {
195 use crate::{regex::regex2, AnyRes as _, AnyResult};
196 use std::{
197 borrow::Cow,
198 path::{Path, PathBuf},
199 };
200 use tokio::{fs, io::AsyncWriteExt as _};
201
202 pub async fn a_tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
204 let path = PathBuf::from(src.as_ref());
205 if path.is_file() {
207 return Ok(vec![path]);
208 }
209 let (target_dir, pattern) = if path.is_dir() {
211 (path, None)
212 } else if let Some(parent) = path.parent() {
213 if !parent.is_dir() {
214 return Err(format!("{} 不是一个有效的路径", parent.display()).into());
215 }
216 (parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
217 } else {
218 return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
219 };
220
221 let files = if is_next {
223 a_tree_folder(&target_dir).await?
224 } else {
225 let mut list = Vec::new();
226 let mut dir = tokio::fs::read_dir(&target_dir).await?;
227 while let Some(entry) = dir.next_entry().await? {
228 list.push(entry.path());
229 }
230 list
231 };
232
233 Ok(match pattern {
235 Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
236 None => files,
237 })
238 }
239
240 pub async fn a_tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
242 where
243 P: AsRef<Path>,
244 {
245 Box::pin(async move {
246 let path = dir_path.as_ref();
247 if !path.exists() {
248 return Err(format!("Path does not exist: {}", path.display()).into());
249 }
250 let mut result = Vec::new();
251 if path.is_dir() {
252 let mut read_dir = tokio::fs::read_dir(path).await?;
253 while let Some(entry) = read_dir.next_entry().await? {
254 let path = entry.path();
255 if path.is_dir() {
256 let sub_files = a_tree_folder(&path).await?;
257 result.extend(sub_files);
258 } else {
259 result.push(path);
260 }
261 }
262 } else {
263 result.push(path.to_path_buf());
264 }
265 Ok(result)
266 })
267 .await
268 }
269
270 pub async fn a_regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
272 let mut entries = tokio::fs::read_dir(src.as_ref()).await.any()?;
273 let mut results = Vec::new();
274 while let Some(entry) = entries.next_entry().await.any()? {
275 let path = entry.path();
276 if path.is_file() {
277 let path_str = path.to_string_lossy().to_string();
278 let (is_success, res) = regex2(&path_str, pat);
279 if is_success {
280 if let Some(v) = res {
281 results.push(v.to_string());
282 }
283 } else {
284 results.push(path_str);
285 }
286 }
287 }
288
289 Ok(results)
290 }
291
292 async fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
294 if !is_append && path.exists() {
295 fs::remove_file(path).await?
296 }
297 let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path).await?;
298 f.write_all(&bytes).await?;
299 if is_sync {
300 f.sync_data().await?;
301 }
302 Ok(())
303 }
304 #[cfg(feature = "encode")]
306 pub async fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
307 let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
308 if had_errors {
309 return Err("异步写入GBK失败".into());
310 } else {
311 write(path, bytes, is_sync, is_append).await
312 }
313 }
314 #[cfg(feature = "encode")]
316 pub async fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
317 let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
318 if had_errors {
319 return Err("异步写入UTF-8失败".into());
320 } else {
321 write(path, bytes, is_sync, is_append).await
322 }
323 }
324}
325
326#[async_trait]
328pub trait AutoPath {
329 fn auto_create_dir(&self) -> AnyResult<()>;
345
346 fn auto_remove_dir(&self) -> AnyResult<()>;
362
363 fn auto_create_file<S: AsRef<str>>(&self, content: S) -> AnyResult<()>;
382
383 fn auto_remove_file(&self) -> AnyResult<()>;
399
400 #[cfg(feature = "tokio")]
420 async fn a_auto_create_dir(&self) -> AnyResult<()>;
421
422 #[cfg(feature = "tokio")]
442 async fn a_auto_remove_dir(&self) -> AnyResult<()>;
443
444 #[cfg(feature = "tokio")]
467 async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()>;
468
469 #[cfg(feature = "tokio")]
489 async fn a_auto_remove_file(&self) -> AnyResult<()>;
490}
491
492#[async_trait]
493impl<T: AsRef<str> + Send + Sync> AutoPath for T {
494 fn auto_create_dir(&self) -> AnyResult<()> {
495 Path::new(self.as_ref()).auto_create_dir()
496 }
497
498 fn auto_remove_dir(&self) -> AnyResult<()> {
499 Path::new(self.as_ref()).auto_remove_dir()
500 }
501
502 fn auto_create_file<S>(&self, content: S) -> AnyResult<()>
503 where
504 S: AsRef<str>,
505 {
506 Path::new(self.as_ref()).auto_create_file(content)
507 }
508
509 fn auto_remove_file(&self) -> AnyResult<()> {
510 Path::new(self.as_ref()).auto_remove_file()
511 }
512 #[cfg(feature = "tokio")]
513 async fn a_auto_create_dir(&self) -> AnyResult<()> {
514 Path::new(self.as_ref()).a_auto_create_dir().await
515 }
516 #[cfg(feature = "tokio")]
517 async fn a_auto_remove_dir(&self) -> AnyResult<()> {
518 Path::new(self.as_ref()).a_auto_remove_dir().await
519 }
520 #[cfg(feature = "tokio")]
521 async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
522 Path::new(self.as_ref()).a_auto_create_file(content).await
523 }
524 #[cfg(feature = "tokio")]
525 async fn a_auto_remove_file(&self) -> AnyResult<()> {
526 Path::new(self.as_ref()).a_auto_remove_file().await
527 }
528}
529#[async_trait]
530impl AutoPath for Path {
531 fn auto_create_dir(&self) -> AnyResult<()> {
532 if !self.exists() {
533 fs::create_dir_all(self)?;
534 if !self.exists() {
535 return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
536 }
537 }
538 return Ok(());
539 }
540 fn auto_remove_dir(&self) -> AnyResult<()> {
541 if self.is_dir() {
542 fs::remove_dir_all(self)?;
543 if self.exists() {
544 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
545 }
546 } else if self.exists() {
547 return Err(format!("{} is not a directory", self.display()).into());
548 }
549 Ok(())
550 }
551
552 fn auto_create_file<S: AsRef<str>>(&self, data: S) -> AnyResult<()> {
553 if !self.exists() {
554 fs::write(self, data.as_ref())?;
555 } else if !self.is_file() {
556 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
557 }
558 Ok(())
559 }
560
561 fn auto_remove_file(&self) -> AnyResult<()> {
562 if self.exists() {
563 if self.is_file() {
564 fs::remove_file(self)?;
565 } else {
566 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
567 }
568 }
569 Ok(())
570 }
571 #[cfg(feature = "tokio")]
572 async fn a_auto_create_dir(&self) -> AnyResult<()> {
573 if !self.exists() {
574 tokio::fs::create_dir_all(self).await?;
575 if !self.exists() {
576 return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
577 }
578 }
579 return Ok(());
580 }
581
582 #[cfg(feature = "tokio")]
583 async fn a_auto_remove_dir(&self) -> AnyResult<()> {
584 if self.is_dir() {
585 tokio::fs::remove_dir_all(self).await?;
586 if self.exists() {
587 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
588 }
589 } else if self.exists() {
590 return Err(format!("{} is not a directory", self.display()).into());
591 }
592 Ok(())
593 }
594
595 #[cfg(feature = "tokio")]
596 async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
597 if !self.exists() {
598 tokio::fs::write(self, content.as_ref()).await?;
599 } else if !self.is_file() {
600 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
601 }
602 Ok(())
603 }
604
605 #[cfg(feature = "tokio")]
606 async fn a_auto_remove_file(&self) -> AnyResult<()> {
607 if self.exists() {
608 if self.is_file() {
609 tokio::fs::remove_file(self).await?;
610 } else {
611 return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
612 }
613 }
614 Ok(())
615 }
616}
617pub fn temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
619 let path = std::env::temp_dir().join(folder.as_ref());
620 path.auto_create_dir()?;
621 let target = path.join(fname.as_ref());
622 target.auto_create_file(content.as_ref())?;
623 Ok(target)
624}
625#[cfg(feature = "tokio")]
627pub async fn a_temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
628 let path = std::env::temp_dir().join(folder.as_ref());
629 path.a_auto_create_dir().await?;
630 let target = path.join(fname.as_ref());
631 target.a_auto_create_file(content.as_ref()).await?;
632 Ok(target)
633}
634
635#[cfg(test)]
636mod auto_path_tests {
637 use super::*;
638 use std::fs;
639
640 #[test]
641 fn test_auto_create_dir() {
642 let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
643 assert!(path.exists() && path.parent().unwrap().is_dir());
644 }
645
646 #[test]
647 fn test_auto_remove_dir() {
648 let path = temp_file("test", "test_remove_dir", "Hello, World!").unwrap();
649 let parent = path.parent().unwrap();
650 parent.auto_remove_dir().unwrap();
651 assert!(!parent.exists());
652 }
653
654 #[test]
655 fn test_auto_create_file() {
656 let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
657 assert!(path.exists() && path.is_file());
658 assert_eq!(fs::read_to_string(&path).unwrap(), "Hello, World!");
659 }
660
661 #[test]
662 fn test_auto_remove_file() {
663 let path = temp_file("test", "test_remove_file.txt", "Test content").unwrap();
664 let parent = path.parent().unwrap();
665 parent.auto_remove_dir().unwrap();
666 assert!(!parent.exists());
667 }
668
669 #[cfg(feature = "tokio")]
670 #[tokio::test]
671 async fn test_a_auto_create_dir() {
672 let path = temp_file("test", "test_async_dir", "Test content").unwrap();
673 path.a_auto_create_dir().await.unwrap();
674 assert!(path.exists() && path.is_dir());
675 }
676
677 #[cfg(feature = "tokio")]
678 #[tokio::test]
679 async fn test_a_auto_remove_dir() {
680 let path = temp_file("test", "test_async_remove_dir", "Test content").unwrap();
681 let parent = path.parent().unwrap();
682 parent.a_auto_remove_dir().await.unwrap();
683 assert!(!parent.exists());
684 }
685
686 #[cfg(feature = "tokio")]
687 #[tokio::test]
688 async fn test_a_auto_create_file() {
689 let path = temp_file("test", "test_async_file.txt", "Hello, Async World!").unwrap();
690 assert!(path.exists() && path.is_file());
691 assert_eq!(tokio::fs::read_to_string(&path).await.unwrap(), "Hello, Async World!");
692 }
693
694 #[cfg(feature = "tokio")]
695 #[tokio::test]
696 async fn test_a_auto_remove_file() {
697 let path = temp_file("test", "test_async_remove_file.txt", "Test async content").unwrap();
698 let parent = path.parent().unwrap();
699 parent.a_auto_remove_dir().await.unwrap();
700 assert!(!parent.exists());
701 }
702}