haystack.rs
是连接文件发现和搜索执行的核心抽象:
//在main.rs中
let haystack_builder = args.haystack_builder();
let haystack = haystack_builder.build_from_result(result);
searcher.search(&haystack);
Haystack
是对ignore::DirEntry
的包装,添加了应用层逻辑,对DirEntry
增加了几种判断,而HaystackBuilder
则是根据DirEntry
的类型返回不同的结果。
pub(crate) struct Haystack {
dent: ignore::DirEntry,
strip_dot_prefix: bool
}
impl Haystack {
pub(crate) fn is_stdin(&self) -> bool {
}
pub(crate) fn is_dir(&self) -> bool {
}
pub(crate) fn is_explicit(&self) -> bool {
}
pub(crate) fn is_file(&self) -> bool {
}
}
Haystack
中的方法指明了文件类型的层次关系:
- 普通文件(
is_file() == true
) - 目录(
is_dir() == true
) - 符号链接 (既不是文件也不是目录)
- 特殊文件(设备、管道、socket)
HaystackBuilder
根据几个is_*
方法返回Option<Haystack>
。
Haystack对DirEntry包装
Haystack
包装了DirEntry
,而不是在DirEntry
中扩展,体现了几个重要的设计原则:
1. 关注点分离
// ignore::DirEntry - 通用文件系统抽象
// 职责:文件遍历、基础元数据、忽略规则
// Haystack - ripgrep 特定的搜索抽象
// 职责:搜索逻辑、用户意图理解、应用层策略
2. 不同的语义层次
DirEntry
的视角:文件系统条目
is_file()
→“这是一个文件系统吗?”is_dir()
→“这是一个文件系统目录吗?”
Haystack
的视角:搜索目标
is_explicit()
→“用户明确要求搜索这个吗?”is_dir()
→ “从搜索角度看,这应该被当作目录处理吗?”
3. 符号链接处理的差异
注意Haystack::is_dir()
的特殊逻辑:
fn is_dir(&self) -> bool {
let ft = match self.dent.file_type() {
None => return false,
Some(ft) => ft,
};
if ft.is_dir() { return true; }
// 关键差异:额外的符号链接解析
self.dent.path_is_symlink() && self.dent.path().is_dir()
}
这个逻辑是ripgrep特有的,不应该污染通用的DirEntry
4. 应用特定的概念
Haystack
引入了ripgrep特有的概念:
is_explicit()
- 基于depth == 0
判断用户意图strip_dot_prefix
- UI优化功能- 搜索优先级策略 - 显式 > 文件 > 其他
5. 依赖方向控制
ignore crate (通用) ← ripgrep core (特定)
如果把 ripgrep 逻辑放入 DirEntry,会让通用库依赖特定应用,违反了依赖倒置原则。
6. 架构优势
- 可测试性 -
Haystack
的逻辑可以独立测试 - 可扩展性 - 可以添加更多ripgrep特定的方法
- 复用性 -
ignore
crate 可以被其他工具使用 - 清晰性 - 每个类型的职责边界明确
这是适配器模式的经典应用,体现了优秀的软件架构设计。
7. 抽象层次的视角
从Haystack
的设计可以学到优秀架构设计的核心思想:抽象层次的视角分离
DirEntry
是文件系统的抽象,就应该从文件系统视角去添加一些方法;Haystack
是搜索目标的抽象,就应该从搜索角度去添加方法。