summaryrefslogtreecommitdiffstats
path: root/crates/printer/src/standard.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/printer/src/standard.rs')
-rw-r--r--crates/printer/src/standard.rs3390
1 files changed, 3390 insertions, 0 deletions
diff --git a/crates/printer/src/standard.rs b/crates/printer/src/standard.rs
new file mode 100644
index 00000000..a0e9668a
--- /dev/null
+++ b/crates/printer/src/standard.rs
@@ -0,0 +1,3390 @@
+use std::cell::{Cell, RefCell};
+use std::cmp;
+use std::io::{self, Write};
+use std::path::Path;
+use std::sync::Arc;
+use std::time::Instant;
+
+use bstr::ByteSlice;
+use grep_matcher::{Match, Matcher};
+use grep_searcher::{
+ LineStep, Searcher, Sink, SinkContext, SinkContextKind, SinkError,
+ SinkFinish, SinkMatch,
+};
+use termcolor::{ColorSpec, NoColor, WriteColor};
+
+use color::ColorSpecs;
+use counter::CounterWriter;
+use stats::Stats;
+use util::{trim_ascii_prefix, PrinterPath, Replacer, Sunk};
+
+/// The configuration for the standard printer.
+///
+/// This is manipulated by the StandardBuilder and then referenced by the
+/// actual implementation. Once a printer is build, the configuration is frozen
+/// and cannot changed.
+#[derive(Debug, Clone)]
+struct Config {
+ colors: ColorSpecs,
+ stats: bool,
+ heading: bool,
+ path: bool,
+ only_matching: bool,
+ per_match: bool,
+ replacement: Arc<Option<Vec<u8>>>,
+ max_columns: Option<u64>,
+ max_columns_preview: bool,
+ max_matches: Option<u64>,
+ column: bool,
+ byte_offset: bool,
+ trim_ascii: bool,
+ separator_search: Arc<Option<Vec<u8>>>,
+ separator_context: Arc<Option<Vec<u8>>>,
+ separator_field_match: Arc<Vec<u8>>,
+ separator_field_context: Arc<Vec<u8>>,
+ separator_path: Option<u8>,
+ path_terminator: Option<u8>,
+}
+
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ colors: ColorSpecs::default(),
+ stats: false,
+ heading: false,
+ path: true,
+ only_matching: false,
+ per_match: false,
+ replacement: Arc::new(None),
+ max_columns: None,
+ max_columns_preview: false,
+ max_matches: None,
+ column: false,
+ byte_offset: false,
+ trim_ascii: false,
+ separator_search: Arc::new(None),
+ separator_context: Arc::new(Some(b"--".to_vec())),
+ separator_field_match: Arc::new(b":".to_vec()),
+ separator_field_context: Arc::new(b"-".to_vec()),
+ separator_path: None,
+ path_terminator: None,
+ }
+ }
+}
+
+/// A builder for the "standard" grep-like printer.
+///
+/// The builder permits configuring how the printer behaves. Configurable
+/// behavior includes, but is not limited to, limiting the number of matches,
+/// tweaking separators, executing pattern replacements, recording statistics
+/// and setting colors.
+///
+/// Some configuration options, such as the display of line numbers or
+/// contextual lines, are drawn directly from the
+/// `grep_searcher::Searcher`'s configuration.
+///
+/// Once a `Standard` printer is built, its configuration cannot be changed.
+#[derive(Clone, Debug)]
+pub struct StandardBuilder {
+ config: Config,
+}
+
+impl StandardBuilder {
+ /// Return a new builder for configuring the standard printer.
+ pub fn new() -> StandardBuilder {
+ StandardBuilder { config: Config::default() }
+ }
+
+ /// Build a printer using any implementation of `termcolor::WriteColor`.
+ ///
+ /// The implementation of `WriteColor` used here controls whether colors
+ /// are used or not when colors have been configured using the
+ /// `color_specs` method.
+ ///
+ /// For maximum portability, callers should generally use either
+ /// `termcolor::StandardStream` or `termcolor::BufferedStandardStream`
+ /// where appropriate, which will automatically enable colors on Windows
+ /// when possible.
+ ///
+ /// However, callers may also provide an arbitrary writer using the
+ /// `termcolor::Ansi` or `termcolor::NoColor` wrappers, which always enable
+ /// colors via ANSI escapes or always disable colors, respectively.
+ ///
+ /// As a convenience, callers may use `build_no_color` to automatically
+ /// select the `termcolor::NoColor` wrapper to avoid needing to import
+ /// from `termcolor` explicitly.
+ pub fn build<W: WriteColor>(&self, wtr: W) -> Standard<W> {
+ Standard {
+ config: self.config.clone(),
+ wtr: RefCell::new(CounterWriter::new(wtr)),
+ matches: vec![],
+ }
+ }
+
+ /// Build a printer from any implementation of `io::Write` and never emit
+ /// any colors, regardless of the user color specification settings.
+ ///
+ /// This is a convenience routine for
+ /// `StandardBuilder::build(termcolor::NoColor::new(wtr))`.
+ pub fn build_no_color<W: io::Write>(
+ &self,
+ wtr: W,
+ ) -> Standard<NoColor<W>> {
+ self.build(NoColor::new(wtr))
+ }
+
+ /// Set the user color specifications to use for coloring in this printer.
+ ///
+ /// A [`UserColorSpec`](struct.UserColorSpec.html) can be constructed from
+ /// a string in accordance with the color specification format. See the
+ /// `UserColorSpec` type documentation for more details on the format.
+ /// A [`ColorSpecs`](struct.ColorSpecs.html) can then be generated from
+ /// zero or more `UserColorSpec`s.
+ ///
+ /// Regardless of the color specifications provided here, whether color
+ /// is actually used or not is determined by the implementation of
+ /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
+ /// is provided to `build`, then no color will ever be printed regardless
+ /// of the color specifications provided here.
+ ///
+ /// This completely overrides any previous color specifications. This does
+ /// not add to any previously provided color specifications on this
+ /// builder.
+ pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut StandardBuilder {
+ self.config.colors = specs;
+ self
+ }
+
+ /// Enable the gathering of various aggregate statistics.
+ ///
+ /// When this is enabled (it's disabled by default), statistics will be
+ /// gathered for all uses of `Standard` printer returned by `build`,
+ /// including but not limited to, the total number of matches, the total
+ /// number of bytes searched and the total number of bytes printed.
+ ///
+ /// Aggregate statistics can be accessed via the sink's
+ /// [`StandardSink::stats`](struct.StandardSink.html#method.stats)
+ /// method.
+ ///
+ /// When this is enabled, this printer may need to do extra work in order
+ /// to compute certain statistics, which could cause the search to take
+ /// longer.
+ ///
+ /// For a complete description of available statistics, see
+ /// [`Stats`](struct.Stats.html).
+ pub fn stats(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.stats = yes;
+ self
+ }
+
+ /// Enable the use of "headings" in the printer.
+ ///
+ /// When this is enabled, and if a file path has been given to the printer,
+ /// then the file path will be printed once on its own line before showing
+ /// any matches. If the heading is not the first thing emitted by the
+ /// printer, then a line terminator is printed before the heading.
+ ///
+ /// By default, this option is disabled. When disabled, the printer will
+ /// not show any heading and will instead print the file path (if one is
+ /// given) on the same line as each matching (or context) line.
+ pub fn heading(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.heading = yes;
+ self
+ }
+
+ /// When enabled, if a path was given to the printer, then it is shown in
+ /// the output (either as a heading or as a prefix to each matching line).
+ /// When disabled, then no paths are ever included in the output even when
+ /// a path is provided to the printer.
+ ///
+ /// This is enabled by default.
+ pub fn path(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.path = yes;
+ self
+ }
+
+ /// Only print the specific matches instead of the entire line containing
+ /// each match. Each match is printed on its own line. When multi line
+ /// search is enabled, then matches spanning multiple lines are printed
+ /// such that only the matching portions of each line are shown.
+ pub fn only_matching(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.only_matching = yes;
+ self
+ }
+
+ /// Print at least one line for every match.
+ ///
+ /// This is similar to the `only_matching` option, except the entire line
+ /// is printed for each match. This is typically useful in conjunction with
+ /// the `column` option, which will show the starting column number for
+ /// every match on every line.
+ ///
+ /// When multi-line mode is enabled, each match and its accompanying lines
+ /// are printed. As with single line matches, if a line contains multiple
+ /// matches (even if only partially), then that line is printed once for
+ /// each match it participates in.
+ pub fn per_match(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.per_match = yes;
+ self
+ }
+
+ /// Set the bytes that will be used to replace each occurrence of a match
+ /// found.
+ ///
+ /// The replacement bytes given may include references to capturing groups,
+ /// which may either be in index form (e.g., `$2`) or can reference named
+ /// capturing groups if present in the original pattern (e.g., `$foo`).
+ ///
+ /// For documentation on the full format, please see the `Capture` trait's
+ /// `interpolate` method in the
+ /// [grep-printer](https://docs.rs/grep-printer) crate.
+ pub fn replacement(
+ &mut self,
+ replacement: Option<Vec<u8>>,
+ ) -> &mut StandardBuilder {
+ self.config.replacement = Arc::new(replacement);
+ self
+ }
+
+ /// Set the maximum number of columns allowed for each line printed. A
+ /// single column is heuristically defined as a single byte.
+ ///
+ /// If a line is found which exceeds this maximum, then it is replaced
+ /// with a message indicating that the line has been omitted.
+ ///
+ /// The default is to not specify a limit, in which each matching or
+ /// contextual line is printed regardless of how long it is.
+ pub fn max_columns(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
+ self.config.max_columns = limit;
+ self
+ }
+
+ /// When enabled, if a line is found to be over the configured maximum
+ /// column limit (measured in terms of bytes), then a preview of the long
+ /// line will be printed instead.
+ ///
+ /// The preview will correspond to the first `N` *grapheme clusters* of
+ /// the line, where `N` is the limit configured by `max_columns`.
+ ///
+ /// If no limit is set, then enabling this has no effect.
+ ///
+ /// This is disabled by default.
+ pub fn max_columns_preview(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.max_columns_preview = yes;
+ self
+ }
+
+ /// Set the maximum amount of matching lines that are printed.
+ ///
+ /// If multi line search is enabled and a match spans multiple lines, then
+ /// that match is counted exactly once for the purposes of enforcing this
+ /// limit, regardless of how many lines it spans.
+ pub fn max_matches(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
+ self.config.max_matches = limit;
+ self
+ }
+
+ /// Print the column number of the first match in a line.
+ ///
+ /// This option is convenient for use with `per_match` which will print a
+ /// line for every match along with the starting offset for that match.
+ ///
+ /// Column numbers are computed in terms of bytes from the start of the
+ /// line being printed.
+ ///
+ /// For matches that span multiple lines, the column number for each
+ /// matching line is in terms of the first matching line.
+ ///
+ /// This is disabled by default.
+ pub fn column(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.column = yes;
+ self
+ }
+
+ /// Print the absolute byte offset of the beginning of each line printed.
+ ///
+ /// The absolute byte offset starts from the beginning of each search and
+ /// is zero based.
+ ///
+ /// If the `only_matching` option is set, then this will print the absolute
+ /// byte offset of the beginning of each match.
+ pub fn byte_offset(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.byte_offset = yes;
+ self
+ }
+
+ /// When enabled, all lines will have prefix ASCII whitespace trimmed
+ /// before being written.
+ ///
+ /// This is disabled by default.
+ pub fn trim_ascii(&mut self, yes: bool) -> &mut StandardBuilder {
+ self.config.trim_ascii = yes;
+ self
+ }
+
+ /// Set the separator used between sets of search results.
+ ///
+ /// When this is set, then it will be printed on its own line immediately
+ /// before the results for a single search if and only if a previous search
+ /// had already printed results. In effect, this permits showing a divider
+ /// between sets of search results that does not appear at the beginning
+ /// or end of all search results.
+ ///
+ /// To reproduce the classic grep format, this is typically set to `--`
+ /// (the same as the context separator) if and only if contextual lines
+ /// have been requested, but disabled otherwise.
+ ///
+ /// By default, this is disabled.
+ pub fn separator_search(
+ &mut self,
+ sep: Option<Vec<u8>>,
+ ) -> &mut StandardBuilder {
+ self.config.separator_search = Arc::new(sep);
+ self
+ }
+
+ /// Set the separator used between discontiguous runs of search context,
+ /// but only when the searcher is configured to report contextual lines.
+ ///
+ /// The separator is always printed on its own line, even if it's empty.
+ ///
+ /// If no separator is set, then nothing is printed when a context break
+ /// occurs.
+ ///
+ /// By default, this is set to `--`.
+ pub fn separator_context(
+ &mut self,
+ sep: Option<Vec<u8>>,
+ ) -> &mut StandardBuilder {
+ self.config.separator_context = Arc::new(sep);
+ self
+ }
+
+ /// Set the separator used between fields emitted for matching lines.
+ ///
+ /// For example, when the searcher has line numbers enabled, this printer
+ /// will print the line number before each matching line. The bytes given
+ /// here will be written after the line number but before the matching
+ /// line.
+ ///
+ /// By default, this is set to `:`.
+ pub fn separator_field_match(
+ &mut self,
+ sep: Vec<u8>,
+ ) -> &mut StandardBuilder {
+ self.config.separator_field_match = Arc::new(sep);
+ self
+ }
+
+ /// Set the separator used between fields emitted for context lines.
+ ///
+ /// For example, when the searcher has line numbers enabled, this printer
+ /// will print the line number before each context line. The bytes given
+ /// here will be written after the line number but before the context
+ /// line.
+ ///
+ /// By default, this is set to `-`.
+ pub fn separator_field_context(
+ &mut self,
+ sep: Vec<u8>,
+ ) -> &mut StandardBuilder {
+ self.config.separator_field_context = Arc::new(sep);
+ self
+ }
+
+ /// Set the path separator used when printing file paths.
+ ///
+ /// When a printer is configured with a file path, and when a match is
+ /// found, that file path will be printed (either as a heading or as a
+ /// prefix to each matching or contextual line, depending on other
+ /// configuration settings). Typically, printing is done by emitting the
+ /// file path as is. However, this setting provides the ability to use a
+ /// different path separator from what the current environment has
+ /// configured.
+ ///
+ /// A typical use for this option is to permit cygwin users on Windows to
+ /// set the path separator to `/` instead of using the system default of
+ /// `\`.
+ pub fn separator_path(&mut self, sep: Option<u8>) -> &mut StandardBuilder {
+ self.config.separator_path = sep;
+ self
+ }
+
+ /// Set the path terminator used.
+ ///
+ /// The path terminator is a byte that is printed after every file path
+ /// emitted by this printer.
+ ///
+ /// If no path terminator is set (the default), then paths are terminated
+ /// by either new lines (for when `heading` is enabled) or the match or
+ /// context field separators (e.g., `:` or `-`).
+ pub fn path_terminator(
+ &mut self,
+ terminator: Option<u8>,
+ ) -> &mut StandardBuilder {
+ self.config.path_terminator = terminator;
+ self
+ }
+}
+
+/// The standard printer, which implements grep-like formatting, including
+/// color support.
+///
+/// A default printer can be created with either of the `Standard::new` or
+/// `Standard::new_no_color` constructors. However, there are a considerable
+/// number of options that configure this printer's output. Those options can
+/// be configured using [`StandardBuilder`](struct.StandardBuilder.html).
+///
+/// This type is generic over `W`, which represents any implementation
+/// of the `termcolor::WriteColor` trait. If colors are not desired,
+/// then the `new_no_color` constructor can be used, or, alternatively,
+/// the `termcolor::NoColor` adapter can be used to wrap any `io::Write`
+/// implementation without enabling any colors.
+#[derive(Debug)]
+pub struct Standard<W> {
+ config: Config,
+ wtr: RefCell<CounterWriter<W>>,
+ matches: Vec<Match>,
+}
+
+impl<W: WriteColor> Standard<W> {
+ /// Return a standard printer with a default configuration that writes
+ /// matches to the given writer.
+ ///
+ /// The writer should be an implementation of `termcolor::WriteColor`
+ /// and not just a bare implementation of `io::Write`. To use a normal
+ /// `io::Write` implementation (simultaneously sacrificing colors), use
+ /// the `new_no_color` constructor.
+ pub fn new(wtr: W) -> Standard<W> {
+ StandardBuilder::new().build(wtr)
+ }
+}
+
+impl<W: io::Write> Standard<NoColor<W>> {
+ /// Return a standard printer with a default configuration that writes
+ /// matches to the given writer.
+ ///
+ /// The writer can be any implementation of `io::Write`. With this
+ /// constructor, the printer will never emit colors.
+ pub fn new_no_color(wtr: W) -> Standard<NoColor<W>> {
+ StandardBuilder::new().build_no_color(wtr)
+ }
+}
+
+impl<W: WriteColor> Standard<W> {
+ /// Return an implementation of `Sink` for the standard printer.
+ ///
+ /// This does not associate the printer with a file path, which means this
+ /// implementation will never print a file path along with the matches.
+ pub fn sink<'s, M: Matcher>(
+ &'s mut self,
+ matcher: M,
+ ) -> StandardSink<'static, 's, M, W> {
+ let stats = if self.config.stats { Some(Stats::new()) } else { None };
+ let needs_match_granularity = self.needs_match_granularity();
+ StandardSink {
+ matcher: matcher,
+ standard: self,
+ replacer: Replacer::new(),
+ path: None,
+ start_time: Instant::now(),
+ match_count: 0,
+ after_context_remaining: 0,
+ binary_byte_offset: None,
+ stats: stats,
+ needs_match_granularity: needs_match_granularity,
+ }
+ }
+
+ /// Return an implementation of `Sink` associated with a file path.
+ ///
+ /// When the printer is associated with a path, then it may, depending on
+ /// its configuration, print the path along with the matches found.
+ pub fn sink_with_path<'p, 's, M, P>(
+ &'s mut self,
+ matcher: M,
+ path: &'p P,
+ ) -> StandardSink<'p, 's, M, W>
+ where
+ M: Matcher,
+ P: ?Sized + AsRef<Path>,
+ {
+ if !self.config.path {
+ return self.sink(matcher);
+ }
+ let stats = if self.config.stats { Some(Stats::new()) } else { None };
+ let ppath = PrinterPath::with_separator(
+ path.as_ref(),
+ self.config.separator_path,
+ );
+ let needs_match_granularity = self.needs_match_granularity();
+ StandardSink {
+ matcher: matcher,
+ standard: self,
+ replacer: Replacer::new(),
+ path: Some(ppath),
+ start_time: Instant::now(),
+ match_count: 0,
+ after_context_remaining: 0,
+ binary_byte_offset: None,
+ stats: stats,
+ needs_match_granularity: needs_match_granularity,
+ }
+ }
+
+ /// Returns true if and only if the configuration of the printer requires
+ /// us to find each individual match in the lines reported by the searcher.
+ ///
+ /// We care about this distinction because finding each individual match
+ /// costs more, so we only do it when we need to.
+ fn needs_match_granularity(&self) -> bool {
+ let supports_color = self.wtr.borrow().supports_color();
+ let match_colored = !self.config.colors.matched().is_none();
+
+ // Coloring requires identifying each individual match.
+ (supports_color && match_colored)
+ // The column feature requires finding the position of the first match.
+ || self.config.column
+ // Requires finding each match for performing replacement.
+ || self.config.replacement.is_some()
+ // Emitting a line for each match requires finding each match.
+ || self.config.per_match
+ // Emitting only the match requires finding each match.
+ || self.config.only_matching
+ // Computing certain statistics requires finding each match.
+ || self.config.stats
+ }
+}
+
+impl<W> Standard<W> {
+ /// Returns true if and only if this printer has written at least one byte
+ /// to the underlying writer during any of the previous searches.
+ pub fn has_written(&self) -> bool {
+ self.wtr.borrow().total_count() > 0
+ }
+
+ /// Return a mutable reference to the underlying writer.
+ pub fn get_mut(&mut self) -> &mut W {
+ self.wtr.get_mut().get_mut()
+ }
+
+ /// Consume this printer and return back ownership of the underlying
+ /// writer.
+ pub fn into_inner(self) -> W {
+ self.wtr.into_inner().into_inner()
+ }
+}
+
+/// An implementation of `Sink` associated with a matcher and an optional file
+/// path for the standard printer.
+///
+/// A `Sink` can be created via the
+/// [`Standard::sink`](struct.Standard.html#method.sink)
+/// or
+/// [`Standard::sink_with_path`](struct.Standard.html#method.sink_with_path)
+/// methods, depending on whether you want to include a file path in the
+/// printer's output.
+///
+/// Building a `StandardSink` is cheap, and callers should create a new one
+/// for each thing that is searched. After a search has completed, callers may
+/// query this sink for information such as whether a match occurred or whether
+/// binary data was found (and if so, the offset at which it occurred).
+///
+/// This type is generic over a few type parameters:
+///
+/// * `'p` refers to the lifetime of the file path, if one is provided. When
+/// no file path is given, then this is `'static`.
+/// * `'s` refers to the lifetime of the
+/// [`Standard`](struct.Standard.html)
+/// printer that this type borrows.
+/// * `M` refers to the type of matcher used by
+/// `grep_searcher::Searcher` that is reporting results to this sink.
+/// * `W` refers to the underlying writer that this printer is writing its
+/// output to.
+#[derive(Debug)]
+pub struct StandardSink<'p, 's, M: Matcher, W: 's> {
+ matcher: M,
+ standard: &'s mut Standard<W>,
+ replacer: Replacer<M>,
+ path: Option<PrinterPath<'p>>,
+ start_time: Instant,
+ match_count: u64,
+ after_context_remaining: u64,
+ binary_byte_offset: Option<u64>,
+ stats: Option<Stats>,
+ needs_match_granularity: bool,
+}
+
+impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
+ /// Returns true if and only if this printer received a match in the
+ /// previous search.
+ ///
+ /// This is unaffected by the result of searches before the previous
+ /// search on this sink.
+ pub fn has_match(&self) -> bool {
+ self.match_count > 0
+ }
+
+ /// Return the total number of matches reported to this sink.
+ ///
+ /// This corresponds to the number of times `Sink::matched` is called
+ /// on the previous search.
+ ///
+ /// This is unaffected by the result of searches before the previous
+ /// search on this sink.
+ pub fn match_count(&self) -> u64 {
+ self.match_count
+ }
+
+ /// If binary data was found in the previous search, this returns the
+ /// offset at which the binary data was first detected.
+ ///
+ /// The offset returned is an absolute offset relative to the entire
+ /// set of bytes searched.
+ ///
+ /// This is unaffected by the result of searches before the previous
+ /// search. e.g., If the search prior to the previous search found binary
+ /// data but the previous search found no binary data, then this will
+ /// return `None`.
+ pub fn binary_byte_offset(&self) -> Option<u64> {
+ self.binary_byte_offset
+ }
+
+ /// Return a reference to the stats produced by the printer for all
+ /// searches executed on this sink.
+ ///
+ /// This only returns stats if they were requested via the
+ /// [`StandardBuilder`](struct.StandardBuilder.html)
+ /// configuration.
+ pub fn stats(&self) -> Option<&Stats> {
+ self.stats.as_ref()
+ }
+
+ /// Execute the matcher over the given bytes and record the match
+ /// locations if the current configuration demands match granularity.
+ fn record_matches(&mut self, bytes: &[u8]) -> io::Result<()> {
+ self.standard.matches.clear();
+ if !self.needs_match_granularity {
+ return Ok(());
+ }
+ // If printing requires knowing the location of each individual match,
+ // then compute and stored those right now for use later. While this
+ // adds an extra copy for storing the matches, we do amortize the
+ // allocation for it and this greatly simplifies the printing logic to
+ // the extent that it's easy to ensure that we never do more than
+ // one search to find the matches (well, for replacements, we do one
+ // additional search to perform the actual replacement).
+ let matches = &mut self.standard.matches;
+ self.matcher
+ .find_iter(bytes, |m| {
+ matches.push(m);
+ true
+ })
+ .map_err(io::Error::error_message)?;
+ // Don't report empty matches appearing at the end of the bytes.
+ if !matches.is_empty()
+ && matches.last().unwrap().is_empty()
+ && matches.last().unwrap().start() >= bytes.len()
+ {
+ matches.pop().unwrap();
+ }
+ Ok(())
+ }
+
+ /// If the configuration specifies a replacement, then this executes the
+ /// replacement, lazily allocating memory if necessary.
+ ///
+ /// To access the result of a replacement, use `replacer.replacement()`.
+ fn replace(&mut self, bytes: &[u8]) -> io::Result<()> {
+ self.replacer.clear();
+ if self.standard.config.replacement.is_some() {
+ let replacement = (*self.standard.config.replacement)
+ .as_ref()
+ .map(|r| &*r)
+ .unwrap();
+ self.replacer.replace_all(&self.matcher, bytes, replacement)?;
+ }
+ Ok(())
+ }
+
+ /// Returns true if this printer should quit.
+ ///
+ /// This implements the logic for handling quitting after seeing a certain
+ /// amount of matches. In most cases, the logic is simple, but we must
+ /// permit all "after" contextual lines to print after reaching the limit.
+ fn should_quit(&self) -> bool {
+ let limit = match self.standard.config.max_matches {
+ None => return false,
+ Some(limit) => limit,
+ };
+ if self.match_count < limit {
+ return false;
+ }
+ self.after_context_remaining == 0
+ }
+}
+
+impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
+ type Error = io::Error;
+
+ fn matched(
+ &mut self,
+ searcher: &Searcher,
+ mat: &SinkMatch,
+ ) -> Result<bool, io::Error> {
+ self.match_count += 1;
+ self.after_context_remaining = searcher.after_context() as u64;
+
+ self.record_matches(mat.bytes())?;
+ self.replace(mat.bytes())?;
+
+ if let Some(ref mut stats) = self.stats {
+ stats.add_matches(self.standard.matches.len() as u64);
+ stats.add_matched_lines(mat.lines().count() as u64);
+ }
+ if searcher.binary_detection().convert_byte().is_some() {
+ if self.binary_byte_offset.is_some() {
+ return Ok(false);
+ }
+ }
+
+ StandardImpl::from_match(searcher, self, mat).sink()?;
+ Ok(!self.should_quit())
+ }
+
+ fn context(
+ &mut self,
+ searcher: &Searcher,
+ ctx: &SinkContext,
+ ) -> Result<bool, io::Error> {
+ self.standard.matches.clear();
+ self.replacer.clear();
+
+ if ctx.kind() == &SinkContextKind::After {
+ self.after_context_remaining =
+ self.after_context_remaining.saturating_sub(1);
+ }
+ if searcher.invert_match() {
+ self.record_matches(ctx.bytes())?;
+ self.replace(ctx.bytes())?;
+ }
+ if searcher.binary_detection().convert_byte().is_some() {
+ if self.binary_byte_offset.is_some() {
+ return Ok(false);
+ }
+ }
+
+ StandardImpl::from_context(searcher, self, ctx).sink()?;
+ Ok(!self.should_quit())
+ }
+
+ fn context_break(
+ &mut self,
+ searcher: &Searcher,
+ ) -> Result<bool, io::Error> {
+ StandardImpl::new(searcher, self).write_context_separator()?;
+ Ok(true)
+ }
+
+ fn binary_data(
+ &mut self,
+ _searcher: &Searcher,
+ binary_byte_offset: u64,
+ ) -> Result<bool, io::Error> {
+ self.binary_byte_offset = Some(binary_byte_offset);
+ Ok(true)
+ }
+
+ fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
+ self.standard.wtr.borrow_mut().reset_count();
+ self.start_time = Instant::now();
+ self.match_count = 0;
+ self.after_context_remaining = 0;
+ self.binary_byte_offset = None;
+ if self.standard.config.max_matches == Some(0) {
+ return Ok(false);
+ }
+ Ok(true)
+ }
+
+ fn finish(
+ &mut self,
+ searcher: &Searcher,
+ finish: &SinkFinish,
+ ) -> Result<(), io::Error> {
+ if let Some(offset) = self.binary_byte_offset {
+ StandardImpl::new(searcher, self).write_binary_message(offset)?;
+ }
+ if let Some(stats) = self.stats.as_mut() {
+ stats.add_elapsed(self.start_time.elapsed());
+ stats.add_searches(1);
+ if self.match_count > 0 {
+ stats.add_searches_with_match(1);
+ }
+ stats.add_bytes_searched(finish.byte_count());
+ stats.add_bytes_printed(self.standard.wtr.borrow().count());
+ }
+ Ok(())
+ }
+}
+
+/// The actual implementation of the standard printer. This couples together
+/// the searcher, the sink implementation and information about the match.
+///
+/// A StandardImpl is initialized every time a match or a contextual line is
+/// reported.
+#[derive(Debug)]
+struct StandardImpl<'a, M: 'a + Matcher, W: 'a> {
+ searcher: &'a Searcher,
+ sink: &'a StandardSink<'a, 'a, M, W>,
+ sunk: Sunk<'a>,
+ /// Set to true if and only if we are writing a match with color.
+ in_color_match: Cell<bool>,
+}
+
+impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
+ /// Bundle self with a searcher and return the core implementation of Sink.
+ fn new(
+ searcher: &'a Searcher,
+ sink: &'a StandardSink<M, W>,
+ ) -> StandardImpl<'a, M, W> {
+ StandardImpl {
+ searcher: searcher,
+ sink: sink,
+ sunk: Sunk::empty(),
+ in_color_match: Cell::new(false),
+ }
+ }
+
+ /// Bundle self with a searcher and return the core implementation of Sink
+ /// for use with handling matching lines.
+ fn from_match(
+ searcher: &'a Searcher,
+ sink: &'a StandardSink<M, W>,
+ mat: &'a SinkMatch<'a>,
+ ) -> StandardImpl<'a, M, W> {
+ let sunk = Sunk::from_sink_match(
+ mat,
+ &sink.standard.matches,
+ sink.replacer.replacement(),
+ );
+ StandardImpl { sunk: sunk, ..StandardImpl::new(searcher, sink) }
+ }
+
+ /// Bundle self with a searcher and return the core implementation of Sink
+ /// for use with handling contextual lines.
+ fn from_context(
+ searcher: &'a Searcher,
+ sink: &'a StandardSink<M, W>,
+ ctx: &'a SinkContext<'a>,
+ ) -> StandardImpl<'a, M, W> {
+ let sunk = Sunk::from_sink_context(
+ ctx,
+ &sink.standard.matches,
+ sink.replacer.replacement(),
+ );
+ StandardImpl { sunk: sunk, ..StandardImpl::new(searcher, sink) }
+ }
+
+ fn sink(&self) -> io::Result<()> {
+ self.write_search_prelude()?;
+ if self.sunk.matches().is_empty() {
+ if self.multi_line() && !self.is_context() {
+ self.sink_fast_multi_line()
+ } else {
+ self.sink_fast()
+ }
+ } else {
+ if self.multi_line() && !self.is_context() {
+ self.sink_slow_multi_line()
+ } else {
+ self.sink_slow()
+ }
+ }
+ }
+
+ /// Print matches (limited to one line) quickly by avoiding the detection
+ /// of each individual match in the lines reported in the given
+ /// `SinkMatch`.
+ ///
+ /// This should only be used when the configuration does not demand match
+ /// granularity and the searcher is not in multi line mode.
+ fn sink_fast(&self) -> io::Result<()> {
+ debug_assert!(self.sunk.matches().is_empty());
+ debug_assert!(!self.multi_line() || self.is_context());
+
+ self.write_prelude(
+ self.sunk.absolute_byte_offset(),
+ self.sunk.line_number(),
+ None,
+ )?;
+ self.write_line(self.sunk.bytes())
+ }
+
+ /// Print matches (possibly spanning more than one line) quickly by
+ /// avoiding the detection of each individual match in the lines reported
+ /// in the given `SinkMatch`.
+ ///
+ /// This should only be used when the configuration does not demand match
+ /// granularity. This may be used when the searcher is in multi line mode.
+ fn sink_fast_multi_line(&self) -> io::Result<()> {
+ debug_assert!(self.sunk.matches().is_empty());
+ // This isn't actually a required invariant for using this method,
+ // but if we wind up here and multi line mode is disabled, then we
+ // should still treat it as a bug since we should be using matched_fast
+ // instead.
+ debug_assert!(self.multi_line());
+
+ let line_term = self.searcher.line_terminator().as_byte();
+ let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
+ for (i, line) in self.sunk.lines(line_term).enumerate() {
+ self.write_prelude(
+ absolute_byte_offset,
+ self.sunk.line_number().map(|n| n + i as u64),
+ None,
+ )?;
+ absolute_byte_offset += line.len() as u64;
+
+ self.write_line(line)?;
+ }<