Haystack

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. 架构优势
  1. 可测试性 - Haystack的逻辑可以独立测试
  2. 可扩展性 - 可以添加更多ripgrep特定的方法
  3. 复用性 - ignorecrate 可以被其他工具使用
  4. 清晰性 - 每个类型的职责边界明确

这是适配器模式的经典应用,体现了优秀的软件架构设计。

7. 抽象层次的视角

Haystack的设计可以学到优秀架构设计的核心思想:抽象层次的视角分离

DirEntry是文件系统的抽象,就应该从文件系统视角去添加一些方法;Haystack是搜索目标的抽象,就应该从搜索角度去添加方法。

使用 Hugo 构建
主题 StackJimmy 设计