use async_trait::async_trait;
use crate::{regex::regex2, AnyRes as _, AnyResult, Error};
use std::{
borrow::Cow,
fs,
io::{self, Write as _},
path::{Path, PathBuf},
};
pub fn tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
let path = PathBuf::from(src.as_ref());
if path.is_file() {
return Ok(vec![path]);
}
let (target_dir, pattern) = if path.is_dir() {
(path, None)
} else if let Some(parent) = path.parent() {
if !parent.is_dir() {
return Err(format!("{} 不是一个有效的路径", parent.display()).into());
}
(parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
} else {
return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
};
let files = if is_next {
tree_folder(&target_dir)?
} else {
let mut list = Vec::new();
for entry in std::fs::read_dir(&target_dir)? {
list.push(entry?.path());
}
list
};
Ok(match pattern {
Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
None => files,
})
}
pub fn tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
where
P: AsRef<Path>,
{
let path = dir_path.as_ref();
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()).into());
}
let mut result = Vec::new();
if path.is_dir() {
result.extend(fs::read_dir(path)?.filter_map(|entry| entry.ok()).flat_map(|entry| {
let path = entry.path();
if path.is_dir() {
tree_folder(path).unwrap_or_default()
} else {
vec![path]
}
}));
} else {
result.push(path.to_path_buf());
}
Ok(result)
}
pub fn tree_from_str(src: impl AsRef<str>) -> Vec<String> {
src.as_ref().split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
}
pub fn regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
Ok(
fs::read_dir(src.as_ref())
.any()?
.filter_map(Result::ok)
.filter_map(|entry| {
let path = entry.path();
path
.is_file()
.then(|| {
let path_str = path.to_string_lossy().to_string();
let (is_success, res) = regex2(&path_str, pat);
if is_success {
res.map(|v| v.to_string())
} else {
Some(path_str)
}
})
.flatten()
})
.collect(),
)
}
pub fn rename_file<P, P2>(src: P, dst: P2) -> AnyResult<()>
where
P: AsRef<Path>,
P2: AsRef<Path>,
{
let src = src.as_ref();
let dst = dst.as_ref();
if src.exists() && src.is_file() {
if dst.exists() && !dst.is_file() {
Err(format!("目标已存在,并非文件格式 {}", dst.display()).into())
} else {
fs::rename(src, dst)?;
if dst.exists() && dst.is_file() {
Ok(())
} else {
Err(format!("源{} 目标移动失败 {}", src.display(), dst.display()).into())
}
}
} else {
Err(format!("原始缓存文件不存在 {}", src.display()).into())
}
}
pub fn convert_path(path_str: &str) -> String {
if cfg!(target_os = "windows") {
path_str.replace('/', "\\")
} else {
String::from(path_str)
}
}
pub fn auto_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> AnyResult<()> {
let from_path = from.as_ref();
let to_path = to.as_ref();
if from_path.is_file() {
fs::copy(from_path, to_path)?;
} else if from_path.is_dir() {
for entry in fs::read_dir(from_path)? {
let entry = entry?;
let from_entry_path = entry.path();
let to_entry_path = to_path.join(from_entry_path.file_name().ok_or(Error::Str("无法解析文件名".into()))?);
auto_copy(from_entry_path, to_entry_path)?;
}
} else {
return Err("Path is neither a file nor a directory".into());
}
Ok(())
}
fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
if !is_append && path.exists() {
}
let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path)?;
f.write_all(&bytes)?;
if is_sync {
f.sync_data()?;
}
Ok(())
}
#[cfg(feature = "encode")]
pub fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
if had_errors {
return Err("写入GBK失败".into());
} else {
write(path, bytes, is_sync, is_append)
}
}
#[cfg(feature = "encode")]
pub fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
if had_errors {
return Err("写入UTF-8失败".into());
} else {
write(path, bytes, is_sync, is_append)
}
}
#[cfg(feature = "tokio")]
pub mod a_sync {
use crate::{regex::regex2, AnyRes as _, AnyResult};
use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use tokio::{fs, io::AsyncWriteExt as _};
pub async fn a_tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
let path = PathBuf::from(src.as_ref());
if path.is_file() {
return Ok(vec![path]);
}
let (target_dir, pattern) = if path.is_dir() {
(path, None)
} else if let Some(parent) = path.parent() {
if !parent.is_dir() {
return Err(format!("{} 不是一个有效的路径", parent.display()).into());
}
(parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
} else {
return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
};
let files = if is_next {
a_tree_folder(&target_dir).await?
} else {
let mut list = Vec::new();
let mut dir = tokio::fs::read_dir(&target_dir).await?;
while let Some(entry) = dir.next_entry().await? {
list.push(entry.path());
}
list
};
Ok(match pattern {
Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
None => files,
})
}
pub async fn a_tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
where
P: AsRef<Path>,
{
Box::pin(async move {
let path = dir_path.as_ref();
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()).into());
}
let mut result = Vec::new();
if path.is_dir() {
let mut read_dir = tokio::fs::read_dir(path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let path = entry.path();
if path.is_dir() {
let sub_files = a_tree_folder(&path).await?;
result.extend(sub_files);
} else {
result.push(path);
}
}
} else {
result.push(path.to_path_buf());
}
Ok(result)
})
.await
}
pub async fn a_regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
let mut entries = tokio::fs::read_dir(src.as_ref()).await.any()?;
let mut results = Vec::new();
while let Some(entry) = entries.next_entry().await.any()? {
let path = entry.path();
if path.is_file() {
let path_str = path.to_string_lossy().to_string();
let (is_success, res) = regex2(&path_str, pat);
if is_success {
if let Some(v) = res {
results.push(v.to_string());
}
} else {
results.push(path_str);
}
}
}
Ok(results)
}
async fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
if !is_append && path.exists() {
fs::remove_file(path).await?
}
let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path).await?;
f.write_all(&bytes).await?;
if is_sync {
f.sync_data().await?;
}
Ok(())
}
#[cfg(feature = "encode")]
pub async fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
if had_errors {
return Err("异步写入GBK失败".into());
} else {
write(path, bytes, is_sync, is_append).await
}
}
#[cfg(feature = "encode")]
pub async fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
if had_errors {
return Err("异步写入UTF-8失败".into());
} else {
write(path, bytes, is_sync, is_append).await
}
}
}
#[async_trait]
pub trait AutoPath {
fn auto_create_dir(&self) -> AnyResult<()>;
fn auto_remove_dir(&self) -> AnyResult<()>;
fn auto_create_file<S: AsRef<str>>(&self, content: S) -> AnyResult<()>;
fn auto_remove_file(&self) -> AnyResult<()>;
#[cfg(feature = "tokio")]
async fn a_auto_create_dir(&self) -> AnyResult<()>;
#[cfg(feature = "tokio")]
async fn a_auto_remove_dir(&self) -> AnyResult<()>;
#[cfg(feature = "tokio")]
async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()>;
#[cfg(feature = "tokio")]
async fn a_auto_remove_file(&self) -> AnyResult<()>;
}
#[async_trait]
impl<T: AsRef<str> + Send + Sync> AutoPath for T {
fn auto_create_dir(&self) -> AnyResult<()> {
Path::new(self.as_ref()).auto_create_dir()
}
fn auto_remove_dir(&self) -> AnyResult<()> {
Path::new(self.as_ref()).auto_remove_dir()
}
fn auto_create_file<S>(&self, content: S) -> AnyResult<()>
where
S: AsRef<str>,
{
Path::new(self.as_ref()).auto_create_file(content)
}
fn auto_remove_file(&self) -> AnyResult<()> {
Path::new(self.as_ref()).auto_remove_file()
}
#[cfg(feature = "tokio")]
async fn a_auto_create_dir(&self) -> AnyResult<()> {
Path::new(self.as_ref()).a_auto_create_dir().await
}
#[cfg(feature = "tokio")]
async fn a_auto_remove_dir(&self) -> AnyResult<()> {
Path::new(self.as_ref()).a_auto_remove_dir().await
}
#[cfg(feature = "tokio")]
async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
Path::new(self.as_ref()).a_auto_create_file(content).await
}
#[cfg(feature = "tokio")]
async fn a_auto_remove_file(&self) -> AnyResult<()> {
Path::new(self.as_ref()).a_auto_remove_file().await
}
}
#[async_trait]
impl AutoPath for Path {
fn auto_create_dir(&self) -> AnyResult<()> {
if !self.exists() {
fs::create_dir_all(self)?;
if !self.exists() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
}
}
return Ok(());
}
fn auto_remove_dir(&self) -> AnyResult<()> {
if self.is_dir() {
fs::remove_dir_all(self)?;
if self.exists() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
} else if self.exists() {
return Err(format!("{} is not a directory", self.display()).into());
}
Ok(())
}
fn auto_create_file<S: AsRef<str>>(&self, data: S) -> AnyResult<()> {
if !self.exists() {
fs::write(self, data.as_ref())?;
} else if !self.is_file() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
Ok(())
}
fn auto_remove_file(&self) -> AnyResult<()> {
if self.exists() {
if self.is_file() {
fs::remove_file(self)?;
} else {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
}
Ok(())
}
#[cfg(feature = "tokio")]
async fn a_auto_create_dir(&self) -> AnyResult<()> {
if !self.exists() {
tokio::fs::create_dir_all(self).await?;
if !self.exists() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
}
}
return Ok(());
}
#[cfg(feature = "tokio")]
async fn a_auto_remove_dir(&self) -> AnyResult<()> {
if self.is_dir() {
tokio::fs::remove_dir_all(self).await?;
if self.exists() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
} else if self.exists() {
return Err(format!("{} is not a directory", self.display()).into());
}
Ok(())
}
#[cfg(feature = "tokio")]
async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
if !self.exists() {
tokio::fs::write(self, content.as_ref()).await?;
} else if !self.is_file() {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
Ok(())
}
#[cfg(feature = "tokio")]
async fn a_auto_remove_file(&self) -> AnyResult<()> {
if self.exists() {
if self.is_file() {
tokio::fs::remove_file(self).await?;
} else {
return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
}
}
Ok(())
}
}
pub fn temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
let path = std::env::temp_dir().join(folder.as_ref());
path.auto_create_dir()?;
let target = path.join(fname.as_ref());
target.auto_create_file(content.as_ref())?;
Ok(target)
}
#[cfg(feature = "tokio")]
pub async fn a_temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
let path = std::env::temp_dir().join(folder.as_ref());
path.a_auto_create_dir().await?;
let target = path.join(fname.as_ref());
target.a_auto_create_file(content.as_ref()).await?;
Ok(target)
}
#[cfg(test)]
mod auto_path_tests {
use super::*;
use std::fs;
#[test]
fn test_auto_create_dir() {
let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
assert!(path.exists() && path.parent().unwrap().is_dir());
}
#[test]
fn test_auto_remove_dir() {
let path = temp_file("test", "test_remove_dir", "Hello, World!").unwrap();
let parent = path.parent().unwrap();
parent.auto_remove_dir().unwrap();
assert!(!parent.exists());
}
#[test]
fn test_auto_create_file() {
let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
assert!(path.exists() && path.is_file());
assert_eq!(fs::read_to_string(&path).unwrap(), "Hello, World!");
}
#[test]
fn test_auto_remove_file() {
let path = temp_file("test", "test_remove_file.txt", "Test content").unwrap();
let parent = path.parent().unwrap();
parent.auto_remove_dir().unwrap();
assert!(!parent.exists());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_a_auto_create_dir() {
let path = temp_file("test", "test_async_dir", "Test content").unwrap();
path.a_auto_create_dir().await.unwrap();
assert!(path.exists() && path.is_dir());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_a_auto_remove_dir() {
let path = temp_file("test", "test_async_remove_dir", "Test content").unwrap();
let parent = path.parent().unwrap();
parent.a_auto_remove_dir().await.unwrap();
assert!(!parent.exists());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_a_auto_create_file() {
let path = temp_file("test", "test_async_file.txt", "Hello, Async World!").unwrap();
assert!(path.exists() && path.is_file());
assert_eq!(tokio::fs::read_to_string(&path).await.unwrap(), "Hello, Async World!");
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_a_auto_remove_file() {
let path = temp_file("test", "test_async_remove_file.txt", "Test async content").unwrap();
let parent = path.parent().unwrap();
parent.a_auto_remove_dir().await.unwrap();
assert!(!parent.exists());
}
}