Initial commit: Add cutPic, dexcel, and zip tools
This commit is contained in:
+257
@@ -0,0 +1,257 @@
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use walkdir::WalkDir;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
/// ZIP 打包工具 - 将文件或文件夹打包成 zip 格式
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "zip")]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(
|
||||
about = "📦 ZIP 打包工具 - 将文件或文件夹快速打包成 zip 格式",
|
||||
long_about = "📦 ZIP 打包工具\n\n将指定的文件或文件夹打包成 zip 压缩文件。\n支持单个文件、多个文件或整个文件夹的打包。\n\n示例:\n zip src\n zip file1.txt file2.txt -n archive.zip\n zip myfolder -v -n backup.zip"
|
||||
)]
|
||||
struct Args {
|
||||
/// 📁 要打包的文件或文件夹路径(可以指定多个)
|
||||
#[arg(required = true, value_name = "PATH")]
|
||||
sources: Vec<PathBuf>,
|
||||
|
||||
/// 📝 输出的 zip 文件名(默认自动生成)
|
||||
#[arg(short = 'n', long = "name", value_name = "FILE")]
|
||||
name: Option<PathBuf>,
|
||||
|
||||
/// 📋 复制 zip 文件路径到剪切板
|
||||
#[arg(short = 'v', long = "clipboard")]
|
||||
clipboard: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// 验证输入路径
|
||||
for source in &args.sources {
|
||||
if !source.exists() {
|
||||
eprintln!("❌ 错误: 路径不存在: {:?}", source);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成输出文件名
|
||||
let output_path = if let Some(name) = &args.name {
|
||||
name.clone()
|
||||
} else {
|
||||
// 根据第一个源文件/文件夹生成默认名称
|
||||
let first_source = &args.sources[0];
|
||||
let stem = first_source
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("archive");
|
||||
|
||||
let mut path = if args.sources.len() == 1 {
|
||||
first_source.parent().unwrap_or(std::path::Path::new(".")).to_path_buf()
|
||||
} else {
|
||||
std::env::current_dir()?
|
||||
};
|
||||
path.push(format!("{}.zip", stem));
|
||||
path
|
||||
};
|
||||
|
||||
println!("\n📦 开始打包...");
|
||||
println!("📂 源文件/文件夹: {} 个", args.sources.len());
|
||||
for (i, source) in args.sources.iter().enumerate() {
|
||||
println!(" {}. {:?}", i + 1, source);
|
||||
}
|
||||
println!("📄 输出文件: {:?}\n", output_path);
|
||||
|
||||
// 创建 zip 文件
|
||||
let zip_file = File::create(&output_path)?;
|
||||
let mut zip_writer = zip::ZipWriter::new(zip_file);
|
||||
|
||||
let options = SimpleFileOptions::default()
|
||||
.compression_method(zip::CompressionMethod::Deflated)
|
||||
.unix_permissions(0o755);
|
||||
|
||||
let mut file_count = 0;
|
||||
let mut total_size = 0u64;
|
||||
|
||||
// 遍历所有源路径
|
||||
for source in &args.sources {
|
||||
if source.is_file() {
|
||||
// 处理单个文件
|
||||
add_file_to_zip(&mut zip_writer, source, "", options, &mut file_count, &mut total_size)?;
|
||||
} else if source.is_dir() {
|
||||
// 处理文件夹
|
||||
add_dir_to_zip(&mut zip_writer, source, options, &mut file_count, &mut total_size)?;
|
||||
}
|
||||
}
|
||||
|
||||
zip_writer.finish()?;
|
||||
|
||||
println!("✅ 打包完成!");
|
||||
println!("📊 共打包 {} 个文件", file_count);
|
||||
println!("💾 文件大小: {}", format_bytes(total_size));
|
||||
println!("📍 保存位置: {:?}", output_path.canonicalize()?);
|
||||
|
||||
// 如果需要复制到剪切板
|
||||
if args.clipboard {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// 在 Windows 上复制文件到剪切板
|
||||
use clipboard_win::{formats::FileList, Setter};
|
||||
use winapi::um::winuser::{OpenClipboard, CloseClipboard, EmptyClipboard};
|
||||
use winapi::shared::windef::HWND;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// 获取绝对路径(不带 \\?\ 前缀)
|
||||
let abs_path = if output_path.is_absolute() {
|
||||
output_path.clone()
|
||||
} else {
|
||||
std::env::current_dir()?.join(&output_path)
|
||||
};
|
||||
|
||||
// 转换为标准字符串格式
|
||||
let path_str = abs_path.to_string_lossy().to_string();
|
||||
let paths: Vec<&str> = vec![path_str.as_str()];
|
||||
|
||||
// 尝试多次打开剪贴板并复制文件
|
||||
for attempt in 0..5 {
|
||||
unsafe {
|
||||
// 尝试打开剪贴板
|
||||
if OpenClipboard(std::ptr::null_mut() as HWND) != 0 {
|
||||
// 清空剪贴板
|
||||
EmptyClipboard();
|
||||
|
||||
// 尝试写入文件列表
|
||||
match FileList.write_clipboard(&paths) {
|
||||
Ok(_) => {
|
||||
println!("📋 已复制文件到剪切板: {}", path_str);
|
||||
CloseClipboard();
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
CloseClipboard();
|
||||
if attempt < 4 {
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
} else {
|
||||
eprintln!("⚠️ 复制文件失败: {}", e);
|
||||
eprintln!("💡 提示: 请手动复制文件或使用资源管理器打开该位置");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if attempt < 4 {
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
} else {
|
||||
eprintln!("⚠️ 无法打开剪贴板,可能被其他程序占用");
|
||||
eprintln!("💡 提示: 请关闭其他使用剪贴板的程序后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let canonical_path = output_path.canonicalize()?;
|
||||
let path_str = canonical_path.to_string_lossy().to_string();
|
||||
use arboard::Clipboard;
|
||||
match Clipboard::new() {
|
||||
Ok(mut clipboard) => {
|
||||
match clipboard.set_text(path_str.clone()) {
|
||||
Ok(_) => println!("📋 已复制路径到剪切板: {}", path_str),
|
||||
Err(e) => eprintln!("⚠️ 复制到剪切板失败: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("⚠️ 无法访问剪切板: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 将单个文件添加到 zip
|
||||
fn add_file_to_zip(
|
||||
writer: &mut zip::ZipWriter<File>,
|
||||
file_path: &std::path::Path,
|
||||
base_path: &str,
|
||||
options: SimpleFileOptions,
|
||||
file_count: &mut usize,
|
||||
total_size: &mut u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file_name = if base_path.is_empty() {
|
||||
file_path.file_name().unwrap().to_string_lossy().to_string()
|
||||
} else {
|
||||
format!("{}/{}", base_path, file_path.file_name().unwrap().to_string_lossy())
|
||||
};
|
||||
|
||||
writer.start_file(file_name, options)?;
|
||||
|
||||
let mut file = File::open(file_path)?;
|
||||
let file_size = file.metadata()?.len();
|
||||
*total_size += file_size;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer)?;
|
||||
writer.write_all(&buffer)?;
|
||||
|
||||
*file_count += 1;
|
||||
println!(" ✓ {}", file_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 将整个文件夹添加到 zip
|
||||
fn add_dir_to_zip(
|
||||
writer: &mut zip::ZipWriter<File>,
|
||||
dir_path: &std::path::Path,
|
||||
options: SimpleFileOptions,
|
||||
file_count: &mut usize,
|
||||
total_size: &mut u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for entry in WalkDir::new(dir_path).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file() {
|
||||
// 计算相对路径
|
||||
let relative_path = path.strip_prefix(dir_path.parent().unwrap_or(dir_path))?;
|
||||
let file_name = relative_path.to_string_lossy().replace('\\', "/");
|
||||
|
||||
writer.start_file(file_name.clone(), options)?;
|
||||
|
||||
let mut file = File::open(path)?;
|
||||
let file_size = file.metadata()?.len();
|
||||
*total_size += file_size;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer)?;
|
||||
writer.write_all(&buffer)?;
|
||||
|
||||
*file_count += 1;
|
||||
println!(" ✓ {}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 格式化字节大小
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = KB * 1024;
|
||||
const GB: u64 = MB * 1024;
|
||||
|
||||
if bytes >= GB {
|
||||
format!("{:.2} GB", bytes as f64 / GB as f64)
|
||||
} else if bytes >= MB {
|
||||
format!("{:.2} MB", bytes as f64 / MB as f64)
|
||||
} else if bytes >= KB {
|
||||
format!("{:.2} KB", bytes as f64 / KB as f64)
|
||||
} else {
|
||||
format!("{} B", bytes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user