use std::path::{Component, Path, PathBuf};
use anyhow::{ensure, Context};
use rayon::iter::{ParallelBridge, ParallelIterator};
use crate::{
path::{canonicalize, simplified},
Result,
};
pub fn ls<'a, P: AsRef<Path> + 'a, T: IntoIterator<Item = &'a P>>(
dirs: T,
) -> Result<Vec<String>> {
let mut entries = vec![];
for entry in dirs {
let entry = simplified(entry.as_ref());
ensure!(
entry.exists(),
"ls item `{}` does not exist",
entry.display()
);
if entry.is_file() {
entries.push(format!("{}", entry.display()));
} else {
for entry in entry.read_dir()?.flatten() {
let path = entry.path();
entries.push(simplified(&path).to_string_lossy().to_string());
}
}
}
Ok(entries)
}
pub fn rm<'a, P: AsRef<Path> + 'a, T: IntoIterator<Item = &'a P>>(
entries: T,
force: bool,
) -> Result<()> {
for entry in entries {
let entry = entry.as_ref();
if !entry.exists() {
continue;
}
if entry.is_file() {
std::fs::remove_file(entry)?;
continue;
}
if entry.is_dir() {
if force {
std::fs::remove_dir_all(entry)?;
} else {
std::fs::remove_dir(entry)?;
}
}
}
Ok(())
}
pub fn mkdir<'a, P: AsRef<Path> + 'a, T: IntoIterator<Item = &'a P>>(
dirs: T,
) -> Result<()> {
for dir in dirs {
let dir = dir.as_ref();
std::fs::create_dir_all(dir)?;
}
Ok(())
}
pub fn abs<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let path = path.as_ref();
if path.exists() {
return Ok(canonicalize(path)?);
}
let path = if path.is_relative() {
std::env::current_dir()?.join(path)
} else {
path.to_path_buf()
};
let mut components = path.components().peekable();
let mut ret =
if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
Ok(ret)
}
#[derive(Debug, Clone)]
pub struct Copier {
src: Vec<PathBuf>,
dst: PathBuf,
includes: Vec<String>,
excludes: Vec<String>,
follow_links: bool,
}
impl Copier {
pub fn run(&self) -> Result<()> {
let should_create_dst = match self.src.len() {
0 => return Ok(()),
1 => !self.src[0].is_file(),
_ => true,
};
if should_create_dst && !self.dst.exists() {
mkdir(&[self.dst.as_path()])?;
}
for src in self.src.iter() {
let src_ref = src.as_path();
ensure!(src_ref.exists(), "{} does not exist", src_ref.display());
let dst_ref = self.dst.as_path();
walkdir::WalkDir::new(src_ref)
.follow_links(self.follow_links)
.into_iter()
.par_bridge()
.flatten()
.filter(|d| d.file_type().is_file())
.try_for_each(|entry| -> Result<()> {
let source_file = entry.path();
if !is_interested_file(
src_ref,
source_file,
&self.includes,
&self.excludes,
) {
return Ok(());
}
let dest_file = if src_ref == source_file {
dst_ref.join(source_file.file_name().context(
format!(
"get file name of {}",
source_file.display()
),
)?)
} else {
dst_ref.join(source_file.strip_prefix(src_ref)?)
};
let dest_file_dir = dest_file.parent().context(format!(
"failed to get parent directory from {}",
dest_file.display()
))?;
if !dest_file_dir.exists() {
std::fs::create_dir_all(dest_file_dir).context(
format!(
"failed to create directory {}",
dest_file_dir.display()
),
)?;
}
std::fs::copy(source_file, &dest_file).context(format!(
"failed to copy {} to {}",
source_file.display(),
dest_file.display(),
))?;
Ok(())
})?;
}
Ok(())
}
}
pub struct CopierBuilder {
cwd: Option<PathBuf>,
copier: Copier,
}
impl CopierBuilder {
pub fn new<P: AsRef<Path>>(dst: P) -> Self {
CopierBuilder {
cwd: None,
copier: Copier {
src: vec![],
dst: dst.as_ref().to_path_buf(),
includes: vec![],
excludes: vec![],
follow_links: false,
},
}
}
pub fn cwd<P: AsRef<Path>>(mut self, cwd: P) -> Self {
self.cwd = Some(cwd.as_ref().to_path_buf());
self
}
pub fn follow_links(mut self, yes: bool) -> Self {
self.copier.follow_links = yes;
self
}
pub fn add<P: AsRef<Path>>(mut self, src: P) -> Result<Self> {
let src = src.as_ref();
let src = if src.is_relative() {
if let Some(ref cwd) = self.cwd {
cwd.join(src)
} else {
std::env::current_dir()?.join(src)
}
} else {
src.to_path_buf()
};
let src = simplified(&src);
for src in glob::glob(&format!("{}", src.display()))?.flatten() {
ensure!(
src.exists(),
"source file {} does not exist",
src.display()
);
self.copier.src.push(src);
}
Ok(self)
}
pub fn ipat<P: AsRef<str>>(mut self, pat: P) -> Self {
self.copier.includes.push(pat.as_ref().to_string());
self
}
pub fn epat<P: AsRef<str>>(mut self, epat: P) -> Self {
self.copier.includes.push(epat.as_ref().to_string());
self
}
pub fn build(&self) -> Copier {
self.copier.clone()
}
}
pub fn is_interested_file<
R: AsRef<Path>,
F: AsRef<Path>,
I: AsRef<str>,
E: AsRef<str>,
>(
root: R,
file_path: F,
include_patterns: &[I],
exclude_patterns: &[E],
) -> bool {
let root = root.as_ref();
let file_path = file_path.as_ref();
if !file_path.is_file() {
return false;
}
let relative_file_path = if file_path.is_absolute() {
let Ok(p) = file_path.strip_prefix(root) else {
return false;
};
p
} else {
file_path
};
let options = glob::MatchOptions {
case_sensitive: !cfg!(windows),
require_literal_separator: false,
require_literal_leading_dot: false,
};
if !exclude_patterns.is_empty() {
for pat in exclude_patterns {
let pat = pat.as_ref();
if Path::new(pat).is_absolute() {
continue;
}
let Ok(pat) = glob::Pattern::new(pat) else {
continue;
};
if pat.matches_path_with(relative_file_path, options) {
return false;
}
}
return true;
}
if !include_patterns.is_empty() {
for pat in include_patterns {
let pat = pat.as_ref();
if Path::new(pat).is_absolute() {
continue;
}
let Ok(pat) = glob::Pattern::new(pat) else {
continue;
};
if pat.matches_path_with(relative_file_path, options) {
return true;
}
}
return false;
}
true
}