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}