use crate::auxiliary::DirGuard;
use crate::exec::{RET_DIR_ERROR, RET_ITEM_ERROR, entry_archive};
use clap::{ArgAction, Parser};
use glob::Pattern;
#[cfg(feature = "regex")]
use regex::Regex;
use std::fs::read_dir;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Args {
#[arg(value_name = "DIRECTORY")]
pub directory_start: PathBuf,
#[arg(short = 'n', long)]
pub dryrun: bool,
#[arg(short, long, value_name = "PATTERN", action = ArgAction::Append)]
pub exclude: Option<String>,
#[cfg(feature = "regex")]
#[arg(long, value_name = "PATTERN", action = ArgAction::Append)]
pub excludere: Option<String>,
#[arg(short = 'x', long)]
pub extract: bool,
#[arg(short, long)]
pub flag: bool,
#[arg(short, long, value_name = "PATTERN", action = ArgAction::Append)]
pub include: Option<String>,
#[cfg(feature = "regex")]
#[arg(long, value_name = "PATTERN", action = ArgAction::Append)]
pub includere: Option<String>,
#[arg(short, long, value_name = "LEVEL")]
pub leveldir: Option<u8>,
#[arg(short, long)]
pub preserve: bool,
#[arg(short, long)]
pub quiet: bool,
#[arg(short, long, value_name = "DIRECTORY")]
pub target: Option<PathBuf>,
#[arg(short, long)]
pub verbose: bool,
#[arg(short, long, value_name = "LEVEL")]
pub zstdlevel: Option<i32>,
}
pub fn cli() -> ExitCode {
let args = Args::parse();
match batch_archive(args) {
Ok(()) => ExitCode::SUCCESS,
Err(ret) => ExitCode::from(ret),
}
}
pub fn batch_archive(args: Args) -> Result<(), u8> {
let mut ret = 0;
let level_tree = args.leveldir.unwrap_or(4);
let compress = !args.extract;
let start_dir = &args.directory_start;
let _guard = DirGuard::new(start_dir)?;
let target_dir = if let Some(target) = &args.target {
Path::new(target)
} else {
start_dir.as_path()
};
match read_dir(start_dir) {
Ok(entries) => {
let entries: Vec<_> = entries.collect();
let mut valid_entries: Vec<_> = vec![];
for entry_result in entries.into_iter() {
match entry_result {
Ok(entry) => {
let file_path = entry.path();
#[cfg(feature = "regex")]
{
if should_process_file_with_regex(
&file_path,
&args.include,
&args.exclude,
&args.includere,
&args.excludere,
) {
valid_entries.push(entry.path());
}
}
#[cfg(not(feature = "regex"))]
{
if should_process_file_no_regex(
&file_path,
&args.include,
&args.exclude,
) {
valid_entries.push(entry.path());
}
}
}
Err(e) => {
eprintln!("出错了! Error: {e}");
return Err(RET_DIR_ERROR);
}
}
}
let total_items = valid_entries.len();
if total_items < 1 {
eprintln!("No item in {:?} to process.", &start_dir)
}
for (current_item, entry_path) in valid_entries.into_iter().enumerate() {
if entry_archive(
entry_path.as_path(),
compress,
args.preserve,
args.flag,
target_dir,
level_tree,
args.zstdlevel.unwrap_or(5_i32),
current_item + 1,
total_items,
args.dryrun,
) != Ok(())
{
ret = RET_ITEM_ERROR
}
}
}
Err(e) => eprintln!("出错了! Error: {e}"),
};
match ret {
0 => Ok(()),
_ => Err(ret),
}
}
#[cfg(feature = "regex")]
fn should_process_file_with_regex(
file_path: &Path,
include_patterns: &Option<String>,
exclude_patterns: &Option<String>,
include_regex_patterns: &Option<String>,
exclude_regex_patterns: &Option<String>,
) -> bool {
let file_name = file_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
if let Some(exclude) = exclude_patterns
&& let Ok(pattern) = Pattern::new(exclude)
&& pattern.matches(file_name)
{
return false;
}
if let Some(exclude_regex) = exclude_regex_patterns
&& let Ok(regex) = Regex::new(exclude_regex)
&& regex.is_match(file_name)
{
return false;
}
let mut should_include = false;
if let Some(include) = include_patterns {
if let Ok(pattern) = Pattern::new(include)
&& pattern.matches(file_name)
{
should_include = true;
}
} else {
should_include = true;
}
if let Some(include_regex) = include_regex_patterns
&& let Ok(regex) = Regex::new(include_regex)
&& regex.is_match(file_name)
{
should_include = true;
}
if include_patterns.is_none() && include_regex_patterns.is_none() {
should_include = true;
}
should_include
}
#[cfg(not(feature = "regex"))]
fn should_process_file_no_regex(
file_path: &Path,
include_patterns: &Option<String>,
exclude_patterns: &Option<String>,
) -> bool {
let file_name = file_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
if let Some(exclude) = exclude_patterns
&& let Ok(pattern) = Pattern::new(exclude)
&& pattern.matches(file_name)
{
return false;
}
if let Some(include) = include_patterns {
if let Ok(pattern) = Pattern::new(include)
&& pattern.matches(file_name)
{
return true;
}
} else {
return true;
}
false
}