[Developing] 有关 cargo-wipe的源代码阅读,和将来的Julia重构

写在前面

大家好,百忙之中抽出点时间来投入自己喜欢的东西中,比学习考研容易多了,就当是个消遣吧
上次不是组了个队,打算用 Julia 重写一些软件吗,结果拖了好久都没有完成,失败的原因有很多,

  1. 将项目的重写误认为是简单的语言替换
  2. 对于理解代码,先是无脑的重新对着仓库敲一遍代码,费时费力,很不讨好
  3. 没有对项目代码的结构有清晰的认识

因此,只好放弃这个项目
沉寂许久后,我打算一步一步来,我的想法是这样,先解决代码的结构问题,结构弄完后,接下来就简单了,自己用熟悉的语言重新写一遍就好了
这次的项目我自己一个人来,就不麻烦其他人了
这里只完成了 代码结构 的问题,还没有完成 代码重构 的问题,要是一起发的话可能要几个月后了,所以就慢慢发吧
当然,如果你从这篇文章中理解了 cargo-wipe 的结构,你也可以尝试写一个自己的 wipe 程序

代码结构

这个代码库的地址在 https://github.com/mihai-dinculescu/cargo-wipe

第三方包功能

anyhow

提供统一的错误处理机制,简化错误类型的创建和传播

num-format

用于数字格式化,可以将数字转换为带有 千位分隔符 ,货币符号等的字符串

number_prefix

提供数字前缀的格式化,如 将数字转换为带有 K(千) ,M(败亡) ,G(十亿) 等单位的表示

clap

命令行参数解析库,支持自动生成帮助信息,使开发者能够轻松地定义和解析命令行参数

yansi

控制台颜色输出库,用于在终端中输出带颜色文本

parameterized

单元测试框架扩展,用于编写参数化的测试用例,提高测试覆盖率和效率

rand

随机数e生成库,提供多种随机数生成器和分布,常用于模拟,游戏,加密等领域

程序入口

声明

use std::io::stdout;

use clap::StructOpt;

pub mod command;
pub mod dir_helpers;
pub mod wipe;

use crate::{
    command::Command,
    wipe::{Wipe, WipeParams},
};

程序引入了

  1. std::io::stdout
  2. clap::StructOpt
  3. command::Command
    本项目中 command 模块下的 Command 结构体
  4. wipe::{Wipe, WipeParams}
    本项目中 wipe 模块下的 WipeWipeParams 结构体

程序声明了以下文件模块

  1. command
  2. dir_helper
  3. wipe

主函数

主函数的功能是调用 clap 模块的,分发自 StructOpt 宏的结构体的 from_args() 函数生成 Command 结构体,通过模式匹配 Command 结构体来运行处理程序

fn main() -> anyhow::Result<()> {
    let mut stdout = stdout();
    let command: Command = Command::from_args();

    match command {
        Command::Wipe(args) => {
            let params: WipeParams = WipeParams::new(&args)?;
            Wipe::new(&mut stdout, &params).run()?;
        }
    }

    Ok(())
}

在错误处理上,主函数简单粗暴的使用 anyhow::Result<()> 来作为返回类型,这样可以简单的使用 ? 来传递 Result 类型,在函数的最后使用 Ok(()) 来保证函数能正确退出

command 模块

command 模块首先声明了三个结构体,这里我根据依赖顺序从先到后来说明一下

  1. LanguageEnum
  2. Args
  3. Command

还有一个独立的枚举类 DirectoryEnum

然后对 LanguageEnum 实现了

  1. str::FromStr
  2. fmt::Display

又对 DirectoryEnum 实现了

  1. From<LanguageEnum>
  2. fmt::Display

LanguageEnum 枚举

#[derive(Debug, PartialEq, Eq, Clone, StructOpt)]
pub enum LanguageEnum {
    #[structopt(name = "node_modules")]
    NodeModules,
    Node,
    Target,
    Rust,
}

枚举成员的 structopt 属性允许将命令行参数映射到枚举值,使得在命令行界面中可以方便地识别用户指定的语言或目录类型
例如,当用户运行 cargo wipe rust 时,命令行解析器会将 rust 参数转换为 LanguageEnum::Rust 值,从而决定执行哪些操作或处理哪个目录类型

  • NodeModules
    具有 structopt 属性,其 name 字段设置为 "nodemodules"
    这意味着当从命令行传递参数时,用户可以指定 node_modules 作为选项。这通常是指在 JavaScript 项目中由 Node.js 使用的依赖管理目录。
  • Node
    没有附加属性,代表 Node.jsJavaScript 相关的目录或配置
  • Target
    同样没有附加属性,通常在 Rust 项目中指代由 Cargo 构建系统生成的目标二进制和编译产物的目录
  • Rust
    没有附加属性,代表 Rust 语言相关的内容,可能用来标识 Rust 项目或配置

Args 结构体

#[derive(Debug, StructOpt)]
pub struct Args {
    pub language: LanguageEnum,

    #[structopt(short, long)]
    pub wipe: bool,

    #[structopt(short, long, parse(from_os_str))]
    pub ignores: Vec<path::PathBuf>
}
  • #[derive(Debug, StructOpt)]
    这个宏注解使得 Command 枚举能够被 structopt 解析器识别和处理, Debug 派生允许枚举的实例在调试时被打印出来

  • wipe: bool
    控制是否真正执行删除操作,此字段可以通过命令行参数 -w--wipe 来设置

  • ignores: Vec<path::PathBuf>
    这个字段用来存储用户指定的绝对路径列表,这些路径在清理过程中应该被忽略

Command 枚举

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
pub enum Command {
    Wipe(Args),
}
  • #[derive(Debug, StructOpt)]
    这个宏注解使得 Command 枚举能够被 structopt 解析器识别和处理, Debug 派生允许枚举的实例在调试时被打印出来

  • #[structopt(bin_name = "cargo")]
    这个注解告诉 structopt 解析器,这个枚举定义的命令应该作为 cargo 二进制程序的子命令,这使得用户可以想使用其他 cargo 子命令一样使用 cargo wipe

  • Wipe(Args)
    Wipe 只携带一个 Args 结构体的实例,这意味着当用户调用 cargo wipe 时, structopt 会解析用户提供的参数,并将它们转换为 Args 结构体中的字段值

DirectoryEnum

#[derive(Debug, PartialEq, Eq)]
pub enum DirectoryEnum {
    NodeModules,
    Target,
}

这个不用说了吧

impl str::FromStr for LanguageEnum

这个 trait 的实现是把字符串转换成 Result<LanguageEnum>

impl fmt::Display for LangaugeEnum

这个 trait 的实现是把 LanguageEnum 转换为字符串,结构虽然是 Result ,但是没有错误的案例

impl From for DirectoryEnum

LanguageEnum 转换为 DirectoryEnum

impl fmt::Display for DirectoryEnum

Directory 转换成字符串

dir_helper 模块

DirInfo 结构体

这个模块声明了一个描述目录信息的 DirInfo 结构体

#[derive(Debug, Copy, Clone)]
pub struct DirInfo {
    pub dir_count: usize,
    pub file_count: usize,
    pub size: usize,
}

其中,

  • dir_count 表示目录个数
  • file_count 表示文件数量
  • size 表示大小,单位是byte

对于这个结构体,实现了这样几个函数

  1. fn new(dir_count: usize, file_count: usize, size: usize) -> Self
    这相当于其他语言中的构造函数,不是成员函数
  2. fn file_count_formatted(&self) -> String
    将文件数量格式化成字符串,格式是 Locale::en
  3. fn size_formatted_mb(&self) -> String
    将大小格式化为 mb 的大小
  4. fn size_formatted_flex(&self) -> Striing
    根据 size 的大小来动态决定输出的字符串

辅助类型别名

pub type PathsResult = io::Result<Vec<Result<String, io::Error>>>;

独立函数

  1. getpathstodelete

    这个函数的参数有

    • path: impl Into<PathBuf>
    • directory: &DirectoryEnum

    这个函数返回了 PathsResult ,有点复杂

    这个函数的作用是,根据 directory 参数,从指定的 path 中提取路径
    细节方面,这个函数使用了 fs::read_dir 来读取一个路径的属性,从而来获取内部下一个文件夹的属性

  2. dirsize

    这个函数的参数仅有一个

    • path: impl Into<PathBuf>

    返回的结果是

    • io::Result<DirInfo>

    这个函数用来统计一个文件夹中的文件夹数,文件数和总大小,返回一个 DirInfo

    细节方面,他也是使用 fs::read_dir 来读取一个路径的属性,从而来获取内部下一个文件夹的属性

wipe 模块

WipeParams

#[derive(Debug, PartialEq, Eq)]
pub struct WipeParams {
    pub wipe: bool,
    pub path: PathBuf,
    pub language: LanguageEnum,
    pub ignores: Vec<PathBuf>,
}

其中,

  • wipe: bool
    表示是否实际删除找到的路径
  • path: PathBuf
    当前目录的路径
  • language: LangaugeEnum
    指示要查找哪种类型的目录
  • ignores: Vec<PathBuf>
    表示要忽略的目录列表

Wipe

#[derive(Debug)]
pub struct Wipe<'a, W>
where
    W: io::Write,
{
    stdout: &'a mut W,
    params: &'a WipeParams,
    previous_info: Option<DirInfo>,
    wipe_info: Option<DirInfo>,
    ignore_info: Option<DirInfo>,
}

这里

  • stdout
    用于表示输出的写入者对象
  • params
    指向 WipeParams 的引用,包含是否擦除的参数
  • previous_info: Option<DirInfo>
    存储擦除前的目录信息
  • wipe_info: Option<DirInfo>
    存储被擦除的目录信息
  • ignore_info: Option<DirInfo>
    存储被忽略的目录信息

impl WipeParams

这里提供了一个构造函数 new ,可以传入 &Args 参数,这个 Args 参数类型在上面有提到过,在 command 模块中

#[derive(Debug, StructOpt)]
pub struct Args {
    /// rust | node
    pub language: LanguageEnum,
    /// Caution! If set it will wipe all folders found! Unset by default
    #[structopt(short, long)]
    pub wipe: bool,
    /// Absolute paths to ignore
    #[structopt(short, long, parse(from_os_str))]
    pub ignores: Vec<path::PathBuf>,
}

impl Wipe

  1. new

    初始化 Wipe 实例,接收一个写入者对象和 WipeParams 引用

  2. run

    调用 写入头写入内容写入脚 的方法来执行整个擦除过程

  3. write_header

    写入擦除命令的头部信息

  4. write_content

    获取要删除的目录列表,遍历每个目录,输出相关信息,并根据配置执行删除或忽略操作

  5. write_summary

    输出擦除前后目录的信息摘要,包括被忽略和被擦除的目录

  6. write_footer

    写入擦除命令的结束信息,包括下一步操作和确认信息

  7. write_space_line

    写入一个空行,相当于换行