写在前面
大家好,百忙之中抽出点时间来投入自己喜欢的东西中,比学习考研容易多了,就当是个消遣吧
上次不是组了个队,打算用 Julia 重写一些软件吗,结果拖了好久都没有完成,失败的原因有很多,
- 将项目的重写误认为是简单的语言替换
- 对于理解代码,先是无脑的重新对着仓库敲一遍代码,费时费力,很不讨好
- 没有对项目代码的结构有清晰的认识
因此,只好放弃这个项目
沉寂许久后,我打算一步一步来,我的想法是这样,先解决代码的结构问题,结构弄完后,接下来就简单了,自己用熟悉的语言重新写一遍就好了
这次的项目我自己一个人来,就不麻烦其他人了
这里只完成了 代码结构 的问题,还没有完成 代码重构 的问题,要是一起发的话可能要几个月后了,所以就慢慢发吧
当然,如果你从这篇文章中理解了 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},
};
程序引入了
std::io::stdout
clap::StructOpt
command::Command
本项目中command
模块下的Command
结构体wipe::{Wipe, WipeParams}
本项目中wipe
模块下的Wipe
和WipeParams
结构体
程序声明了以下文件模块
command
dir_helper
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, ¶ms).run()?;
}
}
Ok(())
}
在错误处理上,主函数简单粗暴的使用 anyhow::Result<()>
来作为返回类型,这样可以简单的使用 ?
来传递 Result
类型,在函数的最后使用 Ok(())
来保证函数能正确退出
command 模块
command
模块首先声明了三个结构体,这里我根据依赖顺序从先到后来说明一下
LanguageEnum
Args
Command
还有一个独立的枚举类 DirectoryEnum
然后对 LanguageEnum
实现了
str::FromStr
fmt::Display
又对 DirectoryEnum
实现了
From<LanguageEnum>
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.js
或JavaScript
相关的目录或配置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
对于这个结构体,实现了这样几个函数
fn new(dir_count: usize, file_count: usize, size: usize) -> Self
这相当于其他语言中的构造函数,不是成员函数fn file_count_formatted(&self) -> String
将文件数量格式化成字符串,格式是Locale::en
fn size_formatted_mb(&self) -> String
将大小格式化为mb
的大小fn size_formatted_flex(&self) -> Striing
根据size
的大小来动态决定输出的字符串
辅助类型别名
pub type PathsResult = io::Result<Vec<Result<String, io::Error>>>;
独立函数
-
getpathstodelete
这个函数的参数有
path: impl Into<PathBuf>
directory: &DirectoryEnum
这个函数返回了
PathsResult
,有点复杂这个函数的作用是,根据
directory
参数,从指定的path
中提取路径
细节方面,这个函数使用了fs::read_dir
来读取一个路径的属性,从而来获取内部下一个文件夹的属性 -
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
-
new
初始化
Wipe
实例,接收一个写入者对象和WipeParams
引用 -
run
调用 写入头 , 写入内容 , 写入脚 的方法来执行整个擦除过程
-
write_header
写入擦除命令的头部信息
-
write_content
获取要删除的目录列表,遍历每个目录,输出相关信息,并根据配置执行删除或忽略操作
-
write_summary
输出擦除前后目录的信息摘要,包括被忽略和被擦除的目录
-
write_footer
写入擦除命令的结束信息,包括下一步操作和确认信息
-
write_space_line
写入一个空行,相当于换行