Skip to main content

fff_grep/searcher/
mod.rs

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