Skip to main content

sift_core/grep/
mod.rs

1//! Grep pipeline orchestration.
2//!
3//! Bridges the query planner, index registry, candidate filter, and search
4//! engine into a single `GrepRequest::run()` call. This is the primary API
5//! the CLI calls. The pipeline is index-agnostic: it works with whatever
6//! index types the `Indexes` registry has opened.
7
8use std::path::PathBuf;
9
10use crate::Candidate;
11use crate::index::Indexes;
12use crate::query::{QueryPlanner, UnindexedStrategy};
13use crate::search::request::{SearchCollection, SearchExecution};
14use crate::search::{
15    CandidateFilter, SearchError, SearchOutcome, SearchOutput, SearchQuery, SearchSeparators,
16    SearchStats,
17};
18use rayon::prelude::*;
19
20/// Result of the grep pipeline.
21pub struct GrepRun {
22    pub outcome: SearchOutcome,
23    /// Unique rel-paths with at least one pattern hit.
24    pub hits: Vec<PathBuf>,
25}
26
27/// User-facing request to the grep pipeline.
28pub struct GrepRequest<'a> {
29    pub indexes: &'a Indexes,
30    pub filter: &'a CandidateFilter,
31    pub output: SearchOutput,
32    pub separators: &'a SearchSeparators,
33    pub collect: SearchCollection,
34    pub store_meta: Option<&'a crate::StoreMeta>,
35    pub unindexed: UnindexedStrategy,
36}
37
38impl GrepRequest<'_> {
39    /// Run the full grep pipeline: resolve candidates, execute search, return outcome.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if candidate resolution, regex compilation, or search execution fails.
44    pub fn run(&self, query: &SearchQuery) -> crate::Result<GrepRun> {
45        if query.opts().max_results == Some(0) {
46            return Err(crate::Error::Search(SearchError::InvalidMaxCount));
47        }
48
49        let spec = query.build_query_spec();
50        let output = self.output;
51        let requirement = output.candidate_requirement();
52
53        let raw = QueryPlanner::new(spec).candidates(
54            self.indexes,
55            requirement,
56            self.filter,
57            self.store_meta,
58            self.unindexed,
59            || self.filter.collect(),
60        )?;
61
62        let candidates: Vec<Candidate> = raw
63            .into_par_iter()
64            .filter(|c| c.matches(self.filter))
65            .collect();
66
67        if candidates.is_empty() {
68            return Ok(GrepRun {
69                outcome: SearchOutcome {
70                    matched: false,
71                    stats: self.collect.stats.then_some(SearchStats::default()),
72                },
73                hits: Vec::new(),
74            });
75        }
76
77        let (outcome, hits) = query.search(&SearchExecution {
78            candidates: &candidates,
79            output,
80            separators: self.separators,
81            collect: self.collect.with_hits(true),
82        })?;
83
84        Ok(GrepRun { outcome, hits })
85    }
86}