项目地址

DevStrip 入门指南

快速开始

核心功能拆解

1. 命令行参数解析

2. 扫描配置构建

3. 目录遍历与候选收集

4. 扫描进度与交互体验

5. 清理执行与结果汇总

通过项目学习 Rust

后续扩展建议

Rust编程入门:devstrip Rust写的开发者缓存目录专清工具

路奇

2025-10-20

🏷

rust

本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。

部分内容可能无法直接访问,如果有需要可以加我们的群交流 我们会定期在群里分享最新的项目实战代码,包括不同语言的实现

老师还会详细讲解代码优化的思路,扫码加入实战群:

入群学习

项目地址

代码已经开源, devstrip 👏 Star

DevStrip 入门指南

DevStrip 是一个面向 macOS 的 Rust 命令行工具,帮助开发者清理久未使用的构建产物与缓存目录,从而快速回收磁盘空间。本文面向 Rust 初学者,通过 DevStrip 项目具体讲解如何构建一个实用的 CLI 应用,并给出后续扩展方向。

快速开始

  1. 获取源码或安装
    cargo install devstrip            # 从 crates.io 安装
    cargo install --path .            # 在仓库根目录执行,安装本地版本
  2. 查看帮助与运行
    devstrip --help                   # 查看全部命令行参数
    devstrip --dry-run                # 先进行预演,确认删除内容
    devstrip --yes                    # 跳过确认并直接清理
  3. 典型用法示例
    devstrip \
      --roots ~/Projects/demo ~/Work/company \
      --exclude ~/Projects/demo/ios-app \
      --keep-latest-derived 2 \
      --keep-latest-cache 3 \
      --min-age-days 5 --dry-run
    这条命令会扫描两个额外目录、排除指定仓库、保留最近的 DerivedData 与缓存,并删除 5 天以上未使用的候选目录

    (如果去掉--dry-run 换成-y 才会真的清理)

下面执行效果: result

核心功能拆解

1. 命令行参数解析

DevStrip 使用 clap 的派生宏快速构建 CLI 接口:

#[derive(Parser, Debug)]
struct Args {
    #[arg(long = "roots", value_name = "PATH", num_args = 1..)]
    roots: Vec<PathBuf>,
    #[arg(short = 'x', long = "exclude", value_name = "PATH")]
    excludes: Vec<PathBuf>,
    #[arg(long = "min-age-days", default_value_t = 2)]
    min_age_days: u64,
    #[arg(long = "dry-run")]
    dry_run: bool,
    #[arg(short = 'y', long = "yes")]
    yes: bool,
}
  • Vec<PathBuf> 支持重复传入路径参数,方便一次指定多个根目录或排除目录。
  • 默认值与别名通过属性一次定义,减少手写解析代码的负担。

2. 扫描配置构建

build_scan_config 负责展开 ~、合并默认目录、过滤排除项,并生成最终的扫描列表:

fn build_scan_config(args: &Args) -> Result<ScanConfig> {
    let mut roots = expand_paths(&args.roots);
    roots.extend(expand_paths(&args.positional_roots));
    let exclude_inputs = expand_paths(&args.excludes);
    let exclude_paths = normalize_paths(&exclude_inputs);
    let resolved_roots = default_roots(&roots, &exclude_paths)?;
    Ok(ScanConfig { roots: resolved_roots, exclude_paths, .. })
}
  • expand_paths~/Projects 这类路径转换成绝对路径。
  • default_roots 会自动加入当前目录以及 ~/Projects~/workspace 等常见开发文件夹,降低用户操作成本。

3. 目录遍历与候选收集

collect_matching_dirs 使用广度优先遍历(BFS)扫描项目目录,匹配常见的构建与缓存文件夹:

let mut queue: VecDeque<(PathBuf, u32)> = VecDeque::new();
queue.push_back((root.clone(), 0));

while let Some((current, depth)) = queue.pop_front() {
    if depth > max_depth || is_excluded(&current, excludes) {
        continue;
    }
    let entries = match fs::read_dir(&current) {
        Ok(iter) => iter,
        Err(_) => continue,
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if file_type.is_dir() && should_collect(&path, cutoff, &pattern_set) {
            let size = calculate_size(&path);
            candidates.push(Candidate { path, size_bytes: size, .. });
        } else if depth < max_depth {
            queue.push_back((path, depth + 1));
        }
    }
}
  • 通过深度限制避免深入无关目录。
  • should_collect 根据目录名与最后修改时间判断是否为目标缓存。
  • calculate_size 会跳过符号链接、防止递归引用导致的死循环。

4. 扫描进度与交互体验

run_with_spinner 将耗时扫描放入后台线程,并通过 mpsc::channel 实时更新主线程的状态指示:

let (status_tx, status_rx) = mpsc::channel::<String>();
let (result_tx, result_rx) = mpsc::channel::<Result<T>>();

thread::spawn(move || {
    let reporter = StatusReporter::channel(status_tx);
    let outcome = func(reporter);
    let _ = result_tx.send(outcome);
});

// 主线程轮询 status_rx 更新动画帧
  • 终端动画仅在支持时启用,避免在非交互环境(如日志或 CI)中产生杂乱输出。
  • StatusReporter 为扫描函数提供 update 接口,用于上报当前处理的目录。

5. 清理执行与结果汇总

cleanup_with_progress 遍历候选项、执行删除,并输出进度条与失败原因:

for (index, candidate) in candidates.iter().enumerate() {
    if styler.supports_animation {
        let bar = render_progress_bar(index + 1, total, 28);
        print!("\rCleaning [{}] {}/{} {}", bar, index + 1, total, candidate.display_name());
    }
    let (success, error) = match delete_path(&candidate.path) {
        Ok(_) => (true, None),
        Err(err) => (false, Some(err.to_string())),
    };
    results.push(CleanupResult { candidate: candidate.clone(), success, error });
}
  • 成功删除后统计总回收空间,失败项将逐一列出,便于后续人工介入。
  • --dry-run 模式下跳过删除动作,只展示潜在收益。

通过项目学习 Rust

  • 掌握 clapchronostd::fsstd::thread 等常用 crate 与标准库组件的结合方式。
  • 体验 Result<T, E>match 模式匹配在错误处理中的实际应用。
  • 学习如何封装终端输出(颜色、进度条)与跨线程通信(通道、共享状态)。
  • 尝试在 --dry-run 模式下运行并阅读源码中的 println! 调试输出来理解流程。

后续扩展建议

  1. Windows 支持:使用 dirs crate 获取平台相关缓存目录,针对 %LOCALAPPDATA%AppData\Local\Temp 等路径新增默认规则;处理 Windows 上只读文件的删除权限问题。
  2. 持久化排除列表:新增配置文件或环境变量,在 gather_candidates 中合并默认排除项,方便团队共享保护目录。
  3. 按类型清理:开放 --only <CATEGORY>--skip <CATEGORY> 选项,或支持读取 JSON/YAML 规则,让用户精确控制清理范围。
  4. 结果报告增强:将删除结果输出为 JSON/CSV,或集成到日志系统,便于在 CI/CD 或企业环境中审计。

通过研读 DevStrip,Rust 初学者不仅能理解一个完整 CLI 工具的结构,也能快速积累文件系统操作、并发编程与用户交互方面的实战经验。动手尝试实现上述扩展,将进一步巩固你对 Rust 生态的理解。

我们会定期在群里分享最新的项目实战代码,包括不同语言的实现

老师还会详细讲解代码优化的思路,扫码加入实战群:

入群学习
入职啦

心仪的工作马上入职啦

友情链接:

Copyright© 2024 杭州园中葵科技有限公司 版权所有