Skip to main content

sift_core/grep/
mod.rs

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