Skip to main content

fff_grep/searcher/
mod.rs

1use grep_matcher::{LineTerminator, Match, Matcher};
2
3use crate::{
4    searcher::glue::{MultiLine, SliceByLine},
5    sink::{Sink, SinkError},
6};
7
8mod core;
9mod glue;
10
11/// We use this type alias since we want the ergonomics of a matcher's `Match`
12/// type, but in practice, we use it for arbitrary ranges, so give it a more
13/// accurate name. This is only used in the searcher's internals.
14type Range = Match;
15
16/// An error that can occur when building a searcher.
17#[derive(Clone, Debug, Eq, PartialEq)]
18#[non_exhaustive]
19pub(crate) enum ConfigError {
20    /// Occurs when a matcher reports a line terminator that is different than
21    /// the one configured in the searcher.
22    MismatchedLineTerminators {
23        /// The matcher's line terminator.
24        matcher: LineTerminator,
25        /// The searcher's line terminator.
26        searcher: LineTerminator,
27    },
28}
29
30impl std::error::Error for ConfigError {}
31
32impl std::fmt::Display for ConfigError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match *self {
35            ConfigError::MismatchedLineTerminators { matcher, searcher } => {
36                write!(
37                    f,
38                    "grep config error: mismatched line terminators, \
39                     matcher has {:?} but searcher has {:?}",
40                    matcher, searcher
41                )
42            }
43        }
44    }
45}
46
47/// The internal configuration of a searcher.
48#[derive(Clone, Debug)]
49pub(crate) struct Config {
50    /// The line terminator to use.
51    pub(crate) line_term: LineTerminator,
52    /// Whether to count line numbers.
53    pub(crate) line_number: bool,
54    /// Whether to enable matching across multiple lines.
55    multi_line: bool,
56}
57
58impl Default for Config {
59    fn default() -> Config {
60        Config {
61            line_term: LineTerminator::default(),
62            line_number: true,
63            multi_line: false,
64        }
65    }
66}
67
68/// A builder for configuring a searcher.
69#[derive(Clone, Debug)]
70pub struct SearcherBuilder {
71    config: Config,
72}
73
74impl Default for SearcherBuilder {
75    fn default() -> SearcherBuilder {
76        SearcherBuilder::new()
77    }
78}
79
80impl SearcherBuilder {
81    /// Create a new searcher builder with a default configuration.
82    pub fn new() -> SearcherBuilder {
83        SearcherBuilder {
84            config: Config::default(),
85        }
86    }
87
88    /// Build a searcher.
89    pub fn build(&self) -> Searcher {
90        Searcher {
91            config: self.config.clone(),
92        }
93    }
94
95    /// Whether to count and include line numbers with matching lines.
96    pub fn line_number(&mut self, yes: bool) -> &mut SearcherBuilder {
97        self.config.line_number = yes;
98        self
99    }
100
101    /// Whether to enable multi line search or not.
102    pub fn multi_line(&mut self, yes: bool) -> &mut SearcherBuilder {
103        self.config.multi_line = yes;
104        self
105    }
106}
107
108/// A searcher executes searches over a haystack and writes results to a caller
109/// provided sink.
110#[derive(Clone, Debug)]
111pub struct Searcher {
112    pub(crate) config: Config,
113}
114
115impl Searcher {
116    /// Create a new searcher with a default configuration.
117    pub fn new() -> Searcher {
118        SearcherBuilder::new().build()
119    }
120
121    /// Execute a search over the given slice and write the results to the
122    /// given sink.
123    pub fn search_slice<M, S>(&self, matcher: M, slice: &[u8], write_to: S) -> Result<(), S::Error>
124    where
125        M: Matcher,
126        S: Sink,
127    {
128        self.check_config(&matcher)
129            .map_err(S::Error::error_message)?;
130
131        if self.multi_line_with_matcher(&matcher) {
132            MultiLine::new(self, matcher, slice, write_to).run()
133        } else {
134            SliceByLine::new(self, matcher, slice, write_to).run()
135        }
136    }
137
138    /// Check that the searcher's configuration and the matcher are consistent.
139    fn check_config<M: Matcher>(&self, matcher: M) -> Result<(), ConfigError> {
140        let matcher_line_term = match matcher.line_terminator() {
141            None => return Ok(()),
142            Some(line_term) => line_term,
143        };
144        if matcher_line_term != self.config.line_term {
145            return Err(ConfigError::MismatchedLineTerminators {
146                matcher: matcher_line_term,
147                searcher: self.config.line_term,
148            });
149        }
150        Ok(())
151    }
152}
153
154impl Default for Searcher {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160/// Configuration query methods used by the sink and internal search core.
161impl Searcher {
162    /// Returns the line terminator used by this searcher.
163    #[inline]
164    pub fn line_terminator(&self) -> LineTerminator {
165        self.config.line_term
166    }
167
168    /// Returns true if and only if this searcher is configured to count line
169    /// numbers.
170    #[inline]
171    pub fn line_number(&self) -> bool {
172        self.config.line_number
173    }
174
175    /// Returns true if and only if this searcher is configured to perform
176    /// multi line search.
177    #[inline]
178    pub fn multi_line(&self) -> bool {
179        self.config.multi_line
180    }
181
182    /// Returns true if and only if this searcher will choose a multi-line
183    /// strategy given the provided matcher.
184    pub fn multi_line_with_matcher<M: Matcher>(&self, matcher: M) -> bool {
185        if !self.multi_line() {
186            return false;
187        }
188        if let Some(line_term) = matcher.line_terminator()
189            && line_term == self.line_terminator()
190        {
191            return false;
192        }
193        if let Some(non_matching) = matcher.non_matching_bytes()
194            && non_matching.contains(self.line_terminator().as_byte())
195        {
196            return false;
197        }
198        true
199    }
200}