diff options
Diffstat (limited to 'crates/printer/src/standard.rs')
-rw-r--r-- | crates/printer/src/standard.rs | 3390 |
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)?; + }< |