feat(cps): initial commit
This commit is contained in:
118
src/main.rs
Normal file
118
src/main.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "cps")]
|
||||
#[command(about = "Recursively copy directory structures and specific files based on patterns.")]
|
||||
#[command(version, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(value_name = "SOURCE")]
|
||||
source: PathBuf,
|
||||
|
||||
#[arg(value_name = "DEST")]
|
||||
dest: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
recursive: bool,
|
||||
|
||||
#[arg(short = 'e', long = "extension", value_name = "PATTERN")]
|
||||
patterns: Vec<String>,
|
||||
|
||||
#[arg(short = 'd', long = "no-empty-dir", visible_alias = "prune")]
|
||||
prune: bool,
|
||||
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
if !cli.source.exists() {
|
||||
anyhow::bail!("Source path does not exist: {:?}", cli.source);
|
||||
}
|
||||
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
if !cli.patterns.is_empty() {
|
||||
for pattern in &cli.patterns {
|
||||
builder.add(Glob::new(pattern).context("Failed to compile glob pattern")?);
|
||||
}
|
||||
}
|
||||
let glob_set = builder.build().context("Failed to build glob set")?;
|
||||
let has_patterns = !cli.patterns.is_empty();
|
||||
|
||||
let max_depth = if cli.recursive { usize::MAX } else { 1 };
|
||||
|
||||
let walker = WalkDir::new(&cli.source)
|
||||
.max_depth(max_depth)
|
||||
.follow_links(false)
|
||||
.into_iter();
|
||||
|
||||
for entry in walker.filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
|
||||
if path == cli.source {
|
||||
continue;
|
||||
}
|
||||
|
||||
let relative_path = path.strip_prefix(&cli.source)?;
|
||||
let target_path = cli.dest.join(relative_path);
|
||||
|
||||
if path.is_dir() {
|
||||
if !cli.prune {
|
||||
create_dir(&target_path, cli.dry_run, cli.verbose)?;
|
||||
}
|
||||
} else if path.is_file() {
|
||||
if has_patterns {
|
||||
let filename = match path.file_name() {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if glob_set.is_match(filename) {
|
||||
if cli.prune {
|
||||
if let Some(parent) = target_path.parent() {
|
||||
create_dir(parent, cli.dry_run, cli.verbose)?;
|
||||
}
|
||||
}
|
||||
|
||||
copy_file(path, &target_path, cli.dry_run, cli.verbose)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_dir(path: &Path, dry_run: bool, verbose: bool) -> Result<()> {
|
||||
if !path.exists() {
|
||||
if verbose {
|
||||
println!("mkdir: {:?}", path);
|
||||
}
|
||||
if !dry_run {
|
||||
fs::create_dir_all(path).context(format!("Failed to create dir {:?}", path))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_file(src: &Path, dest: &Path, dry_run: bool, verbose: bool) -> Result<()> {
|
||||
if verbose {
|
||||
println!("copy: {:?} -> {:?}", src, dest);
|
||||
}
|
||||
if !dry_run {
|
||||
if let Some(parent) = dest.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(src, dest).context(format!("Failed to copy file {:?}", src))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user