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