grep_printer/
summary.rs

1use std::{
2    cell::RefCell,
3    io::{self, Write},
4    path::Path,
5    sync::Arc,
6    time::Instant,
7};
8
9use {
10    grep_matcher::Matcher,
11    grep_searcher::{Searcher, Sink, SinkError, SinkFinish, SinkMatch},
12    termcolor::{ColorSpec, NoColor, WriteColor},
13};
14
15use crate::{
16    color::ColorSpecs,
17    counter::CounterWriter,
18    hyperlink::{self, HyperlinkConfig},
19    stats::Stats,
20    util::{find_iter_at_in_context, PrinterPath},
21};
22
23/// The configuration for the summary printer.
24///
25/// This is manipulated by the SummaryBuilder and then referenced by the actual
26/// implementation. Once a printer is build, the configuration is frozen and
27/// cannot changed.
28#[derive(Debug, Clone)]
29struct Config {
30    kind: SummaryKind,
31    colors: ColorSpecs,
32    hyperlink: HyperlinkConfig,
33    stats: bool,
34    path: bool,
35    max_matches: Option<u64>,
36    exclude_zero: bool,
37    separator_field: Arc<Vec<u8>>,
38    separator_path: Option<u8>,
39    path_terminator: Option<u8>,
40}
41
42impl Default for Config {
43    fn default() -> Config {
44        Config {
45            kind: SummaryKind::Count,
46            colors: ColorSpecs::default(),
47            hyperlink: HyperlinkConfig::default(),
48            stats: false,
49            path: true,
50            max_matches: None,
51            exclude_zero: true,
52            separator_field: Arc::new(b":".to_vec()),
53            separator_path: None,
54            path_terminator: None,
55        }
56    }
57}
58
59/// The type of summary output (if any) to print.
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum SummaryKind {
62    /// Show only a count of the total number of matches (counting each line
63    /// at most once) found.
64    ///
65    /// If the `path` setting is enabled, then the count is prefixed by the
66    /// corresponding file path.
67    Count,
68    /// Show only a count of the total number of matches (counting possibly
69    /// many matches on each line) found.
70    ///
71    /// If the `path` setting is enabled, then the count is prefixed by the
72    /// corresponding file path.
73    CountMatches,
74    /// Show only the file path if and only if a match was found.
75    ///
76    /// This ignores the `path` setting and always shows the file path. If no
77    /// file path is provided, then searching will immediately stop and return
78    /// an error.
79    PathWithMatch,
80    /// Show only the file path if and only if a match was found.
81    ///
82    /// This ignores the `path` setting and always shows the file path. If no
83    /// file path is provided, then searching will immediately stop and return
84    /// an error.
85    PathWithoutMatch,
86    /// Don't show any output and the stop the search once a match is found.
87    ///
88    /// Note that if `stats` is enabled, then searching continues in order to
89    /// compute statistics.
90    Quiet,
91}
92
93impl SummaryKind {
94    /// Returns true if and only if this output mode requires a file path.
95    ///
96    /// When an output mode requires a file path, then the summary printer
97    /// will report an error at the start of every search that lacks a file
98    /// path.
99    fn requires_path(&self) -> bool {
100        use self::SummaryKind::*;
101
102        match *self {
103            PathWithMatch | PathWithoutMatch => true,
104            Count | CountMatches | Quiet => false,
105        }
106    }
107
108    /// Returns true if and only if this output mode requires computing
109    /// statistics, regardless of whether they have been enabled or not.
110    fn requires_stats(&self) -> bool {
111        use self::SummaryKind::*;
112
113        match *self {
114            CountMatches => true,
115            Count | PathWithMatch | PathWithoutMatch | Quiet => false,
116        }
117    }
118
119    /// Returns true if and only if a printer using this output mode can
120    /// quit after seeing the first match.
121    fn quit_early(&self) -> bool {
122        use self::SummaryKind::*;
123
124        match *self {
125            PathWithMatch | Quiet => true,
126            Count | CountMatches | PathWithoutMatch => false,
127        }
128    }
129}
130
131/// A builder for summary printer.
132///
133/// The builder permits configuring how the printer behaves. The summary
134/// printer has fewer configuration options than the standard printer because
135/// it aims to produce aggregate output about a single search (typically just
136/// one line) instead of output for each match.
137///
138/// Once a `Summary` printer is built, its configuration cannot be changed.
139#[derive(Clone, Debug)]
140pub struct SummaryBuilder {
141    config: Config,
142}
143
144impl SummaryBuilder {
145    /// Return a new builder for configuring the summary printer.
146    pub fn new() -> SummaryBuilder {
147        SummaryBuilder { config: Config::default() }
148    }
149
150    /// Build a printer using any implementation of `termcolor::WriteColor`.
151    ///
152    /// The implementation of `WriteColor` used here controls whether colors
153    /// are used or not when colors have been configured using the
154    /// `color_specs` method.
155    ///
156    /// For maximum portability, callers should generally use either
157    /// `termcolor::StandardStream` or `termcolor::BufferedStandardStream`
158    /// where appropriate, which will automatically enable colors on Windows
159    /// when possible.
160    ///
161    /// However, callers may also provide an arbitrary writer using the
162    /// `termcolor::Ansi` or `termcolor::NoColor` wrappers, which always enable
163    /// colors via ANSI escapes or always disable colors, respectively.
164    ///
165    /// As a convenience, callers may use `build_no_color` to automatically
166    /// select the `termcolor::NoColor` wrapper to avoid needing to import
167    /// from `termcolor` explicitly.
168    pub fn build<W: WriteColor>(&self, wtr: W) -> Summary<W> {
169        Summary {
170            config: self.config.clone(),
171            wtr: RefCell::new(CounterWriter::new(wtr)),
172        }
173    }
174
175    /// Build a printer from any implementation of `io::Write` and never emit
176    /// any colors, regardless of the user color specification settings.
177    ///
178    /// This is a convenience routine for
179    /// `SummaryBuilder::build(termcolor::NoColor::new(wtr))`.
180    pub fn build_no_color<W: io::Write>(&self, wtr: W) -> Summary<NoColor<W>> {
181        self.build(NoColor::new(wtr))
182    }
183
184    /// Set the output mode for this printer.
185    ///
186    /// The output mode controls how aggregate results of a search are printed.
187    ///
188    /// By default, this printer uses the `Count` mode.
189    pub fn kind(&mut self, kind: SummaryKind) -> &mut SummaryBuilder {
190        self.config.kind = kind;
191        self
192    }
193
194    /// Set the user color specifications to use for coloring in this printer.
195    ///
196    /// A [`UserColorSpec`](crate::UserColorSpec) can be constructed from
197    /// a string in accordance with the color specification format. See
198    /// the `UserColorSpec` type documentation for more details on the
199    /// format. A [`ColorSpecs`] can then be generated from zero or more
200    /// `UserColorSpec`s.
201    ///
202    /// Regardless of the color specifications provided here, whether color
203    /// is actually used or not is determined by the implementation of
204    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
205    /// is provided to `build`, then no color will ever be printed regardless
206    /// of the color specifications provided here.
207    ///
208    /// This completely overrides any previous color specifications. This does
209    /// not add to any previously provided color specifications on this
210    /// builder.
211    ///
212    /// The default color specifications provide no styling.
213    pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut SummaryBuilder {
214        self.config.colors = specs;
215        self
216    }
217
218    /// Set the configuration to use for hyperlinks output by this printer.
219    ///
220    /// Regardless of the hyperlink format provided here, whether hyperlinks
221    /// are actually used or not is determined by the implementation of
222    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
223    /// is provided to `build`, then no hyperlinks will ever be printed
224    /// regardless of the format provided here.
225    ///
226    /// This completely overrides any previous hyperlink format.
227    ///
228    /// The default configuration results in not emitting any hyperlinks.
229    pub fn hyperlink(
230        &mut self,
231        config: HyperlinkConfig,
232    ) -> &mut SummaryBuilder {
233        self.config.hyperlink = config;
234        self
235    }
236
237    /// Enable the gathering of various aggregate statistics.
238    ///
239    /// When this is enabled (it's disabled by default), statistics will be
240    /// gathered for all uses of `Summary` printer returned by `build`,
241    /// including but not limited to, the total number of matches, the total
242    /// number of bytes searched and the total number of bytes printed.
243    ///
244    /// Aggregate statistics can be accessed via the sink's
245    /// [`SummarySink::stats`] method.
246    ///
247    /// When this is enabled, this printer may need to do extra work in order
248    /// to compute certain statistics, which could cause the search to take
249    /// longer. For example, in `Quiet` mode, a search can quit after finding
250    /// the first match, but if `stats` is enabled, then the search will
251    /// continue after the first match in order to compute statistics.
252    ///
253    /// For a complete description of available statistics, see [`Stats`].
254    ///
255    /// Note that some output modes, such as `CountMatches`, automatically
256    /// enable this option even if it has been explicitly disabled.
257    pub fn stats(&mut self, yes: bool) -> &mut SummaryBuilder {
258        self.config.stats = yes;
259        self
260    }
261
262    /// When enabled, if a path was given to the printer, then it is shown in
263    /// the output (either as a heading or as a prefix to each matching line).
264    /// When disabled, then no paths are ever included in the output even when
265    /// a path is provided to the printer.
266    ///
267    /// This setting has no effect in `PathWithMatch` and `PathWithoutMatch`
268    /// modes.
269    ///
270    /// This is enabled by default.
271    pub fn path(&mut self, yes: bool) -> &mut SummaryBuilder {
272        self.config.path = yes;
273        self
274    }
275
276    /// Set the maximum amount of matches that are printed.
277    ///
278    /// If multi line search is enabled and a match spans multiple lines, then
279    /// that match is counted exactly once for the purposes of enforcing this
280    /// limit, regardless of how many lines it spans.
281    ///
282    /// This is disabled by default.
283    pub fn max_matches(&mut self, limit: Option<u64>) -> &mut SummaryBuilder {
284        self.config.max_matches = limit;
285        self
286    }
287
288    /// Exclude count-related summary results with no matches.
289    ///
290    /// When enabled and the mode is either `Count` or `CountMatches`, then
291    /// results are not printed if no matches were found. Otherwise, every
292    /// search prints a result with a possibly `0` number of matches.
293    ///
294    /// This is enabled by default.
295    pub fn exclude_zero(&mut self, yes: bool) -> &mut SummaryBuilder {
296        self.config.exclude_zero = yes;
297        self
298    }
299
300    /// Set the separator used between fields for the `Count` and
301    /// `CountMatches` modes.
302    ///
303    /// By default, this is set to `:`.
304    pub fn separator_field(&mut self, sep: Vec<u8>) -> &mut SummaryBuilder {
305        self.config.separator_field = Arc::new(sep);
306        self
307    }
308
309    /// Set the path separator used when printing file paths.
310    ///
311    /// Typically, printing is done by emitting the file path as is. However,
312    /// this setting provides the ability to use a different path separator
313    /// from what the current environment has configured.
314    ///
315    /// A typical use for this option is to permit cygwin users on Windows to
316    /// set the path separator to `/` instead of using the system default of
317    /// `\`.
318    ///
319    /// This is disabled by default.
320    pub fn separator_path(&mut self, sep: Option<u8>) -> &mut SummaryBuilder {
321        self.config.separator_path = sep;
322        self
323    }
324
325    /// Set the path terminator used.
326    ///
327    /// The path terminator is a byte that is printed after every file path
328    /// emitted by this printer.
329    ///
330    /// If no path terminator is set (the default), then paths are terminated
331    /// by either new lines or the configured field separator.
332    pub fn path_terminator(
333        &mut self,
334        terminator: Option<u8>,
335    ) -> &mut SummaryBuilder {
336        self.config.path_terminator = terminator;
337        self
338    }
339}
340
341/// The summary printer, which emits aggregate results from a search.
342///
343/// Aggregate results generally correspond to file paths and/or the number of
344/// matches found.
345///
346/// A default printer can be created with either of the `Summary::new` or
347/// `Summary::new_no_color` constructors. However, there are a number of
348/// options that configure this printer's output. Those options can be
349/// configured using [`SummaryBuilder`].
350///
351/// This type is generic over `W`, which represents any implementation of
352/// the `termcolor::WriteColor` trait.
353#[derive(Clone, Debug)]
354pub struct Summary<W> {
355    config: Config,
356    wtr: RefCell<CounterWriter<W>>,
357}
358
359impl<W: WriteColor> Summary<W> {
360    /// Return a summary printer with a default configuration that writes
361    /// matches to the given writer.
362    ///
363    /// The writer should be an implementation of `termcolor::WriteColor`
364    /// and not just a bare implementation of `io::Write`. To use a normal
365    /// `io::Write` implementation (simultaneously sacrificing colors), use
366    /// the `new_no_color` constructor.
367    ///
368    /// The default configuration uses the `Count` summary mode.
369    pub fn new(wtr: W) -> Summary<W> {
370        SummaryBuilder::new().build(wtr)
371    }
372}
373
374impl<W: io::Write> Summary<NoColor<W>> {
375    /// Return a summary printer with a default configuration that writes
376    /// matches to the given writer.
377    ///
378    /// The writer can be any implementation of `io::Write`. With this
379    /// constructor, the printer will never emit colors.
380    ///
381    /// The default configuration uses the `Count` summary mode.
382    pub fn new_no_color(wtr: W) -> Summary<NoColor<W>> {
383        SummaryBuilder::new().build_no_color(wtr)
384    }
385}
386
387impl<W: WriteColor> Summary<W> {
388    /// Return an implementation of `Sink` for the summary printer.
389    ///
390    /// This does not associate the printer with a file path, which means this
391    /// implementation will never print a file path. If the output mode of
392    /// this summary printer does not make sense without a file path (such as
393    /// `PathWithMatch` or `PathWithoutMatch`), then any searches executed
394    /// using this sink will immediately quit with an error.
395    pub fn sink<'s, M: Matcher>(
396        &'s mut self,
397        matcher: M,
398    ) -> SummarySink<'static, 's, M, W> {
399        let interpolator =
400            hyperlink::Interpolator::new(&self.config.hyperlink);
401        let stats = if self.config.stats || self.config.kind.requires_stats() {
402            Some(Stats::new())
403        } else {
404            None
405        };
406        SummarySink {
407            matcher,
408            summary: self,
409            interpolator,
410            path: None,
411            start_time: Instant::now(),
412            match_count: 0,
413            binary_byte_offset: None,
414            stats,
415        }
416    }
417
418    /// Return an implementation of `Sink` associated with a file path.
419    ///
420    /// When the printer is associated with a path, then it may, depending on
421    /// its configuration, print the path.
422    pub fn sink_with_path<'p, 's, M, P>(
423        &'s mut self,
424        matcher: M,
425        path: &'p P,
426    ) -> SummarySink<'p, 's, M, W>
427    where
428        M: Matcher,
429        P: ?Sized + AsRef<Path>,
430    {
431        if !self.config.path && !self.config.kind.requires_path() {
432            return self.sink(matcher);
433        }
434        let interpolator =
435            hyperlink::Interpolator::new(&self.config.hyperlink);
436        let stats = if self.config.stats || self.config.kind.requires_stats() {
437            Some(Stats::new())
438        } else {
439            None
440        };
441        let ppath = PrinterPath::new(path.as_ref())
442            .with_separator(self.config.separator_path);
443        SummarySink {
444            matcher,
445            summary: self,
446            interpolator,
447            path: Some(ppath),
448            start_time: Instant::now(),
449            match_count: 0,
450            binary_byte_offset: None,
451            stats,
452        }
453    }
454}
455
456impl<W> Summary<W> {
457    /// Returns true if and only if this printer has written at least one byte
458    /// to the underlying writer during any of the previous searches.
459    pub fn has_written(&self) -> bool {
460        self.wtr.borrow().total_count() > 0
461    }
462
463    /// Return a mutable reference to the underlying writer.
464    pub fn get_mut(&mut self) -> &mut W {
465        self.wtr.get_mut().get_mut()
466    }
467
468    /// Consume this printer and return back ownership of the underlying
469    /// writer.
470    pub fn into_inner(self) -> W {
471        self.wtr.into_inner().into_inner()
472    }
473}
474
475/// An implementation of `Sink` associated with a matcher and an optional file
476/// path for the summary printer.
477///
478/// This type is generic over a few type parameters:
479///
480/// * `'p` refers to the lifetime of the file path, if one is provided. When
481/// no file path is given, then this is `'static`.
482/// * `'s` refers to the lifetime of the [`Summary`] printer that this type
483/// borrows.
484/// * `M` refers to the type of matcher used by
485/// `grep_searcher::Searcher` that is reporting results to this sink.
486/// * `W` refers to the underlying writer that this printer is writing its
487/// output to.
488#[derive(Debug)]
489pub struct SummarySink<'p, 's, M: Matcher, W> {
490    matcher: M,
491    summary: &'s mut Summary<W>,
492    interpolator: hyperlink::Interpolator,
493    path: Option<PrinterPath<'p>>,
494    start_time: Instant,
495    match_count: u64,
496    binary_byte_offset: Option<u64>,
497    stats: Option<Stats>,
498}
499
500impl<'p, 's, M: Matcher, W: WriteColor> SummarySink<'p, 's, M, W> {
501    /// Returns true if and only if this printer received a match in the
502    /// previous search.
503    ///
504    /// This is unaffected by the result of searches before the previous
505    /// search.
506    pub fn has_match(&self) -> bool {
507        match self.summary.config.kind {
508            SummaryKind::PathWithoutMatch => self.match_count == 0,
509            _ => self.match_count > 0,
510        }
511    }
512
513    /// If binary data was found in the previous search, this returns the
514    /// offset at which the binary data was first detected.
515    ///
516    /// The offset returned is an absolute offset relative to the entire
517    /// set of bytes searched.
518    ///
519    /// This is unaffected by the result of searches before the previous
520    /// search. e.g., If the search prior to the previous search found binary
521    /// data but the previous search found no binary data, then this will
522    /// return `None`.
523    pub fn binary_byte_offset(&self) -> Option<u64> {
524        self.binary_byte_offset
525    }
526
527    /// Return a reference to the stats produced by the printer for all
528    /// searches executed on this sink.
529    ///
530    /// This only returns stats if they were requested via the
531    /// [`SummaryBuilder`] configuration.
532    pub fn stats(&self) -> Option<&Stats> {
533        self.stats.as_ref()
534    }
535
536    /// Returns true if and only if the searcher may report matches over
537    /// multiple lines.
538    ///
539    /// Note that this doesn't just return whether the searcher is in multi
540    /// line mode, but also checks if the matter can match over multiple lines.
541    /// If it can't, then we don't need multi line handling, even if the
542    /// searcher has multi line mode enabled.
543    fn multi_line(&self, searcher: &Searcher) -> bool {
544        searcher.multi_line_with_matcher(&self.matcher)
545    }
546
547    /// Returns true if this printer should quit.
548    ///
549    /// This implements the logic for handling quitting after seeing a certain
550    /// amount of matches. In most cases, the logic is simple, but we must
551    /// permit all "after" contextual lines to print after reaching the limit.
552    fn should_quit(&self) -> bool {
553        let limit = match self.summary.config.max_matches {
554            None => return false,
555            Some(limit) => limit,
556        };
557        self.match_count >= limit
558    }
559
560    /// If this printer has a file path associated with it, then this will
561    /// write that path to the underlying writer followed by a line terminator.
562    /// (If a path terminator is set, then that is used instead of the line
563    /// terminator.)
564    fn write_path_line(&mut self, searcher: &Searcher) -> io::Result<()> {
565        if self.path.is_some() {
566            self.write_path()?;
567            if let Some(term) = self.summary.config.path_terminator {
568                self.write(&[term])?;
569            } else {
570                self.write_line_term(searcher)?;
571            }
572        }
573        Ok(())
574    }
575
576    /// If this printer has a file path associated with it, then this will
577    /// write that path to the underlying writer followed by the field
578    /// separator. (If a path terminator is set, then that is used instead of
579    /// the field separator.)
580    fn write_path_field(&mut self) -> io::Result<()> {
581        if self.path.is_some() {
582            self.write_path()?;
583            if let Some(term) = self.summary.config.path_terminator {
584                self.write(&[term])?;
585            } else {
586                self.write(&self.summary.config.separator_field)?;
587            }
588        }
589        Ok(())
590    }
591
592    /// If this printer has a file path associated with it, then this will
593    /// write that path to the underlying writer in the appropriate style
594    /// (color and hyperlink).
595    fn write_path(&mut self) -> io::Result<()> {
596        if self.path.is_some() {
597            let status = self.start_hyperlink()?;
598            self.write_spec(
599                self.summary.config.colors.path(),
600                self.path.as_ref().unwrap().as_bytes(),
601            )?;
602            self.end_hyperlink(status)?;
603        }
604        Ok(())
605    }
606
607    /// Starts a hyperlink span when applicable.
608    fn start_hyperlink(
609        &mut self,
610    ) -> io::Result<hyperlink::InterpolatorStatus> {
611        let Some(hyperpath) =
612            self.path.as_ref().and_then(|p| p.as_hyperlink())
613        else {
614            return Ok(hyperlink::InterpolatorStatus::inactive());
615        };
616        let values = hyperlink::Values::new(hyperpath);
617        self.interpolator.begin(&values, &mut *self.summary.wtr.borrow_mut())
618    }
619
620    fn end_hyperlink(
621        &self,
622        status: hyperlink::InterpolatorStatus,
623    ) -> io::Result<()> {
624        self.interpolator.finish(status, &mut *self.summary.wtr.borrow_mut())
625    }
626
627    /// Write the line terminator configured on the given searcher.
628    fn write_line_term(&self, searcher: &Searcher) -> io::Result<()> {
629        self.write(searcher.line_terminator().as_bytes())
630    }
631
632    /// Write the given bytes using the give style.
633    fn write_spec(&self, spec: &ColorSpec, buf: &[u8]) -> io::Result<()> {
634        self.summary.wtr.borrow_mut().set_color(spec)?;
635        self.write(buf)?;
636        self.summary.wtr.borrow_mut().reset()?;
637        Ok(())
638    }
639
640    /// Write all of the given bytes.
641    fn write(&self, buf: &[u8]) -> io::Result<()> {
642        self.summary.wtr.borrow_mut().write_all(buf)
643    }
644}
645
646impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
647    type Error = io::Error;
648
649    fn matched(
650        &mut self,
651        searcher: &Searcher,
652        mat: &SinkMatch<'_>,
653    ) -> Result<bool, io::Error> {
654        let is_multi_line = self.multi_line(searcher);
655        let sink_match_count = if self.stats.is_none() && !is_multi_line {
656            1
657        } else {
658            // This gives us as many bytes as the searcher can offer. This
659            // isn't guaranteed to hold the necessary context to get match
660            // detection correct (because of look-around), but it does in
661            // practice.
662            let buf = mat.buffer();
663            let range = mat.bytes_range_in_buffer();
664            let mut count = 0;
665            find_iter_at_in_context(
666                searcher,
667                &self.matcher,
668                buf,
669                range,
670                |_| {
671                    count += 1;
672                    true
673                },
674            )?;
675            count
676        };
677        if is_multi_line {
678            self.match_count += sink_match_count;
679        } else {
680            self.match_count += 1;
681        }
682        if let Some(ref mut stats) = self.stats {
683            stats.add_matches(sink_match_count);
684            stats.add_matched_lines(mat.lines().count() as u64);
685        } else if self.summary.config.kind.quit_early() {
686            return Ok(false);
687        }
688        Ok(!self.should_quit())
689    }
690
691    fn binary_data(
692        &mut self,
693        searcher: &Searcher,
694        binary_byte_offset: u64,
695    ) -> Result<bool, io::Error> {
696        if searcher.binary_detection().quit_byte().is_some() {
697            if let Some(ref path) = self.path {
698                log::debug!(
699                    "ignoring {path}: found binary data at \
700                     offset {binary_byte_offset}",
701                    path = path.as_path().display(),
702                );
703            }
704        }
705        Ok(true)
706    }
707
708    fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
709        if self.path.is_none() && self.summary.config.kind.requires_path() {
710            return Err(io::Error::error_message(format!(
711                "output kind {:?} requires a file path",
712                self.summary.config.kind,
713            )));
714        }
715        self.summary.wtr.borrow_mut().reset_count();
716        self.start_time = Instant::now();
717        self.match_count = 0;
718        self.binary_byte_offset = None;
719        if self.summary.config.max_matches == Some(0) {
720            return Ok(false);
721        }
722
723        Ok(true)
724    }
725
726    fn finish(
727        &mut self,
728        searcher: &Searcher,
729        finish: &SinkFinish,
730    ) -> Result<(), io::Error> {
731        self.binary_byte_offset = finish.binary_byte_offset();
732        if let Some(ref mut stats) = self.stats {
733            stats.add_elapsed(self.start_time.elapsed());
734            stats.add_searches(1);
735            if self.match_count > 0 {
736                stats.add_searches_with_match(1);
737            }
738            stats.add_bytes_searched(finish.byte_count());
739            stats.add_bytes_printed(self.summary.wtr.borrow().count());
740        }
741        // If our binary detection method says to quit after seeing binary
742        // data, then we shouldn't print any results at all, even if we've
743        // found a match before detecting binary data. The intent here is to
744        // keep BinaryDetection::quit as a form of filter. Otherwise, we can
745        // present a matching file with a smaller number of matches than
746        // there might be, which can be quite misleading.
747        //
748        // If our binary detection method is to convert binary data, then we
749        // don't quit and therefore search the entire contents of the file.
750        //
751        // There is an unfortunate inconsistency here. Namely, when using
752        // Quiet or PathWithMatch, then the printer can quit after the first
753        // match seen, which could be long before seeing binary data. This
754        // means that using PathWithMatch can print a path where as using
755        // Count might not print it at all because of binary data.
756        //
757        // It's not possible to fix this without also potentially significantly
758        // impacting the performance of Quiet or PathWithMatch, so we accept
759        // the bug.
760        if self.binary_byte_offset.is_some()
761            && searcher.binary_detection().quit_byte().is_some()
762        {
763            // Squash the match count. The statistics reported will still
764            // contain the match count, but the "official" match count should
765            // be zero.
766            self.match_count = 0;
767            return Ok(());
768        }
769
770        let show_count =
771            !self.summary.config.exclude_zero || self.match_count > 0;
772        match self.summary.config.kind {
773            SummaryKind::Count => {
774                if show_count {
775                    self.write_path_field()?;
776                    self.write(self.match_count.to_string().as_bytes())?;
777                    self.write_line_term(searcher)?;
778                }
779            }
780            SummaryKind::CountMatches => {
781                if show_count {
782                    self.write_path_field()?;
783                    let stats = self
784                        .stats
785                        .as_ref()
786                        .expect("CountMatches should enable stats tracking");
787                    self.write(stats.matches().to_string().as_bytes())?;
788                    self.write_line_term(searcher)?;
789                }
790            }
791            SummaryKind::PathWithMatch => {
792                if self.match_count > 0 {
793                    self.write_path_line(searcher)?;
794                }
795            }
796            SummaryKind::PathWithoutMatch => {
797                if self.match_count == 0 {
798                    self.write_path_line(searcher)?;
799                }
800            }
801            SummaryKind::Quiet => {}
802        }
803        Ok(())
804    }
805}
806
807#[cfg(test)]
808mod tests {
809    use grep_regex::RegexMatcher;
810    use grep_searcher::SearcherBuilder;
811    use termcolor::NoColor;
812
813    use super::{Summary, SummaryBuilder, SummaryKind};
814
815    const SHERLOCK: &'static [u8] = b"\
816For the Doctor Watsons of this world, as opposed to the Sherlock
817Holmeses, success in the province of detective work must always
818be, to a very large extent, the result of luck. Sherlock Holmes
819can extract a clew from a wisp of straw or a flake of cigar ash;
820but Doctor Watson has to have it taken out for him and dusted,
821and exhibited clearly, with a label attached.
822";
823
824    fn printer_contents(printer: &mut Summary<NoColor<Vec<u8>>>) -> String {
825        String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
826    }
827
828    #[test]
829    fn path_with_match_error() {
830        let matcher = RegexMatcher::new(r"Watson").unwrap();
831        let mut printer = SummaryBuilder::new()
832            .kind(SummaryKind::PathWithMatch)
833            .build_no_color(vec![]);
834        let res = SearcherBuilder::new().build().search_reader(
835            &matcher,
836            SHERLOCK,
837            printer.sink(&matcher),
838        );
839        assert!(res.is_err());
840    }
841
842    #[test]
843    fn path_without_match_error() {
844        let matcher = RegexMatcher::new(r"Watson").unwrap();
845        let mut printer = SummaryBuilder::new()
846            .kind(SummaryKind::PathWithoutMatch)
847            .build_no_color(vec![]);
848        let res = SearcherBuilder::new().build().search_reader(
849            &matcher,
850            SHERLOCK,
851            printer.sink(&matcher),
852        );
853        assert!(res.is_err());
854    }
855
856    #[test]
857    fn count_no_path() {
858        let matcher = RegexMatcher::new(r"Watson").unwrap();
859        let mut printer = SummaryBuilder::new()
860            .kind(SummaryKind::Count)
861            .build_no_color(vec![]);
862        SearcherBuilder::new()
863            .build()
864            .search_reader(&matcher, SHERLOCK, printer.sink(&matcher))
865            .unwrap();
866
867        let got = printer_contents(&mut printer);
868        assert_eq_printed!("2\n", got);
869    }
870
871    #[test]
872    fn count_no_path_even_with_path() {
873        let matcher = RegexMatcher::new(r"Watson").unwrap();
874        let mut printer = SummaryBuilder::new()
875            .kind(SummaryKind::Count)
876            .path(false)
877            .build_no_color(vec![]);
878        SearcherBuilder::new()
879            .build()
880            .search_reader(
881                &matcher,
882                SHERLOCK,
883                printer.sink_with_path(&matcher, "sherlock"),
884            )
885            .unwrap();
886
887        let got = printer_contents(&mut printer);
888        assert_eq_printed!("2\n", got);
889    }
890
891    #[test]
892    fn count_path() {
893        let matcher = RegexMatcher::new(r"Watson").unwrap();
894        let mut printer = SummaryBuilder::new()
895            .kind(SummaryKind::Count)
896            .build_no_color(vec![]);
897        SearcherBuilder::new()
898            .build()
899            .search_reader(
900                &matcher,
901                SHERLOCK,
902                printer.sink_with_path(&matcher, "sherlock"),
903            )
904            .unwrap();
905
906        let got = printer_contents(&mut printer);
907        assert_eq_printed!("sherlock:2\n", got);
908    }
909
910    #[test]
911    fn count_path_with_zero() {
912        let matcher = RegexMatcher::new(r"NO MATCH").unwrap();
913        let mut printer = SummaryBuilder::new()
914            .kind(SummaryKind::Count)
915            .exclude_zero(false)
916            .build_no_color(vec![]);
917        SearcherBuilder::new()
918            .build()
919            .search_reader(
920                &matcher,
921                SHERLOCK,
922                printer.sink_with_path(&matcher, "sherlock"),
923            )
924            .unwrap();
925
926        let got = printer_contents(&mut printer);
927        assert_eq_printed!("sherlock:0\n", got);
928    }
929
930    #[test]
931    fn count_path_without_zero() {
932        let matcher = RegexMatcher::new(r"NO MATCH").unwrap();
933        let mut printer = SummaryBuilder::new()
934            .kind(SummaryKind::Count)
935            .exclude_zero(true)
936            .build_no_color(vec![]);
937        SearcherBuilder::new()
938            .build()
939            .search_reader(
940                &matcher,
941                SHERLOCK,
942                printer.sink_with_path(&matcher, "sherlock"),
943            )
944            .unwrap();
945
946        let got = printer_contents(&mut printer);
947        assert_eq_printed!("", got);
948    }
949
950    #[test]
951    fn count_path_field_separator() {
952        let matcher = RegexMatcher::new(r"Watson").unwrap();
953        let mut printer = SummaryBuilder::new()
954            .kind(SummaryKind::Count)
955            .separator_field(b"ZZ".to_vec())
956            .build_no_color(vec![]);
957        SearcherBuilder::new()
958            .build()
959            .search_reader(
960                &matcher,
961                SHERLOCK,
962                printer.sink_with_path(&matcher, "sherlock"),
963            )
964            .unwrap();
965
966        let got = printer_contents(&mut printer);
967        assert_eq_printed!("sherlockZZ2\n", got);
968    }
969
970    #[test]
971    fn count_path_terminator() {
972        let matcher = RegexMatcher::new(r"Watson").unwrap();
973        let mut printer = SummaryBuilder::new()
974            .kind(SummaryKind::Count)
975            .path_terminator(Some(b'\x00'))
976            .build_no_color(vec![]);
977        SearcherBuilder::new()
978            .build()
979            .search_reader(
980                &matcher,
981                SHERLOCK,
982                printer.sink_with_path(&matcher, "sherlock"),
983            )
984            .unwrap();
985
986        let got = printer_contents(&mut printer);
987        assert_eq_printed!("sherlock\x002\n", got);
988    }
989
990    #[test]
991    fn count_path_separator() {
992        let matcher = RegexMatcher::new(r"Watson").unwrap();
993        let mut printer = SummaryBuilder::new()
994            .kind(SummaryKind::Count)
995            .separator_path(Some(b'\\'))
996            .build_no_color(vec![]);
997        SearcherBuilder::new()
998            .build()
999            .search_reader(
1000                &matcher,
1001                SHERLOCK,
1002                printer.sink_with_path(&matcher, "/home/andrew/sherlock"),
1003            )
1004            .unwrap();
1005
1006        let got = printer_contents(&mut printer);
1007        assert_eq_printed!("\\home\\andrew\\sherlock:2\n", got);
1008    }
1009
1010    #[test]
1011    fn count_max_matches() {
1012        let matcher = RegexMatcher::new(r"Watson").unwrap();
1013        let mut printer = SummaryBuilder::new()
1014            .kind(SummaryKind::Count)
1015            .max_matches(Some(1))
1016            .build_no_color(vec![]);
1017        SearcherBuilder::new()
1018            .build()
1019            .search_reader(&matcher, SHERLOCK, printer.sink(&matcher))
1020            .unwrap();
1021
1022        let got = printer_contents(&mut printer);
1023        assert_eq_printed!("1\n", got);
1024    }
1025
1026    #[test]
1027    fn count_matches() {
1028        let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
1029        let mut printer = SummaryBuilder::new()
1030            .kind(SummaryKind::CountMatches)
1031            .build_no_color(vec![]);
1032        SearcherBuilder::new()
1033            .build()
1034            .search_reader(
1035                &matcher,
1036                SHERLOCK,
1037                printer.sink_with_path(&matcher, "sherlock"),
1038            )
1039            .unwrap();
1040
1041        let got = printer_contents(&mut printer);
1042        assert_eq_printed!("sherlock:4\n", got);
1043    }
1044
1045    #[test]
1046    fn path_with_match_found() {
1047        let matcher = RegexMatcher::new(r"Watson").unwrap();
1048        let mut printer = SummaryBuilder::new()
1049            .kind(SummaryKind::PathWithMatch)
1050            .build_no_color(vec![]);
1051        SearcherBuilder::new()
1052            .build()
1053            .search_reader(
1054                &matcher,
1055                SHERLOCK,
1056                printer.sink_with_path(&matcher, "sherlock"),
1057            )
1058            .unwrap();
1059
1060        let got = printer_contents(&mut printer);
1061        assert_eq_printed!("sherlock\n", got);
1062    }
1063
1064    #[test]
1065    fn path_with_match_not_found() {
1066        let matcher = RegexMatcher::new(r"ZZZZZZZZ").unwrap();
1067        let mut printer = SummaryBuilder::new()
1068            .kind(SummaryKind::PathWithMatch)
1069            .build_no_color(vec![]);
1070        SearcherBuilder::new()
1071            .build()
1072            .search_reader(
1073                &matcher,
1074                SHERLOCK,
1075                printer.sink_with_path(&matcher, "sherlock"),
1076            )
1077            .unwrap();
1078
1079        let got = printer_contents(&mut printer);
1080        assert_eq_printed!("", got);
1081    }
1082
1083    #[test]
1084    fn path_without_match_found() {
1085        let matcher = RegexMatcher::new(r"ZZZZZZZZZ").unwrap();
1086        let mut printer = SummaryBuilder::new()
1087            .kind(SummaryKind::PathWithoutMatch)
1088            .build_no_color(vec![]);
1089        SearcherBuilder::new()
1090            .build()
1091            .search_reader(
1092                &matcher,
1093                SHERLOCK,
1094                printer.sink_with_path(&matcher, "sherlock"),
1095            )
1096            .unwrap();
1097
1098        let got = printer_contents(&mut printer);
1099        assert_eq_printed!("sherlock\n", got);
1100    }
1101
1102    #[test]
1103    fn path_without_match_not_found() {
1104        let matcher = RegexMatcher::new(r"Watson").unwrap();
1105        let mut printer = SummaryBuilder::new()
1106            .kind(SummaryKind::PathWithoutMatch)
1107            .build_no_color(vec![]);
1108        SearcherBuilder::new()
1109            .build()
1110            .search_reader(
1111                &matcher,
1112                SHERLOCK,
1113                printer.sink_with_path(&matcher, "sherlock"),
1114            )
1115            .unwrap();
1116
1117        let got = printer_contents(&mut printer);
1118        assert_eq_printed!("", got);
1119    }
1120
1121    #[test]
1122    fn quiet() {
1123        let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
1124        let mut printer = SummaryBuilder::new()
1125            .kind(SummaryKind::Quiet)
1126            .build_no_color(vec![]);
1127        let match_count = {
1128            let mut sink = printer.sink_with_path(&matcher, "sherlock");
1129            SearcherBuilder::new()
1130                .build()
1131                .search_reader(&matcher, SHERLOCK, &mut sink)
1132                .unwrap();
1133            sink.match_count
1134        };
1135
1136        let got = printer_contents(&mut printer);
1137        assert_eq_printed!("", got);
1138        // There is actually more than one match, but Quiet should quit after
1139        // finding the first one.
1140        assert_eq!(1, match_count);
1141    }
1142
1143    #[test]
1144    fn quiet_with_stats() {
1145        let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
1146        let mut printer = SummaryBuilder::new()
1147            .kind(SummaryKind::Quiet)
1148            .stats(true)
1149            .build_no_color(vec![]);
1150        let match_count = {
1151            let mut sink = printer.sink_with_path(&matcher, "sherlock");
1152            SearcherBuilder::new()
1153                .build()
1154                .search_reader(&matcher, SHERLOCK, &mut sink)
1155                .unwrap();
1156            sink.match_count
1157        };
1158
1159        let got = printer_contents(&mut printer);
1160        assert_eq_printed!("", got);
1161        // There is actually more than one match, and Quiet will usually quit
1162        // after finding the first one, but since we request stats, it will
1163        // mush on to find all matches.
1164        assert_eq!(3, match_count);
1165    }
1166}