Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/tests/annealer_corpus/rg_printer.rs

133 KiB, 1 run

created by r1870400018:11802, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use std::{
2 cell::{Cell, RefCell},
3 cmp,
4 io::{self, Write},
5 path::Path,
6 sync::Arc,
7 time::Instant,
8};
9
10use {
11 bstr::ByteSlice,
12 grep_matcher::{Match, Matcher},
13 grep_searcher::{
14 LineStep, Searcher, Sink, SinkContext, SinkFinish, SinkMatch,
15 },
16 termcolor::{ColorSpec, NoColor, WriteColor},
17};
18
19use crate::{
20 color::ColorSpecs,
21 counter::CounterWriter,
22 hyperlink::{self, HyperlinkConfig},
23 stats::Stats,
24 util::{
25 DecimalFormatter, PrinterPath, Replacer, Sunk,
26 find_iter_at_in_context, trim_ascii_prefix, trim_line_terminator,
27 },
28};
29
30/// The configuration for the standard printer.
31///
32/// This is manipulated by the StandardBuilder and then referenced by the
33/// actual implementation. Once a printer is build, the configuration is frozen
34/// and cannot changed.
35#[derive(Debug, Clone)]
36struct Config {
37 colors: ColorSpecs,
38 hyperlink: HyperlinkConfig,
39 stats: bool,
40 heading: bool,
41 path: bool,
42 only_matching: bool,
43 per_match: bool,
44 per_match_one_line: bool,
45 replacement: Arc<Option<Vec<u8>>>,
46 max_columns: Option<u64>,
47 max_columns_preview: bool,
48 column: bool,
49 byte_offset: bool,
50 trim_ascii: bool,
51 separator_search: Arc<Option<Vec<u8>>>,
52 separator_context: Arc<Option<Vec<u8>>>,
53 separator_field_match: Arc<Vec<u8>>,
54 separator_field_context: Arc<Vec<u8>>,
55 separator_path: Option<u8>,
56 path_terminator: Option<u8>,
57}
58
59impl Default for Config {
60 fn default() -> Config {
61 Config {
62 colors: ColorSpecs::default(),
63 hyperlink: HyperlinkConfig::default(),
64 stats: false,
65 heading: false,
66 path: true,
67 only_matching: false,
68 per_match: false,
69 per_match_one_line: false,
70 replacement: Arc::new(None),
71 max_columns: None,
72 max_columns_preview: false,
73 column: false,
74 byte_offset: false,
75 trim_ascii: false,
76 separator_search: Arc::new(None),
77 separator_context: Arc::new(Some(b"--".to_vec())),
78 separator_field_match: Arc::new(b":".to_vec()),
79 separator_field_context: Arc::new(b"-".to_vec()),
80 separator_path: None,
81 path_terminator: None,
82 }
83 }
84}
85
86/// A builder for the "standard" grep-like printer.
87///
88/// The builder permits configuring how the printer behaves. Configurable
89/// behavior includes, but is not limited to, limiting the number of matches,
90/// tweaking separators, executing pattern replacements, recording statistics
91/// and setting colors.
92///
93/// Some configuration options, such as the display of line numbers or
94/// contextual lines, are drawn directly from the
95/// `grep_searcher::Searcher`'s configuration.
96///
97/// Once a `Standard` printer is built, its configuration cannot be changed.
98#[derive(Clone, Debug)]
99pub struct StandardBuilder {
100 config: Config,
101}
102
103impl StandardBuilder {
104 /// Return a new builder for configuring the standard printer.
105 pub fn new() -> StandardBuilder {
106 StandardBuilder { config: Config::default() }
107 }
108
109 /// Build a printer using any implementation of `termcolor::WriteColor`.
110 ///
111 /// The implementation of `WriteColor` used here controls whether colors
112 /// are used or not when colors have been configured using the
113 /// `color_specs` method.
114 ///
115 /// For maximum portability, callers should generally use either
116 /// `termcolor::StandardStream` or `termcolor::BufferedStandardStream`
117 /// where appropriate, which will automatically enable colors on Windows
118 /// when possible.
119 ///
120 /// However, callers may also provide an arbitrary writer using the
121 /// `termcolor::Ansi` or `termcolor::NoColor` wrappers, which always enable
122 /// colors via ANSI escapes or always disable colors, respectively.
123 ///
124 /// As a convenience, callers may use `build_no_color` to automatically
125 /// select the `termcolor::NoColor` wrapper to avoid needing to import
126 /// from `termcolor` explicitly.
127 pub fn build<W: WriteColor>(&self, wtr: W) -> Standard<W> {
128 Standard {
129 config: self.config.clone(),
130 wtr: RefCell::new(CounterWriter::new(wtr)),
131 matches: vec![],
132 }
133 }
134
135 /// Build a printer from any implementation of `io::Write` and never emit
136 /// any colors, regardless of the user color specification settings.
137 ///
138 /// This is a convenience routine for
139 /// `StandardBuilder::build(termcolor::NoColor::new(wtr))`.
140 pub fn build_no_color<W: io::Write>(
141 &self,
142 wtr: W,
143 ) -> Standard<NoColor<W>> {
144 self.build(NoColor::new(wtr))
145 }
146
147 /// Set the user color specifications to use for coloring in this printer.
148 ///
149 /// A [`UserColorSpec`](crate::UserColorSpec) can be constructed from
150 /// a string in accordance with the color specification format. See
151 /// the `UserColorSpec` type documentation for more details on the
152 /// format. A [`ColorSpecs`] can then be generated from zero or more
153 /// `UserColorSpec`s.
154 ///
155 /// Regardless of the color specifications provided here, whether color
156 /// is actually used or not is determined by the implementation of
157 /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
158 /// is provided to `build`, then no color will ever be printed regardless
159 /// of the color specifications provided here.
160 ///
161 /// This completely overrides any previous color specifications. This does
162 /// not add to any previously provided color specifications on this
163 /// builder.
164 pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut StandardBuilder {
165 self.config.colors = specs;
166 self
167 }
168
169 /// Set the configuration to use for hyperlinks output by this printer.
170 ///
171 /// Regardless of the hyperlink format provided here, whether hyperlinks
172 /// are actually used or not is determined by the implementation of
173 /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
174 /// is provided to `build`, then no hyperlinks will ever be printed
175 /// regardless of the format provided here.
176 ///
177 /// This completely overrides any previous hyperlink format.
178 ///
179 /// The default configuration results in not emitting any hyperlinks.
180 pub fn hyperlink(
181 &mut self,
182 config: HyperlinkConfig,
183 ) -> &mut StandardBuilder {
184 self.config.hyperlink = config;
185 self
186 }
187
188 /// Enable the gathering of various aggregate statistics.
189 ///
190 /// When this is enabled (it's disabled by default), statistics will be
191 /// gathered for all uses of `Standard` printer returned by `build`,
192 /// including but not limited to, the total number of matches, the total
193 /// number of bytes searched and the total number of bytes printed.
194 ///
195 /// Aggregate statistics can be accessed via the sink's
196 /// [`StandardSink::stats`] method.
197 ///
198 /// When this is enabled, this printer may need to do extra work in order
199 /// to compute certain statistics, which could cause the search to take
200 /// longer.
201 ///
202 /// For a complete description of available statistics, see [`Stats`].
203 pub fn stats(&mut self, yes: bool) -> &mut StandardBuilder {
204 self.config.stats = yes;
205 self
206 }
207
208 /// Enable the use of "headings" in the printer.
209 ///
210 /// When this is enabled, and if a file path has been given to the printer,
211 /// then the file path will be printed once on its own line before showing
212 /// any matches. If the heading is not the first thing emitted by the
213 /// printer, then a line terminator is printed before the heading.
214 ///
215 /// By default, this option is disabled. When disabled, the printer will
216 /// not show any heading and will instead print the file path (if one is
217 /// given) on the same line as each matching (or context) line.
218 pub fn heading(&mut self, yes: bool) -> &mut StandardBuilder {
219 self.config.heading = yes;
220 self
221 }
222
223 /// When enabled, if a path was given to the printer, then it is shown in
224 /// the output (either as a heading or as a prefix to each matching line).
225 /// When disabled, then no paths are ever included in the output even when
226 /// a path is provided to the printer.
227 ///
228 /// This is enabled by default.
229 pub fn path(&mut self, yes: bool) -> &mut StandardBuilder {
230 self.config.path = yes;
231 self
232 }
233
234 /// Only print the specific matches instead of the entire line containing
235 /// each match. Each match is printed on its own line. When multi line
236 /// search is enabled, then matches spanning multiple lines are printed
237 /// such that only the matching portions of each line are shown.
238 pub fn only_matching(&mut self, yes: bool) -> &mut StandardBuilder {
239 self.config.only_matching = yes;
240 self
241 }
242
243 /// Print at least one line for every match.
244 ///
245 /// This is similar to the `only_matching` option, except the entire line
246 /// is printed for each match. This is typically useful in conjunction with
247 /// the `column` option, which will show the starting column number for
248 /// every match on every line.
249 ///
250 /// When multi-line mode is enabled, each match is printed, including every
251 /// line in the match. As with single line matches, if a line contains
252 /// multiple matches (even if only partially), then that line is printed
253 /// once for each match it participates in, assuming it's the first line in
254 /// that match. In multi-line mode, column numbers only indicate the start
255 /// of a match. Subsequent lines in a multi-line match always have a column
256 /// number of `1`.
257 ///
258 /// When a match contains multiple lines, enabling `per_match_one_line`
259 /// will cause only the first line each in match to be printed.
260 pub fn per_match(&mut self, yes: bool) -> &mut StandardBuilder {
261 self.config.per_match = yes;
262 self
263 }
264
265 /// Print at most one line per match when `per_match` is enabled.
266 ///
267 /// By default, every line in each match found is printed when `per_match`
268 /// is enabled. However, this is sometimes undesirable, e.g., when you
269 /// only ever want one line per match.
270 ///
271 /// This is only applicable when multi-line matching is enabled, since
272 /// otherwise, matches are guaranteed to span one line.
273 ///
274 /// This is disabled by default.
275 pub fn per_match_one_line(&mut self, yes: bool) -> &mut StandardBuilder {
276 self.config.per_match_one_line = yes;
277 self
278 }
279
280 /// Set the bytes that will be used to replace each occurrence of a match
281 /// found.
282 ///
283 /// The replacement bytes given may include references to capturing groups,
284 /// which may either be in index form (e.g., `$2`) or can reference named
285 /// capturing groups if present in the original pattern (e.g., `$foo`).
286 ///
287 /// For documentation on the full format, please see the `Capture` trait's
288 /// `interpolate` method in the
289 /// [grep-printer](https://docs.rs/grep-printer) crate.
290 pub fn replacement(
291 &mut self,
292 replacement: Option<Vec<u8>>,
293 ) -> &mut StandardBuilder {
294 self.config.replacement = Arc::new(replacement);
295 self
296 }
297
298 /// Set the maximum number of columns allowed for each line printed. A
299 /// single column is heuristically defined as a single byte.
300 ///
301 /// If a line is found which exceeds this maximum, then it is replaced
302 /// with a message indicating that the line has been omitted.
303 ///
304 /// The default is to not specify a limit, in which each matching or
305 /// contextual line is printed regardless of how long it is.
306 pub fn max_columns(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
307 self.config.max_columns = limit;
308 self
309 }
310
311 /// When enabled, if a line is found to be over the configured maximum
312 /// column limit (measured in terms of bytes), then a preview of the long
313 /// line will be printed instead.
314 ///
315 /// The preview will correspond to the first `N` *grapheme clusters* of
316 /// the line, where `N` is the limit configured by `max_columns`.
317 ///
318 /// If no limit is set, then enabling this has no effect.
319 ///
320 /// This is disabled by default.
321 pub fn max_columns_preview(&mut self, yes: bool) -> &mut StandardBuilder {
322 self.config.max_columns_preview = yes;
323 self
324 }
325
326 /// Print the column number of the first match in a line.
327 ///
328 /// This option is convenient for use with `per_match` which will print a
329 /// line for every match along with the starting offset for that match.
330 ///
331 /// Column numbers are computed in terms of bytes from the start of the
332 /// line being printed.
333 ///
334 /// This is disabled by default.
335 pub fn column(&mut self, yes: bool) -> &mut StandardBuilder {
336 self.config.column = yes;
337 self
338 }
339
340 /// Print the absolute byte offset of the beginning of each line printed.
341 ///
342 /// The absolute byte offset starts from the beginning of each search and
343 /// is zero based.
344 ///
345 /// If the `only_matching` option is set, then this will print the absolute
346 /// byte offset of the beginning of each match.
347 pub fn byte_offset(&mut self, yes: bool) -> &mut StandardBuilder {
348 self.config.byte_offset = yes;
349 self
350 }
351
352 /// When enabled, all lines will have prefix ASCII whitespace trimmed
353 /// before being written.
354 ///
355 /// This is disabled by default.
356 pub fn trim_ascii(&mut self, yes: bool) -> &mut StandardBuilder {
357 self.config.trim_ascii = yes;
358 self
359 }
360
361 /// Set the separator used between sets of search results.
362 ///
363 /// When this is set, then it will be printed on its own line immediately
364 /// before the results for a single search if and only if a previous search
365 /// had already printed results. In effect, this permits showing a divider
366 /// between sets of search results that does not appear at the beginning
367 /// or end of all search results.
368 ///
369 /// To reproduce the classic grep format, this is typically set to `--`
370 /// (the same as the context separator) if and only if contextual lines
371 /// have been requested, but disabled otherwise.
372 ///
373 /// By default, this is disabled.
374 pub fn separator_search(
375 &mut self,
376 sep: Option<Vec<u8>>,
377 ) -> &mut StandardBuilder {
378 self.config.separator_search = Arc::new(sep);
379 self
380 }
381
382 /// Set the separator used between discontiguous runs of search context,
383 /// but only when the searcher is configured to report contextual lines.
384 ///
385 /// The separator is always printed on its own line, even if it's empty.
386 ///
387 /// If no separator is set, then nothing is printed when a context break
388 /// occurs.
389 ///
390 /// By default, this is set to `--`.
391 pub fn separator_context(
392 &mut self,
393 sep: Option<Vec<u8>>,
394 ) -> &mut StandardBuilder {
395 self.config.separator_context = Arc::new(sep);
396 self
397 }
398
399 /// Set the separator used between fields emitted for matching lines.
400 ///
401 /// For example, when the searcher has line numbers enabled, this printer
402 /// will print the line number before each matching line. The bytes given
403 /// here will be written after the line number but before the matching
404 /// line.
405 ///
406 /// By default, this is set to `:`.
407 pub fn separator_field_match(
408 &mut self,
409 sep: Vec<u8>,
410 ) -> &mut StandardBuilder {
411 self.config.separator_field_match = Arc::new(sep);
412 self
413 }
414
415 /// Set the separator used between fields emitted for context lines.
416 ///
417 /// For example, when the searcher has line numbers enabled, this printer
418 /// will print the line number before each context line. The bytes given
419 /// here will be written after the line number but before the context
420 /// line.
421 ///
422 /// By default, this is set to `-`.
423 pub fn separator_field_context(
424 &mut self,
425 sep: Vec<u8>,
426 ) -> &mut StandardBuilder {
427 self.config.separator_field_context = Arc::new(sep);
428 self
429 }
430
431 /// Set the path separator used when printing file paths.
432 ///
433 /// When a printer is configured with a file path, and when a match is
434 /// found, that file path will be printed (either as a heading or as a
435 /// prefix to each matching or contextual line, depending on other
436 /// configuration settings). Typically, printing is done by emitting the
437 /// file path as is. However, this setting provides the ability to use a
438 /// different path separator from what the current environment has
439 /// configured.
440 ///
441 /// A typical use for this option is to permit cygwin users on Windows to
442 /// set the path separator to `/` instead of using the system default of
443 /// `\`.
444 pub fn separator_path(&mut self, sep: Option<u8>) -> &mut StandardBuilder {
445 self.config.separator_path = sep;
446 self
447 }
448
449 /// Set the path terminator used.
450 ///
451 /// The path terminator is a byte that is printed after every file path
452 /// emitted by this printer.
453 ///
454 /// If no path terminator is set (the default), then paths are terminated
455 /// by either new lines (for when `heading` is enabled) or the match or
456 /// context field separators (e.g., `:` or `-`).
457 pub fn path_terminator(
458 &mut self,
459 terminator: Option<u8>,
460 ) -> &mut StandardBuilder {
461 self.config.path_terminator = terminator;
462 self
463 }
464}
465
466/// The standard printer, which implements grep-like formatting, including
467/// color support.
468///
469/// A default printer can be created with either of the `Standard::new` or
470/// `Standard::new_no_color` constructors. However, there are a considerable
471/// number of options that configure this printer's output. Those options can
472/// be configured using [`StandardBuilder`].
473///
474/// This type is generic over `W`, which represents any implementation
475/// of the `termcolor::WriteColor` trait. If colors are not desired,
476/// then the `new_no_color` constructor can be used, or, alternatively,
477/// the `termcolor::NoColor` adapter can be used to wrap any `io::Write`
478/// implementation without enabling any colors.
479#[derive(Clone, Debug)]
480pub struct Standard<W> {
481 config: Config,
482 wtr: RefCell<CounterWriter<W>>,
483 matches: Vec<Match>,
484}
485
486impl<W: WriteColor> Standard<W> {
487 /// Return a standard printer with a default configuration that writes
488 /// matches to the given writer.
489 ///
490 /// The writer should be an implementation of `termcolor::WriteColor`
491 /// and not just a bare implementation of `io::Write`. To use a normal
492 /// `io::Write` implementation (simultaneously sacrificing colors), use
493 /// the `new_no_color` constructor.
494 pub fn new(wtr: W) -> Standard<W> {
495 StandardBuilder::new().build(wtr)
496 }
497}
498
499impl<W: io::Write> Standard<NoColor<W>> {
500 /// Return a standard printer with a default configuration that writes
501 /// matches to the given writer.
502 ///
503 /// The writer can be any implementation of `io::Write`. With this
504 /// constructor, the printer will never emit colors.
505 pub fn new_no_color(wtr: W) -> Standard<NoColor<W>> {
506 StandardBuilder::new().build_no_color(wtr)
507 }
508}
509
510impl<W: WriteColor> Standard<W> {
511 /// Return an implementation of `Sink` for the standard printer.
512 ///
513 /// This does not associate the printer with a file path, which means this
514 /// implementation will never print a file path along with the matches.
515 pub fn sink<'s, M: Matcher>(
516 &'s mut self,
517 matcher: M,
518 ) -> StandardSink<'static, 's, M, W> {
519 let interpolator =
520 hyperlink::Interpolator::new(&self.config.hyperlink);
521 let stats = if self.config.stats { Some(Stats::new()) } else { None };
522 let needs_match_granularity = self.needs_match_granularity();
523 StandardSink {
524 matcher,
525 standard: self,
526 replacer: Replacer::new(),
527 interpolator,
528 path: None,
529 start_time: Instant::now(),
530 match_count: 0,
531 binary_byte_offset: None,
532 stats,
533 needs_match_granularity,
534 }
535 }
536
537 /// Return an implementation of `Sink` associated with a file path.
538 ///
539 /// When the printer is associated with a path, then it may, depending on
540 /// its configuration, print the path along with the matches found.
541 pub fn sink_with_path<'p, 's, M, P>(
542 &'s mut self,
543 matcher: M,
544 path: &'p P,
545 ) -> StandardSink<'p, 's, M, W>
546 where
547 M: Matcher,
548 P: ?Sized + AsRef<Path>,
549 {
550 if !self.config.path {
551 return self.sink(matcher);
552 }
553 let interpolator =
554 hyperlink::Interpolator::new(&self.config.hyperlink);
555 let stats = if self.config.stats { Some(Stats::new()) } else { None };
556 let ppath = PrinterPath::new(path.as_ref())
557 .with_separator(self.config.separator_path);
558 let needs_match_granularity = self.needs_match_granularity();
559 StandardSink {
560 matcher,
561 standard: self,
562 replacer: Replacer::new(),
563 interpolator,
564 path: Some(ppath),
565 start_time: Instant::now(),
566 match_count: 0,
567 binary_byte_offset: None,
568 stats,
569 needs_match_granularity,
570 }
571 }
572
573 /// Returns true if and only if the configuration of the printer requires
574 /// us to find each individual match in the lines reported by the searcher.
575 ///
576 /// We care about this distinction because finding each individual match
577 /// costs more, so we only do it when we need to.
578 fn needs_match_granularity(&self) -> bool {
579 let supports_color = self.wtr.borrow().supports_color();
580 let match_colored = !self.config.colors.matched().is_none();
581
582 // Coloring requires identifying each individual match.
583 (supports_color && match_colored)
584 // The column feature requires finding the position of the first match.
585 || self.config.column
586 // Requires finding each match for performing replacement.
587 || self.config.replacement.is_some()
588 // Emitting a line for each match requires finding each match.
589 || self.config.per_match
590 // Emitting only the match requires finding each match.
591 || self.config.only_matching
592 // Computing certain statistics requires finding each match.
593 || self.config.stats
594 }
595}
596
597impl<W> Standard<W> {
598 /// Returns true if and only if this printer has written at least one byte
599 /// to the underlying writer during any of the previous searches.
600 pub fn has_written(&self) -> bool {
601 self.wtr.borrow().total_count() > 0
602 }
603
604 /// Return a mutable reference to the underlying writer.
605 pub fn get_mut(&mut self) -> &mut W {
606 self.wtr.get_mut().get_mut()
607 }
608
609 /// Consume this printer and return back ownership of the underlying
610 /// writer.
611 pub fn into_inner(self) -> W {
612 self.wtr.into_inner().into_inner()
613 }
614}
615
616/// An implementation of `Sink` associated with a matcher and an optional file
617/// path for the standard printer.
618///
619/// A `Sink` can be created via the [`Standard::sink`] or
620/// [`Standard::sink_with_path`] methods, depending on whether you want to
621/// include a file path in the printer's output.
622///
623/// Building a `StandardSink` is cheap, and callers should create a new one
624/// for each thing that is searched. After a search has completed, callers may
625/// query this sink for information such as whether a match occurred or whether
626/// binary data was found (and if so, the offset at which it occurred).
627///
628/// This type is generic over a few type parameters:
629///
630/// * `'p` refers to the lifetime of the file path, if one is provided. When
631/// no file path is given, then this is `'static`.
632/// * `'s` refers to the lifetime of the [`Standard`] printer that this type
633/// borrows.
634/// * `M` refers to the type of matcher used by
635/// `grep_searcher::Searcher` that is reporting results to this sink.
636/// * `W` refers to the underlying writer that this printer is writing its
637/// output to.
638#[derive(Debug)]
639pub struct StandardSink<'p, 's, M: Matcher, W> {
640 matcher: M,
641 standard: &'s mut Standard<W>,
642 replacer: Replacer<M>,
643 interpolator: hyperlink::Interpolator,
644 path: Option<PrinterPath<'p>>,
645 start_time: Instant,
646 match_count: u64,
647 binary_byte_offset: Option<u64>,
648 stats: Option<Stats>,
649 needs_match_granularity: bool,
650}
651
652impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
653 /// Returns true if and only if this printer received a match in the
654 /// previous search.
655 ///
656 /// This is unaffected by the result of searches before the previous
657 /// search on this sink.
658 pub fn has_match(&self) -> bool {
659 self.match_count > 0
660 }
661
662 /// Return the total number of matches reported to this sink.
663 ///
664 /// This corresponds to the number of times `Sink::matched` is called
665 /// on the previous search.
666 ///
667 /// This is unaffected by the result of searches before the previous
668 /// search on this sink.
669 pub fn match_count(&self) -> u64 {
670 self.match_count
671 }
672
673 /// If binary data was found in the previous search, this returns the
674 /// offset at which the binary data was first detected.
675 ///
676 /// The offset returned is an absolute offset relative to the entire
677 /// set of bytes searched.
678 ///
679 /// This is unaffected by the result of searches before the previous
680 /// search. e.g., If the search prior to the previous search found binary
681 /// data but the previous search found no binary data, then this will
682 /// return `None`.
683 pub fn binary_byte_offset(&self) -> Option<u64> {
684 self.binary_byte_offset
685 }
686
687 /// Return a reference to the stats produced by the printer for all
688 /// searches executed on this sink.
689 ///
690 /// This only returns stats if they were requested via the
691 /// [`StandardBuilder`] configuration.
692 pub fn stats(&self) -> Option<&Stats> {
693 self.stats.as_ref()
694 }
695
696 /// Execute the matcher over the given bytes and record the match
697 /// locations if the current configuration demands match granularity.
698 fn record_matches(
699 &mut self,
700 searcher: &Searcher,
701 bytes: &[u8],
702 range: std::ops::Range<usize>,
703 ) -> io::Result<()> {
704 self.standard.matches.clear();
705 if !self.needs_match_granularity {
706 return Ok(());
707 }
708 // If printing requires knowing the location of each individual match,
709 // then compute and stored those right now for use later. While this
710 // adds an extra copy for storing the matches, we do amortize the
711 // allocation for it and this greatly simplifies the printing logic to
712 // the extent that it's easy to ensure that we never do more than
713 // one search to find the matches (well, for replacements, we do one
714 // additional search to perform the actual replacement).
715 let matches = &mut self.standard.matches;
716 find_iter_at_in_context(
717 searcher,
718 &self.matcher,
719 bytes,
720 range.clone(),
721 |m| {
722 let (s, e) = (m.start() - range.start, m.end() - range.start);
723 matches.push(Match::new(s, e));
724 true
725 },
726 )?;
727 // Don't report empty matches appearing at the end of the bytes.
728 if !matches.is_empty()
729 && matches.last().unwrap().is_empty()
730 && matches.last().unwrap().start() >= range.end
731 {
732 matches.pop().unwrap();
733 }
734 Ok(())
735 }
736
737 /// If the configuration specifies a replacement, then this executes the
738 /// replacement, lazily allocating memory if necessary.
739 ///
740 /// To access the result of a replacement, use `replacer.replacement()`.
741 fn replace(
742 &mut self,
743 searcher: &Searcher,
744 bytes: &[u8],
745 range: std::ops::Range<usize>,
746 ) -> io::Result<()> {
747 self.replacer.clear();
748 if self.standard.config.replacement.is_some() {
749 let replacement =
750 (*self.standard.config.replacement).as_ref().unwrap();
751 self.replacer.replace_all(
752 searcher,
753 &self.matcher,
754 bytes,
755 range,
756 replacement,
757 )?;
758 }
759 Ok(())
760 }
761}
762
763impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
764 type Error = io::Error;
765
766 fn matched(
767 &mut self,
768 searcher: &Searcher,
769 mat: &SinkMatch<'_>,
770 ) -> Result<bool, io::Error> {
771 self.match_count += 1;
772
773 self.record_matches(
774 searcher,
775 mat.buffer(),
776 mat.bytes_range_in_buffer(),
777 )?;
778 self.replace(searcher, mat.buffer(), mat.bytes_range_in_buffer())?;
779
780 if let Some(ref mut stats) = self.stats {
781 stats.add_matches(self.standard.matches.len() as u64);
782 stats.add_matched_lines(mat.lines().count() as u64);
783 }
784 if searcher.binary_detection().convert_byte().is_some() {
785 if self.binary_byte_offset.is_some() {
786 return Ok(false);
787 }
788 }
789 StandardImpl::from_match(searcher, self, mat).sink()?;
790 Ok(true)
791 }
792
793 fn context(
794 &mut self,
795 searcher: &Searcher,
796 ctx: &SinkContext<'_>,
797 ) -> Result<bool, io::Error> {
798 self.standard.matches.clear();
799 self.replacer.clear();
800
801 if searcher.invert_match() {
802 self.record_matches(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
803 self.replace(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
804 }
805 if searcher.binary_detection().convert_byte().is_some() {
806 if self.binary_byte_offset.is_some() {
807 return Ok(false);
808 }
809 }
810
811 StandardImpl::from_context(searcher, self, ctx).sink()?;
812 Ok(true)
813 }
814
815 fn context_break(
816 &mut self,
817 searcher: &Searcher,
818 ) -> Result<bool, io::Error> {
819 StandardImpl::new(searcher, self).write_context_separator()?;
820 Ok(true)
821 }
822
823 fn binary_data(
824 &mut self,
825 searcher: &Searcher,
826 binary_byte_offset: u64,
827 ) -> Result<bool, io::Error> {
828 if searcher.binary_detection().quit_byte().is_some() {
829 if let Some(ref path) = self.path {
830 log::debug!(
831 "ignoring {path}: found binary data at \
832 offset {binary_byte_offset}",
833 path = path.as_path().display(),
834 );
835 }
836 }
837 self.binary_byte_offset = Some(binary_byte_offset);
838 Ok(true)
839 }
840
841 fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
842 self.standard.wtr.borrow_mut().reset_count();
843 self.start_time = Instant::now();
844 self.match_count = 0;
845 self.binary_byte_offset = None;
846 Ok(true)
847 }
848
849 fn finish(
850 &mut self,
851 searcher: &Searcher,
852 finish: &SinkFinish,
853 ) -> Result<(), io::Error> {
854 if let Some(offset) = self.binary_byte_offset {
855 StandardImpl::new(searcher, self).write_binary_message(offset)?;
856 }
857 if let Some(stats) = self.stats.as_mut() {
858 stats.add_elapsed(self.start_time.elapsed());
859 stats.add_searches(1);
860 if self.match_count > 0 {
861 stats.add_searches_with_match(1);
862 }
863 stats.add_bytes_searched(finish.byte_count());
864 stats.add_bytes_printed(self.standard.wtr.borrow().count());
865 }
866 Ok(())
867 }
868}
869
870/// The actual implementation of the standard printer. This couples together
871/// the searcher, the sink implementation and information about the match.
872///
873/// A StandardImpl is initialized every time a match or a contextual line is
874/// reported.
875#[derive(Debug)]
876struct StandardImpl<'a, M: Matcher, W> {
877 searcher: &'a Searcher,
878 sink: &'a StandardSink<'a, 'a, M, W>,
879 sunk: Sunk<'a>,
880 /// Set to true if and only if we are writing a match with color.
881 in_color_match: Cell<bool>,
882}
883
884impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
885 /// Bundle self with a searcher and return the core implementation of Sink.
886 fn new(
887 searcher: &'a Searcher,
888 sink: &'a StandardSink<'_, '_, M, W>,
889 ) -> StandardImpl<'a, M, W> {
890 StandardImpl {
891 searcher,
892 sink,
893 sunk: Sunk::empty(),
894 in_color_match: Cell::new(false),
895 }
896 }
897
898 /// Bundle self with a searcher and return the core implementation of Sink
899 /// for use with handling matching lines.
900 fn from_match(
901 searcher: &'a Searcher,
902 sink: &'a StandardSink<'_, '_, M, W>,
903 mat: &'a SinkMatch<'a>,
904 ) -> StandardImpl<'a, M, W> {
905 let sunk = Sunk::from_sink_match(
906 mat,
907 &sink.standard.matches,
908 sink.replacer.replacement(),
909 );
910 StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
911 }
912
913 /// Bundle self with a searcher and return the core implementation of Sink
914 /// for use with handling contextual lines.
915 fn from_context(
916 searcher: &'a Searcher,
917 sink: &'a StandardSink<'_, '_, M, W>,
918 ctx: &'a SinkContext<'a>,
919 ) -> StandardImpl<'a, M, W> {
920 let sunk = Sunk::from_sink_context(
921 ctx,
922 &sink.standard.matches,
923 sink.replacer.replacement(),
924 );
925 StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
926 }
927
928 fn sink(&self) -> io::Result<()> {
929 self.write_search_prelude()?;
930 if self.sunk.matches().is_empty() {
931 if self.multi_line() && !self.is_context() {
932 self.sink_fast_multi_line()
933 } else {
934 self.sink_fast()
935 }
936 } else {
937 if self.multi_line() && !self.is_context() {
938 self.sink_slow_multi_line()
939 } else {
940 self.sink_slow()
941 }
942 }
943 }
944
945 /// Print matches (limited to one line) quickly by avoiding the detection
946 /// of each individual match in the lines reported in the given
947 /// `SinkMatch`.
948 ///
949 /// This should only be used when the configuration does not demand match
950 /// granularity and the searcher is not in multi line mode.
951 fn sink_fast(&self) -> io::Result<()> {
952 debug_assert!(self.sunk.matches().is_empty());
953 debug_assert!(!self.multi_line() || self.is_context());
954
955 self.write_prelude(
956 self.sunk.absolute_byte_offset(),
957 self.sunk.line_number(),
958 None,
959 )?;
960 self.write_line(self.sunk.bytes())
961 }
962
963 /// Print matches (possibly spanning more than one line) quickly by
964 /// avoiding the detection of each individual match in the lines reported
965 /// in the given `SinkMatch`.
966 ///
967 /// This should only be used when the configuration does not demand match
968 /// granularity. This may be used when the searcher is in multi line mode.
969 fn sink_fast_multi_line(&self) -> io::Result<()> {
970 debug_assert!(self.sunk.matches().is_empty());
971 // This isn't actually a required invariant for using this method,
972 // but if we wind up here and multi line mode is disabled, then we
973 // should still treat it as a bug since we should be using matched_fast
974 // instead.
975 debug_assert!(self.multi_line());
976
977 let line_term = self.searcher.line_terminator().as_byte();
978 let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
979 for (i, line) in self.sunk.lines(line_term).enumerate() {
980 self.write_prelude(
981 absolute_byte_offset,
982 self.sunk.line_number().map(|n| n + i as u64),
983 None,
984 )?;
985 absolute_byte_offset += line.len() as u64;
986
987 self.write_line(line)?;
988 }
989 Ok(())
990 }
991
992 /// Print a matching line where the configuration of the printer requires
993 /// finding each individual match (e.g., for coloring).
994 fn sink_slow(&self) -> io::Result<()> {
995 debug_assert!(!self.sunk.matches().is_empty());
996 debug_assert!(!self.multi_line() || self.is_context());
997
998 if self.config().only_matching {
999 for &m in self.sunk.matches() {
1000 self.write_prelude(
1001 self.sunk.absolute_byte_offset() + m.start() as u64,
1002 self.sunk.line_number(),
1003 Some(m.start() as u64 + 1),
1004 )?;
1005
1006 let buf = &self.sunk.bytes()[m];
1007 self.write_colored_line(&[Match::new(0, buf.len())], buf)?;
1008 }
1009 } else if self.config().per_match {
1010 for &m in self.sunk.matches() {
1011 self.write_prelude(
1012 self.sunk.absolute_byte_offset() + m.start() as u64,
1013 self.sunk.line_number(),
1014 Some(m.start() as u64 + 1),
1015 )?;
1016 self.write_colored_line(&[m], self.sunk.bytes())?;
1017 }
1018 } else {
1019 self.write_prelude(
1020 self.sunk.absolute_byte_offset(),
1021 self.sunk.line_number(),
1022 Some(self.sunk.matches()[0].start() as u64 + 1),
1023 )?;
1024 self.write_colored_line(self.sunk.matches(), self.sunk.bytes())?;
1025 }
1026 Ok(())
1027 }
1028
1029 fn sink_slow_multi_line(&self) -> io::Result<()> {
1030 debug_assert!(!self.sunk.matches().is_empty());
1031 debug_assert!(self.multi_line());
1032
1033 if self.config().only_matching {
1034 return self.sink_slow_multi_line_only_matching();
1035 } else if self.config().per_match {
1036 return self.sink_slow_multi_per_match();
1037 }
1038
1039 let line_term = self.searcher.line_terminator().as_byte();
1040 let bytes = self.sunk.bytes();
1041 let matches = self.sunk.matches();
1042 let mut midx = 0;
1043 let mut count = 0;
1044 let mut stepper = LineStep::new(line_term, 0, bytes.len());
1045 while let Some((start, end)) = stepper.next(bytes) {
1046 let mut line = Match::new(start, end);
1047 self.write_prelude(
1048 self.sunk.absolute_byte_offset() + line.start() as u64,
1049 self.sunk.line_number().map(|n| n + count),
1050 Some(matches[0].start() as u64 + 1),
1051 )?;
1052 count += 1;
1053 self.trim_ascii_prefix(bytes, &mut line);
1054 if self.exceeds_max_columns(&bytes[line]) {
1055 self.write_exceeded_line(bytes, line, matches, &mut midx)?;
1056 } else {
1057 self.write_colored_matches(bytes, line, matches, &mut midx)?;
1058 self.write_line_term()?;
1059 }
1060 }
1061 Ok(())
1062 }
1063
1064 fn sink_slow_multi_line_only_matching(&self) -> io::Result<()> {
1065 let line_term = self.searcher.line_terminator().as_byte();
1066 let spec = self.config().colors.matched();
1067 let bytes = self.sunk.bytes();
1068 let matches = self.sunk.matches();
1069 let mut midx = 0;
1070 let mut count = 0;
1071 let mut stepper = LineStep::new(line_term, 0, bytes.len());
1072 while let Some((start, end)) = stepper.next(bytes) {
1073 let mut line = Match::new(start, end);
1074 self.trim_line_terminator(bytes, &mut line);
1075 self.trim_ascii_prefix(bytes, &mut line);
1076 while !line.is_empty() {
1077 if matches[midx].end() <= line.start() {
1078 if midx + 1 < matches.len() {
1079 midx += 1;
1080 continue;
1081 } else {
1082 break;
1083 }
1084 }
1085 let m = matches[midx];
1086
1087 if line.start() < m.start() {
1088 let upto = cmp::min(line.end(), m.start());
1089 line = line.with_start(upto);
1090 } else {
1091 let upto = cmp::min(line.end(), m.end());
1092 self.write_prelude(
1093 self.sunk.absolute_byte_offset() + m.start() as u64,
1094 self.sunk.line_number().map(|n| n + count),
1095 Some(m.start() as u64 + 1),
1096 )?;
1097
1098 let this_line = line.with_end(upto);
1099 line = line.with_start(upto);
1100 if self.exceeds_max_columns(&bytes[this_line]) {
1101 self.write_exceeded_line(
1102 bytes, this_line, matches, &mut midx,
1103 )?;
1104 } else {
1105 self.write_spec(spec, &bytes[this_line])?;
1106 self.write_line_term()?;
1107 }
1108 }
1109 }
1110 count += 1;
1111 }
1112 Ok(())
1113 }
1114
1115 fn sink_slow_multi_per_match(&self) -> io::Result<()> {
1116 let line_term = self.searcher.line_terminator().as_byte();
1117 let spec = self.config().colors.matched();
1118 let bytes = self.sunk.bytes();
1119 for &m in self.sunk.matches() {
1120 let mut count = 0;
1121 let mut stepper = LineStep::new(line_term, 0, bytes.len());
1122 while let Some((start, end)) = stepper.next(bytes) {
1123 let mut line = Match::new(start, end);
1124 if line.start() >= m.end() {
1125 break;
1126 } else if line.end() <= m.start() {
1127 count += 1;
1128 continue;
1129 }
1130 self.write_prelude(
1131 self.sunk.absolute_byte_offset() + line.start() as u64,
1132 self.sunk.line_number().map(|n| n + count),
1133 Some(m.start().saturating_sub(line.start()) as u64 + 1),
1134 )?;
1135 count += 1;
1136 self.trim_line_terminator(bytes, &mut line);
1137 self.trim_ascii_prefix(bytes, &mut line);
1138 if self.exceeds_max_columns(&bytes[line]) {
1139 self.write_exceeded_line(bytes, line, &[m], &mut 0)?;
1140 continue;
1141 }
1142
1143 while !line.is_empty() {
1144 if m.end() <= line.start() {
1145 self.write(&bytes[line])?;
1146 line = line.with_start(line.end());
1147 } else if line.start() < m.start() {
1148 let upto = cmp::min(line.end(), m.start());
1149 self.write(&bytes[line.with_end(upto)])?;
1150 line = line.with_start(upto);
1151 } else {
1152 let upto = cmp::min(line.end(), m.end());
1153 self.write_spec(spec, &bytes[line.with_end(upto)])?;
1154 line = line.with_start(upto);
1155 }
1156 }
1157 self.write_line_term()?;
1158 // It turns out that vimgrep really only wants one line per
1159 // match, even when a match spans multiple lines. So when
1160 // that option is enabled, we just quit after printing the
1161 // first line.
1162 //
1163 // See: https://github.com/BurntSushi/ripgrep/issues/1866
1164 if self.config().per_match_one_line {
1165 break;
1166 }
1167 }
1168 }
1169 Ok(())
1170 }
1171
1172 /// Write the beginning part of a matching line. This (may) include things
1173 /// like the file path, line number among others, depending on the
1174 /// configuration and the parameters given.
1175 #[inline(always)]
1176 fn write_prelude(
1177 &self,
1178 absolute_byte_offset: u64,
1179 line_number: Option<u64>,
1180 column: Option<u64>,
1181 ) -> io::Result<()> {
1182 let mut prelude = PreludeWriter::new(self);
1183 prelude.start(line_number, column)?;
1184 prelude.write_path()?;
1185 prelude.write_line_number(line_number)?;
1186 prelude.write_column_number(column)?;
1187 prelude.write_byte_offset(absolute_byte_offset)?;
1188 prelude.end()
1189 }
1190
1191 #[inline(always)]
1192 fn write_line(&self, line: &[u8]) -> io::Result<()> {
1193 let line = if !self.config().trim_ascii {
1194 line
1195 } else {
1196 let lineterm = self.searcher.line_terminator();
1197 let full_range = Match::new(0, line.len());
1198 let range = trim_ascii_prefix(lineterm, line, full_range);
1199 &line[range]
1200 };
1201 if self.exceeds_max_columns(line) {
1202 let range = Match::new(0, line.len());
1203 self.write_exceeded_line(
1204 line,
1205 range,
1206 self.sunk.matches(),
1207 &mut 0,
1208 )?;
1209 } else {
1210 // self.write_trim(line)?;
1211 self.write(line)?;
1212 if !self.has_line_terminator(line) {
1213 self.write_line_term()?;
1214 }
1215 }
1216 Ok(())
1217 }
1218
1219 fn write_colored_line(
1220 &self,
1221 matches: &[Match],
1222 bytes: &[u8],
1223 ) -> io::Result<()> {
1224 // If we know we aren't going to emit color, then we can go faster.
1225 let spec = self.config().colors.matched();
1226 if !self.wtr().borrow().supports_color() || spec.is_none() {
1227 return self.write_line(bytes);
1228 }
1229
1230 let mut line = Match::new(0, bytes.len());
1231 self.trim_ascii_prefix(bytes, &mut line);
1232 if self.exceeds_max_columns(bytes) {
1233 self.write_exceeded_line(bytes, line, matches, &mut 0)
1234 } else {
1235 self.write_colored_matches(bytes, line, matches, &mut 0)?;
1236 self.write_line_term()?;
1237 Ok(())
1238 }
1239 }
1240
1241 /// Write the `line` portion of `bytes`, with appropriate coloring for
1242 /// each `match`, starting at `match_index`.
1243 ///
1244 /// This accounts for trimming any whitespace prefix and will *never* print
1245 /// a line terminator. If a match exceeds the range specified by `line`,
1246 /// then only the part of the match within `line` (if any) is printed.
1247 fn write_colored_matches(
1248 &self,
1249 bytes: &[u8],
1250 mut line: Match,
1251 matches: &[Match],
1252 match_index: &mut usize,
1253 ) -> io::Result<()> {
1254 self.trim_line_terminator(bytes, &mut line);
1255 if matches.is_empty() {
1256 self.write(&bytes[line])?;
1257 return Ok(());
1258 }
1259 self.start_line_highlight()?;
1260 while !line.is_empty() {
1261 if matches[*match_index].end() <= line.start() {
1262 if *match_index + 1 < matches.len() {
1263 *match_index += 1;
1264 continue;
1265 } else {
1266 self.end_color_match()?;
1267 self.write(&bytes[line])?;
1268 break;
1269 }
1270 }
1271
1272 let m = matches[*match_index];
1273 if line.start() < m.start() {
1274 let upto = cmp::min(line.end(), m.start());
1275 self.end_color_match()?;
1276 self.write(&bytes[line.with_end(upto)])?;
1277 line = line.with_start(upto);
1278 } else {
1279 let upto = cmp::min(line.end(), m.end());
1280 self.start_color_match()?;
1281 self.write(&bytes[line.with_end(upto)])?;
1282 line = line.with_start(upto);
1283 }
1284 }
1285 self.end_color_match()?;
1286 self.end_line_highlight()?;
1287 Ok(())
1288 }
1289
1290 fn write_exceeded_line(
1291 &self,
1292 bytes: &[u8],
1293 mut line: Match,
1294 matches: &[Match],
1295 match_index: &mut usize,
1296 ) -> io::Result<()> {
1297 if self.config().max_columns_preview {
1298 let original = line;
1299 let end = bytes[line]
1300 .grapheme_indices()
1301 .map(|(_, end, _)| end)
1302 .take(self.config().max_columns.unwrap_or(0) as usize)
1303 .last()
1304 .unwrap_or(0)
1305 + line.start();
1306 line = line.with_end(end);
1307 self.write_colored_matches(bytes, line, matches, match_index)?;
1308
1309 if matches.is_empty() {
1310 self.write(b" [... omitted end of long line]")?;
1311 } else {
1312 let remaining = matches
1313 .iter()
1314 .filter(|m| {
1315 m.start() >= line.end() && m.start() < original.end()
1316 })
1317 .count();
1318 let tense = if remaining == 1 { "match" } else { "matches" };
1319 write!(
1320 self.wtr().borrow_mut(),
1321 " [... {} more {}]",
1322 remaining,
1323 tense,
1324 )?;
1325 }
1326 self.write_line_term()?;
1327 return Ok(());
1328 }
1329 if self.sunk.original_matches().is_empty() {
1330 if self.is_context() {
1331 self.write(b"[Omitted long context line]")?;
1332 } else {
1333 self.write(b"[Omitted long matching line]")?;
1334 }
1335 } else {
1336 if self.config().only_matching {
1337 if self.is_context() {
1338 self.write(b"[Omitted long context line]")?;
1339 } else {
1340 self.write(b"[Omitted long matching line]")?;
1341 }
1342 } else {
1343 write!(
1344 self.wtr().borrow_mut(),
1345 "[Omitted long line with {} matches]",
1346 self.sunk.original_matches().len(),
1347 )?;
1348 }
1349 }
1350 self.write_line_term()?;
1351 Ok(())
1352 }
1353
1354 /// If this printer has a file path associated with it, then this will
1355 /// write that path to the underlying writer followed by a line terminator.
1356 /// (If a path terminator is set, then that is used instead of the line
1357 /// terminator.)
1358 fn write_path_line(&self) -> io::Result<()> {
1359 if let Some(path) = self.path() {
1360 self.write_path_hyperlink(path)?;
1361 if let Some(term) = self.config().path_terminator {
1362 self.write(&[term])?;
1363 } else {
1364 self.write_line_term()?;
1365 }
1366 }
1367 Ok(())
1368 }
1369
1370 fn write_search_prelude(&self) -> io::Result<()> {
1371 let this_search_written = self.wtr().borrow().count() > 0;
1372 if this_search_written {
1373 return Ok(());
1374 }
1375 if let Some(ref sep) = *self.config().separator_search {
1376 let ever_written = self.wtr().borrow().total_count() > 0;
1377 if ever_written {
1378 self.write(sep)?;
1379 self.write_line_term()?;
1380 }
1381 }
1382 if self.config().heading {
1383 self.write_path_line()?;
1384 }
1385 Ok(())
1386 }
1387
1388 fn write_binary_message(&self, offset: u64) -> io::Result<()> {
1389 if !self.sink.has_match() {
1390 return Ok(());
1391 }
1392
1393 let bin = self.searcher.binary_detection();
1394 if let Some(byte) = bin.quit_byte() {
1395 if let Some(path) = self.path() {
1396 self.write_path_hyperlink(path)?;
1397 self.write(b": ")?;
1398 }
1399 let remainder = format!(
1400 "WARNING: stopped searching binary file after match \
1401 (found {:?} byte around offset {})\n",
1402 [byte].as_bstr(),
1403 offset,
1404 );
1405 self.write(remainder.as_bytes())?;
1406 } else if let Some(byte) = bin.convert_byte() {
1407 if let Some(path) = self.path() {
1408 self.write_path_hyperlink(path)?;
1409 self.write(b": ")?;
1410 }
1411 let remainder = format!(
1412 "binary file matches (found {:?} byte around offset {})\n",
1413 [byte].as_bstr(),
1414 offset,
1415 );
1416 self.write(remainder.as_bytes())?;
1417 }
1418 Ok(())
1419 }
1420
1421 fn write_context_separator(&self) -> io::Result<()> {
1422 if let Some(ref sep) = *self.config().separator_context {
1423 self.write(sep)?;
1424 self.write_line_term()?;
1425 }
1426 Ok(())
1427 }
1428
1429 fn write_line_term(&self) -> io::Result<()> {
1430 self.write(self.searcher.line_terminator().as_bytes())
1431 }
1432
1433 fn write_spec(&self, spec: &ColorSpec, buf: &[u8]) -> io::Result<()> {
1434 let mut wtr = self.wtr().borrow_mut();
1435 wtr.set_color(spec)?;
1436 wtr.write_all(buf)?;
1437 wtr.reset()?;
1438 Ok(())
1439 }
1440
1441 fn write_path(&self, path: &PrinterPath) -> io::Result<()> {
1442 let mut wtr = self.wtr().borrow_mut();
1443 wtr.set_color(self.config().colors.path())?;
1444 wtr.write_all(path.as_bytes())?;
1445 wtr.reset()
1446 }
1447
1448 fn write_path_hyperlink(&self, path: &PrinterPath) -> io::Result<()> {
1449 let status = self.start_hyperlink(path, None, None)?;
1450 self.write_path(path)?;
1451 self.end_hyperlink(status)
1452 }
1453
1454 fn start_hyperlink(
1455 &self,
1456 path: &PrinterPath,
1457 line_number: Option<u64>,
1458 column: Option<u64>,
1459 ) -> io::Result<hyperlink::InterpolatorStatus> {
1460 let Some(hyperpath) = path.as_hyperlink() else {
1461 return Ok(hyperlink::InterpolatorStatus::inactive());
1462 };
1463 let values =
1464 hyperlink::Values::new(hyperpath).line(line_number).column(column);
1465 self.sink.interpolator.begin(&values, &mut *self.wtr().borrow_mut())
1466 }
1467
1468 fn end_hyperlink(
1469 &self,
1470 status: hyperlink::InterpolatorStatus,
1471 ) -> io::Result<()> {
1472 self.sink.interpolator.finish(status, &mut *self.wtr().borrow_mut())
1473 }
1474
1475 fn start_color_match(&self) -> io::Result<()> {
1476 if self.in_color_match.get() {
1477 return Ok(());
1478 }
1479 self.wtr().borrow_mut().set_color(self.config().colors.matched())?;
1480 self.in_color_match.set(true);
1481 Ok(())
1482 }
1483
1484 fn end_color_match(&self) -> io::Result<()> {
1485 if !self.in_color_match.get() {
1486 return Ok(());
1487 }
1488 if self.highlight_on() {
1489 self.wtr()
1490 .borrow_mut()
1491 .set_color(self.config().colors.highlight())?;
1492 } else {
1493 self.wtr().borrow_mut().reset()?;
1494 }
1495 self.in_color_match.set(false);
1496 Ok(())
1497 }
1498
1499 fn highlight_on(&self) -> bool {
1500 !self.config().colors.highlight().is_none() && !self.is_context()
1501 }
1502
1503 fn start_line_highlight(&self) -> io::Result<()> {
1504 if self.highlight_on() {
1505 self.wtr()
1506 .borrow_mut()
1507 .set_color(self.config().colors.highlight())?;
1508 }
1509 Ok(())
1510 }
1511
1512 fn end_line_highlight(&self) -> io::Result<()> {
1513 if self.highlight_on() {
1514 self.wtr().borrow_mut().reset()?;
1515 }
1516 Ok(())
1517 }
1518
1519 fn write(&self, buf: &[u8]) -> io::Result<()> {
1520 self.wtr().borrow_mut().write_all(buf)
1521 }
1522
1523 fn trim_line_terminator(&self, buf: &[u8], line: &mut Match) {
1524 trim_line_terminator(&self.searcher, buf, line);
1525 }
1526
1527 fn has_line_terminator(&self, buf: &[u8]) -> bool {
1528 self.searcher.line_terminator().is_suffix(buf)
1529 }
1530
1531 fn is_context(&self) -> bool {
1532 self.sunk.context_kind().is_some()
1533 }
1534
1535 /// Return the underlying configuration for this printer.
1536 fn config(&self) -> &'a Config {
1537 &self.sink.standard.config
1538 }
1539
1540 /// Return the underlying writer that we are printing to.
1541 fn wtr(&self) -> &'a RefCell<CounterWriter<W>> {
1542 &self.sink.standard.wtr
1543 }
1544
1545 /// Return the path associated with this printer, if one exists.
1546 fn path(&self) -> Option<&'a PrinterPath<'a>> {
1547 self.sink.path.as_ref()
1548 }
1549
1550 /// Return the appropriate field separator based on whether we are emitting
1551 /// matching or contextual lines.
1552 fn separator_field(&self) -> &[u8] {
1553 if self.is_context() {
1554 &self.config().separator_field_context
1555 } else {
1556 &self.config().separator_field_match
1557 }
1558 }
1559
1560 /// Returns true if and only if the given line exceeds the maximum number
1561 /// of columns set. If no maximum is set, then this always returns false.
1562 fn exceeds_max_columns(&self, line: &[u8]) -> bool {
1563 self.config().max_columns.map_or(false, |m| line.len() as u64 > m)
1564 }
1565
1566 /// Returns true if and only if the searcher may report matches over
1567 /// multiple lines.
1568 ///
1569 /// Note that this doesn't just return whether the searcher is in multi
1570 /// line mode, but also checks if the matter can match over multiple lines.
1571 /// If it can't, then we don't need multi line handling, even if the
1572 /// searcher has multi line mode enabled.
1573 fn multi_line(&self) -> bool {
1574 self.searcher.multi_line_with_matcher(&self.sink.matcher)
1575 }
1576
1577 /// Trim prefix ASCII spaces from the given slice and return the
1578 /// corresponding range.
1579 ///
1580 /// This stops trimming a prefix as soon as it sees non-whitespace or a
1581 /// line terminator.
1582 fn trim_ascii_prefix(&self, slice: &[u8], range: &mut Match) {
1583 if !self.config().trim_ascii {
1584 return;
1585 }
1586 let lineterm = self.searcher.line_terminator();
1587 *range = trim_ascii_prefix(lineterm, slice, *range)
1588 }
1589}
1590
1591/// A writer for the prelude (the beginning part of a matching line).
1592///
1593/// This encapsulates the state needed to print the prelude.
1594struct PreludeWriter<'a, M: Matcher, W> {
1595 std: &'a StandardImpl<'a, M, W>,
1596 next_separator: PreludeSeparator,
1597 field_separator: &'a [u8],
1598 interp_status: hyperlink::InterpolatorStatus,
1599}
1600
1601/// A type of separator used in the prelude
1602enum PreludeSeparator {
1603 /// No separator.
1604 None,
1605 /// The field separator, either for a matching or contextual line.
1606 FieldSeparator,
1607 /// The path terminator.
1608 PathTerminator,
1609}
1610
1611impl<'a, M: Matcher, W: WriteColor> PreludeWriter<'a, M, W> {
1612 /// Creates a new prelude printer.
1613 #[inline(always)]
1614 fn new(std: &'a StandardImpl<'a, M, W>) -> PreludeWriter<'a, M, W> {
1615 PreludeWriter {
1616 std,
1617 next_separator: PreludeSeparator::None,
1618 field_separator: std.separator_field(),
1619 interp_status: hyperlink::InterpolatorStatus::inactive(),
1620 }
1621 }
1622
1623 /// Starts the prelude with a hyperlink when applicable.
1624 ///
1625 /// If a heading was written, and the hyperlink format is invariant on
1626 /// the line number, then this doesn't hyperlink each line prelude, as it
1627 /// wouldn't point to the line anyway. The hyperlink on the heading should
1628 /// be sufficient and less confusing.
1629 #[inline(always)]
1630 fn start(
1631 &mut self,
1632 line_number: Option<u64>,
1633 column: Option<u64>,
1634 ) -> io::Result<()> {
1635 let Some(path) = self.std.path() else { return Ok(()) };
1636 if self.config().hyperlink.format().is_line_dependent()
1637 || !self.config().heading
1638 {
1639 self.interp_status =
1640 self.std.start_hyperlink(path, line_number, column)?;
1641 }
1642 Ok(())
1643 }
1644
1645 /// Ends the prelude and writes the remaining output.
1646 #[inline(always)]
1647 fn end(&mut self) -> io::Result<()> {
1648 self.std.end_hyperlink(std::mem::replace(
1649 &mut self.interp_status,
1650 hyperlink::InterpolatorStatus::inactive(),
1651 ))?;
1652 self.write_separator()
1653 }
1654
1655 /// If this printer has a file path associated with it, then this will
1656 /// write that path to the underlying writer followed by the given field
1657 /// separator. (If a path terminator is set, then that is used instead of
1658 /// the field separator.)
1659 #[inline(always)]
1660 fn write_path(&mut self) -> io::Result<()> {
1661 // The prelude doesn't handle headings, only what comes before a match
1662 // on the same line. So if we are emitting paths in headings, we should
1663 // not do it here on each line.
1664 if self.config().heading {
1665 return Ok(());
1666 }
1667 let Some(path) = self.std.path() else { return Ok(()) };
1668 self.write_separator()?;
1669 self.std.write_path(path)?;
1670
1671 self.next_separator = if self.config().path_terminator.is_some() {
1672 PreludeSeparator::PathTerminator
1673 } else {
1674 PreludeSeparator::FieldSeparator
1675 };
1676 Ok(())
1677 }
1678
1679 /// Writes the line number field if present.
1680 #[inline(always)]
1681 fn write_line_number(&mut self, line: Option<u64>) -> io::Result<()> {
1682 let Some(line_number) = line else { return Ok(()) };
1683 self.write_separator()?;
1684 let n = DecimalFormatter::new(line_number);
1685 self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
1686 self.next_separator = PreludeSeparator::FieldSeparator;
1687 Ok(())
1688 }
1689
1690 /// Writes the column number field if present and configured to do so.
1691 #[inline(always)]
1692 fn write_column_number(&mut self, column: Option<u64>) -> io::Result<()> {
1693 if !self.config().column {
1694 return Ok(());
1695 }
1696 let Some(column_number) = column else { return Ok(()) };
1697 self.write_separator()?;
1698 let n = DecimalFormatter::new(column_number);
1699 self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
1700 self.next_separator = PreludeSeparator::FieldSeparator;
1701 Ok(())
1702 }
1703
1704 /// Writes the byte offset field if configured to do so.
1705 #[inline(always)]
1706 fn write_byte_offset(&mut self, offset: u64) -> io::Result<()> {
1707 if !self.config().byte_offset {
1708 return Ok(());
1709 }
1710 self.write_separator()?;
1711 let n = DecimalFormatter::new(offset);
1712 self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
1713 self.next_separator = PreludeSeparator::FieldSeparator;
1714 Ok(())
1715 }
1716
1717 /// Writes the separator defined by the preceding field.
1718 ///
1719 /// This is called before writing the contents of a field, and at
1720 /// the end of the prelude.
1721 #[inline(always)]
1722 fn write_separator(&mut self) -> io::Result<()> {
1723 match self.next_separator {
1724 PreludeSeparator::None => {}
1725 PreludeSeparator::FieldSeparator => {
1726 self.std.write(self.field_separator)?;
1727 }
1728 PreludeSeparator::PathTerminator => {
1729 if let Some(term) = self.config().path_terminator {
1730 self.std.write(&[term])?;
1731 }
1732 }
1733 }
1734 self.next_separator = PreludeSeparator::None;
1735 Ok(())
1736 }
1737
1738 #[inline(always)]
1739 fn config(&self) -> &Config {
1740 self.std.config()
1741 }
1742}
1743
1744#[cfg(test)]
1745mod tests {
1746 use grep_matcher::LineTerminator;
1747 use grep_regex::{RegexMatcher, RegexMatcherBuilder};
1748 use grep_searcher::SearcherBuilder;
1749 use termcolor::{Ansi, NoColor};
1750
1751 use super::{ColorSpecs, Standard, StandardBuilder};
1752
1753 const SHERLOCK: &'static str = "\
1754For the Doctor Watsons of this world, as opposed to the Sherlock
1755Holmeses, success in the province of detective work must always
1756be, to a very large extent, the result of luck. Sherlock Holmes
1757can extract a clew from a wisp of straw or a flake of cigar ash;
1758but Doctor Watson has to have it taken out for him and dusted,
1759and exhibited clearly, with a label attached.\
1760";
1761
1762 #[allow(dead_code)]
1763 const SHERLOCK_CRLF: &'static str = "\
1764For the Doctor Watsons of this world, as opposed to the Sherlock\r
1765Holmeses, success in the province of detective work must always\r
1766be, to a very large extent, the result of luck. Sherlock Holmes\r
1767can extract a clew from a wisp of straw or a flake of cigar ash;\r
1768but Doctor Watson has to have it taken out for him and dusted,\r
1769and exhibited clearly, with a label attached.\
1770";
1771
1772 fn printer_contents(printer: &mut Standard<NoColor<Vec<u8>>>) -> String {
1773 String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
1774 }
1775
1776 fn printer_contents_ansi(printer: &mut Standard<Ansi<Vec<u8>>>) -> String {
1777 String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
1778 }
1779
1780 #[test]
1781 fn reports_match() {
1782 let matcher = RegexMatcher::new("Sherlock").unwrap();
1783 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1784 let mut sink = printer.sink(&matcher);
1785 SearcherBuilder::new()
1786 .line_number(false)
1787 .build()
1788 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1789 .unwrap();
1790 assert!(sink.has_match());
1791
1792 let matcher = RegexMatcher::new("zzzzz").unwrap();
1793 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1794 let mut sink = printer.sink(&matcher);
1795 SearcherBuilder::new()
1796 .line_number(false)
1797 .build()
1798 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1799 .unwrap();
1800 assert!(!sink.has_match());
1801 }
1802
1803 #[test]
1804 fn reports_binary() {
1805 use grep_searcher::BinaryDetection;
1806
1807 let matcher = RegexMatcher::new("Sherlock").unwrap();
1808 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1809 let mut sink = printer.sink(&matcher);
1810 SearcherBuilder::new()
1811 .line_number(false)
1812 .build()
1813 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1814 .unwrap();
1815 assert!(sink.binary_byte_offset().is_none());
1816
1817 let matcher = RegexMatcher::new(".+").unwrap();
1818 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1819 let mut sink = printer.sink(&matcher);
1820 SearcherBuilder::new()
1821 .line_number(false)
1822 .binary_detection(BinaryDetection::quit(b'\x00'))
1823 .build()
1824 .search_reader(&matcher, &b"abc\x00"[..], &mut sink)
1825 .unwrap();
1826 assert_eq!(sink.binary_byte_offset(), Some(3));
1827 }
1828
1829 #[test]
1830 fn reports_stats() {
1831 use std::time::Duration;
1832
1833 let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
1834 let mut printer =
1835 StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
1836 let stats = {
1837 let mut sink = printer.sink(&matcher);
1838 SearcherBuilder::new()
1839 .line_number(false)
1840 .build()
1841 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1842 .unwrap();
1843 sink.stats().unwrap().clone()
1844 };
1845 let buf = printer_contents(&mut printer);
1846
1847 assert!(stats.elapsed() > Duration::default());
1848 assert_eq!(stats.searches(), 1);
1849 assert_eq!(stats.searches_with_match(), 1);
1850 assert_eq!(stats.bytes_searched(), SHERLOCK.len() as u64);
1851 assert_eq!(stats.bytes_printed(), buf.len() as u64);
1852 assert_eq!(stats.matched_lines(), 2);
1853 assert_eq!(stats.matches(), 3);
1854 }
1855
1856 #[test]
1857 fn reports_stats_multiple() {
1858 use std::time::Duration;
1859
1860 let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
1861 let mut printer =
1862 StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
1863 let stats = {
1864 let mut sink = printer.sink(&matcher);
1865 SearcherBuilder::new()
1866 .line_number(false)
1867 .build()
1868 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1869 .unwrap();
1870 SearcherBuilder::new()
1871 .line_number(false)
1872 .build()
1873 .search_reader(&matcher, &b"zzzzzzzzzz"[..], &mut sink)
1874 .unwrap();
1875 SearcherBuilder::new()
1876 .line_number(false)
1877 .build()
1878 .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1879 .unwrap();
1880 sink.stats().unwrap().clone()
1881 };
1882 let buf = printer_contents(&mut printer);
1883
1884 assert!(stats.elapsed() > Duration::default());
1885 assert_eq!(stats.searches(), 3);
1886 assert_eq!(stats.searches_with_match(), 2);
1887 assert_eq!(stats.bytes_searched(), 10 + 2 * SHERLOCK.len() as u64);
1888 assert_eq!(stats.bytes_printed(), buf.len() as u64);
1889 assert_eq!(stats.matched_lines(), 4);
1890 assert_eq!(stats.matches(), 6);
1891 }
1892
1893 #[test]
1894 fn context_break() {
1895 let matcher = RegexMatcher::new("Watson").unwrap();
1896 let mut printer = StandardBuilder::new()
1897 .separator_context(Some(b"--abc--".to_vec()))
1898 .build(NoColor::new(vec![]));
1899 SearcherBuilder::new()
1900 .line_number(false)
1901 .before_context(1)
1902 .after_context(1)
1903 .build()
1904 .search_reader(
1905 &matcher,
1906 SHERLOCK.as_bytes(),
1907 printer.sink(&matcher),
1908 )
1909 .unwrap();
1910
1911 let got = printer_contents(&mut printer);
1912 let expected = "\
1913For the Doctor Watsons of this world, as opposed to the Sherlock
1914Holmeses, success in the province of detective work must always
1915--abc--
1916can extract a clew from a wisp of straw or a flake of cigar ash;
1917but Doctor Watson has to have it taken out for him and dusted,
1918and exhibited clearly, with a label attached.
1919";
1920 assert_eq_printed!(expected, got);
1921 }
1922
1923 #[test]
1924 fn context_break_multiple_no_heading() {
1925 let matcher = RegexMatcher::new("Watson").unwrap();
1926 let mut printer = StandardBuilder::new()
1927 .separator_search(Some(b"--xyz--".to_vec()))
1928 .separator_context(Some(b"--abc--".to_vec()))
1929 .build(NoColor::new(vec![]));
1930
1931 SearcherBuilder::new()
1932 .line_number(false)
1933 .before_context(1)
1934 .after_context(1)
1935 .build()
1936 .search_reader(
1937 &matcher,
1938 SHERLOCK.as_bytes(),
1939 printer.sink(&matcher),
1940 )
1941 .unwrap();
1942 SearcherBuilder::new()
1943 .line_number(false)
1944 .before_context(1)
1945 .after_context(1)
1946 .build()
1947 .search_reader(
1948 &matcher,
1949 SHERLOCK.as_bytes(),
1950 printer.sink(&matcher),
1951 )
1952 .unwrap();
1953
1954 let got = printer_contents(&mut printer);
1955 let expected = "\
1956For the Doctor Watsons of this world, as opposed to the Sherlock
1957Holmeses, success in the province of detective work must always
1958--abc--
1959can extract a clew from a wisp of straw or a flake of cigar ash;
1960but Doctor Watson has to have it taken out for him and dusted,
1961and exhibited clearly, with a label attached.
1962--xyz--
1963For the Doctor Watsons of this world, as opposed to the Sherlock
1964Holmeses, success in the province of detective work must always
1965--abc--
1966can extract a clew from a wisp of straw or a flake of cigar ash;
1967but Doctor Watson has to have it taken out for him and dusted,
1968and exhibited clearly, with a label attached.
1969";
1970 assert_eq_printed!(expected, got);
1971 }
1972
1973 #[test]
1974 fn context_break_multiple_heading() {
1975 let matcher = RegexMatcher::new("Watson").unwrap();
1976 let mut printer = StandardBuilder::new()
1977 .heading(true)
1978 .separator_search(Some(b"--xyz--".to_vec()))
1979 .separator_context(Some(b"--abc--".to_vec()))
1980 .build(NoColor::new(vec![]));
1981
1982 SearcherBuilder::new()
1983 .line_number(false)
1984 .before_context(1)
1985 .after_context(1)
1986 .build()
1987 .search_reader(
1988 &matcher,
1989 SHERLOCK.as_bytes(),
1990 printer.sink(&matcher),
1991 )
1992 .unwrap();
1993 SearcherBuilder::new()
1994 .line_number(false)
1995 .before_context(1)
1996 .after_context(1)
1997 .build()
1998 .search_reader(
1999 &matcher,
2000 SHERLOCK.as_bytes(),
2001 printer.sink(&matcher),
2002 )
2003 .unwrap();
2004
2005 let got = printer_contents(&mut printer);
2006 let expected = "\
2007For the Doctor Watsons of this world, as opposed to the Sherlock
2008Holmeses, success in the province of detective work must always
2009--abc--
2010can extract a clew from a wisp of straw or a flake of cigar ash;
2011but Doctor Watson has to have it taken out for him and dusted,
2012and exhibited clearly, with a label attached.
2013--xyz--
2014For the Doctor Watsons of this world, as opposed to the Sherlock
2015Holmeses, success in the province of detective work must always
2016--abc--
2017can extract a clew from a wisp of straw or a flake of cigar ash;
2018but Doctor Watson has to have it taken out for him and dusted,
2019and exhibited clearly, with a label attached.
2020";
2021 assert_eq_printed!(expected, got);
2022 }
2023
2024 #[test]
2025 fn path() {
2026 let matcher = RegexMatcher::new("Watson").unwrap();
2027 let mut printer =
2028 StandardBuilder::new().path(false).build(NoColor::new(vec![]));
2029 SearcherBuilder::new()
2030 .line_number(true)
2031 .build()
2032 .search_reader(
2033 &matcher,
2034 SHERLOCK.as_bytes(),
2035 printer.sink_with_path(&matcher, "sherlock"),
2036 )
2037 .unwrap();
2038
2039 let got = printer_contents(&mut printer);
2040 let expected = "\
20411:For the Doctor Watsons of this world, as opposed to the Sherlock
20425:but Doctor Watson has to have it taken out for him and dusted,
2043";
2044 assert_eq_printed!(expected, got);
2045 }
2046
2047 #[test]
2048 fn separator_field() {
2049 let matcher = RegexMatcher::new("Watson").unwrap();
2050 let mut printer = StandardBuilder::new()
2051 .separator_field_match(b"!!".to_vec())
2052 .separator_field_context(b"^^".to_vec())
2053 .build(NoColor::new(vec![]));
2054 SearcherBuilder::new()
2055 .line_number(false)
2056 .before_context(1)
2057 .after_context(1)
2058 .build()
2059 .search_reader(
2060 &matcher,
2061 SHERLOCK.as_bytes(),
2062 printer.sink_with_path(&matcher, "sherlock"),
2063 )
2064 .unwrap();
2065
2066 let got = printer_contents(&mut printer);
2067 let expected = "\
2068sherlock!!For the Doctor Watsons of this world, as opposed to the Sherlock
2069sherlock^^Holmeses, success in the province of detective work must always
2070--
2071sherlock^^can extract a clew from a wisp of straw or a flake of cigar ash;
2072sherlock!!but Doctor Watson has to have it taken out for him and dusted,
2073sherlock^^and exhibited clearly, with a label attached.
2074";
2075 assert_eq_printed!(expected, got);
2076 }
2077
2078 #[test]
2079 fn separator_path() {
2080 let matcher = RegexMatcher::new("Watson").unwrap();
2081 let mut printer = StandardBuilder::new()
2082 .separator_path(Some(b'Z'))
2083 .build(NoColor::new(vec![]));
2084 SearcherBuilder::new()
2085 .line_number(false)
2086 .build()
2087 .search_reader(
2088 &matcher,
2089 SHERLOCK.as_bytes(),
2090 printer.sink_with_path(&matcher, "books/sherlock"),
2091 )
2092 .unwrap();
2093
2094 let got = printer_contents(&mut printer);
2095 let expected = "\
2096booksZsherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2097booksZsherlock:but Doctor Watson has to have it taken out for him and dusted,
2098";
2099 assert_eq_printed!(expected, got);
2100 }
2101
2102 #[test]
2103 fn path_terminator() {
2104 let matcher = RegexMatcher::new("Watson").unwrap();
2105 let mut printer = StandardBuilder::new()
2106 .path_terminator(Some(b'Z'))
2107 .build(NoColor::new(vec![]));
2108 SearcherBuilder::new()
2109 .line_number(false)
2110 .build()
2111 .search_reader(
2112 &matcher,
2113 SHERLOCK.as_bytes(),
2114 printer.sink_with_path(&matcher, "books/sherlock"),
2115 )
2116 .unwrap();
2117
2118 let got = printer_contents(&mut printer);
2119 let expected = "\
2120books/sherlockZFor the Doctor Watsons of this world, as opposed to the Sherlock
2121books/sherlockZbut Doctor Watson has to have it taken out for him and dusted,
2122";
2123 assert_eq_printed!(expected, got);
2124 }
2125
2126 #[test]
2127 fn heading() {
2128 let matcher = RegexMatcher::new("Watson").unwrap();
2129 let mut printer =
2130 StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
2131 SearcherBuilder::new()
2132 .line_number(false)
2133 .build()
2134 .search_reader(
2135 &matcher,
2136 SHERLOCK.as_bytes(),
2137 printer.sink_with_path(&matcher, "sherlock"),
2138 )
2139 .unwrap();
2140
2141 let got = printer_contents(&mut printer);
2142 let expected = "\
2143sherlock
2144For the Doctor Watsons of this world, as opposed to the Sherlock
2145but Doctor Watson has to have it taken out for him and dusted,
2146";
2147 assert_eq_printed!(expected, got);
2148 }
2149
2150 #[test]
2151 fn no_heading() {
2152 let matcher = RegexMatcher::new("Watson").unwrap();
2153 let mut printer =
2154 StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
2155 SearcherBuilder::new()
2156 .line_number(false)
2157 .build()
2158 .search_reader(
2159 &matcher,
2160 SHERLOCK.as_bytes(),
2161 printer.sink_with_path(&matcher, "sherlock"),
2162 )
2163 .unwrap();
2164
2165 let got = printer_contents(&mut printer);
2166 let expected = "\
2167sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2168sherlock:but Doctor Watson has to have it taken out for him and dusted,
2169";
2170 assert_eq_printed!(expected, got);
2171 }
2172
2173 #[test]
2174 fn no_heading_multiple() {
2175 let matcher = RegexMatcher::new("Watson").unwrap();
2176 let mut printer =
2177 StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
2178 SearcherBuilder::new()
2179 .line_number(false)
2180 .build()
2181 .search_reader(
2182 &matcher,
2183 SHERLOCK.as_bytes(),
2184 printer.sink_with_path(&matcher, "sherlock"),
2185 )
2186 .unwrap();
2187
2188 let matcher = RegexMatcher::new("Sherlock").unwrap();
2189 SearcherBuilder::new()
2190 .line_number(false)
2191 .build()
2192 .search_reader(
2193 &matcher,
2194 SHERLOCK.as_bytes(),
2195 printer.sink_with_path(&matcher, "sherlock"),
2196 )
2197 .unwrap();
2198
2199 let got = printer_contents(&mut printer);
2200 let expected = "\
2201sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2202sherlock:but Doctor Watson has to have it taken out for him and dusted,
2203sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2204sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
2205";
2206 assert_eq_printed!(expected, got);
2207 }
2208
2209 #[test]
2210 fn heading_multiple() {
2211 let matcher = RegexMatcher::new("Watson").unwrap();
2212 let mut printer =
2213 StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
2214 SearcherBuilder::new()
2215 .line_number(false)
2216 .build()
2217 .search_reader(
2218 &matcher,
2219 SHERLOCK.as_bytes(),
2220 printer.sink_with_path(&matcher, "sherlock"),
2221 )
2222 .unwrap();
2223
2224 let matcher = RegexMatcher::new("Sherlock").unwrap();
2225 SearcherBuilder::new()
2226 .line_number(false)
2227 .build()
2228 .search_reader(
2229 &matcher,
2230 SHERLOCK.as_bytes(),
2231 printer.sink_with_path(&matcher, "sherlock"),
2232 )
2233 .unwrap();
2234
2235 let got = printer_contents(&mut printer);
2236 let expected = "\
2237sherlock
2238For the Doctor Watsons of this world, as opposed to the Sherlock
2239but Doctor Watson has to have it taken out for him and dusted,
2240sherlock
2241For the Doctor Watsons of this world, as opposed to the Sherlock
2242be, to a very large extent, the result of luck. Sherlock Holmes
2243";
2244 assert_eq_printed!(expected, got);
2245 }
2246
2247 #[test]
2248 fn trim_ascii() {
2249 let matcher = RegexMatcher::new("Watson").unwrap();
2250 let mut printer = StandardBuilder::new()
2251 .trim_ascii(true)
2252 .build(NoColor::new(vec![]));
2253 SearcherBuilder::new()
2254 .line_number(false)
2255 .build()
2256 .search_reader(
2257 &matcher,
2258 " Watson".as_bytes(),
2259 printer.sink(&matcher),
2260 )
2261 .unwrap();
2262
2263 let got = printer_contents(&mut printer);
2264 let expected = "\
2265Watson
2266";
2267 assert_eq_printed!(expected, got);
2268 }
2269
2270 #[test]
2271 fn trim_ascii_multi_line() {
2272 let matcher = RegexMatcher::new("(?s:.{0})Watson").unwrap();
2273 let mut printer = StandardBuilder::new()
2274 .trim_ascii(true)
2275 .stats(true)
2276 .build(NoColor::new(vec![]));
2277 SearcherBuilder::new()
2278 .line_number(false)
2279 .multi_line(true)
2280 .build()
2281 .search_reader(
2282 &matcher,
2283 " Watson".as_bytes(),
2284 printer.sink(&matcher),
2285 )
2286 .unwrap();
2287
2288 let got = printer_contents(&mut printer);
2289 let expected = "\
2290Watson
2291";
2292 assert_eq_printed!(expected, got);
2293 }
2294
2295 #[test]
2296 fn trim_ascii_with_line_term() {
2297 let matcher = RegexMatcher::new("Watson").unwrap();
2298 let mut printer = StandardBuilder::new()
2299 .trim_ascii(true)
2300 .build(NoColor::new(vec![]));
2301 SearcherBuilder::new()
2302 .line_number(true)
2303 .before_context(1)
2304 .build()
2305 .search_reader(
2306 &matcher,
2307 "\n Watson".as_bytes(),
2308 printer.sink(&matcher),
2309 )
2310 .unwrap();
2311
2312 let got = printer_contents(&mut printer);
2313 let expected = "\
23141-
23152:Watson
2316";
2317 assert_eq_printed!(expected, got);
2318 }
2319
2320 #[test]
2321 fn line_number() {
2322 let matcher = RegexMatcher::new("Watson").unwrap();
2323 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2324 SearcherBuilder::new()
2325 .line_number(true)
2326 .build()
2327 .search_reader(
2328 &matcher,
2329 SHERLOCK.as_bytes(),
2330 printer.sink(&matcher),
2331 )
2332 .unwrap();
2333
2334 let got = printer_contents(&mut printer);
2335 let expected = "\
23361:For the Doctor Watsons of this world, as opposed to the Sherlock
23375:but Doctor Watson has to have it taken out for him and dusted,
2338";
2339 assert_eq_printed!(expected, got);
2340 }
2341
2342 #[test]
2343 fn line_number_multi_line() {
2344 let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2345 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2346 SearcherBuilder::new()
2347 .line_number(true)
2348 .multi_line(true)
2349 .build()
2350 .search_reader(
2351 &matcher,
2352 SHERLOCK.as_bytes(),
2353 printer.sink(&matcher),
2354 )
2355 .unwrap();
2356
2357 let got = printer_contents(&mut printer);
2358 let expected = "\
23591:For the Doctor Watsons of this world, as opposed to the Sherlock
23602:Holmeses, success in the province of detective work must always
23613:be, to a very large extent, the result of luck. Sherlock Holmes
23624:can extract a clew from a wisp of straw or a flake of cigar ash;
23635:but Doctor Watson has to have it taken out for him and dusted,
2364";
2365 assert_eq_printed!(expected, got);
2366 }
2367
2368 #[test]
2369 fn column_number() {
2370 let matcher = RegexMatcher::new("Watson").unwrap();
2371 let mut printer =
2372 StandardBuilder::new().column(true).build(NoColor::new(vec![]));
2373 SearcherBuilder::new()
2374 .line_number(false)
2375 .build()
2376 .search_reader(
2377 &matcher,
2378 SHERLOCK.as_bytes(),
2379 printer.sink(&matcher),
2380 )
2381 .unwrap();
2382
2383 let got = printer_contents(&mut printer);
2384 let expected = "\
238516:For the Doctor Watsons of this world, as opposed to the Sherlock
238612:but Doctor Watson has to have it taken out for him and dusted,
2387";
2388 assert_eq_printed!(expected, got);
2389 }
2390
2391 #[test]
2392 fn column_number_multi_line() {
2393 let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2394 let mut printer =
2395 StandardBuilder::new().column(true).build(NoColor::new(vec![]));
2396 SearcherBuilder::new()
2397 .line_number(false)
2398 .multi_line(true)
2399 .build()
2400 .search_reader(
2401 &matcher,
2402 SHERLOCK.as_bytes(),
2403 printer.sink(&matcher),
2404 )
2405 .unwrap();
2406
2407 let got = printer_contents(&mut printer);
2408 let expected = "\
240916:For the Doctor Watsons of this world, as opposed to the Sherlock
241016:Holmeses, success in the province of detective work must always
241116:be, to a very large extent, the result of luck. Sherlock Holmes
241216:can extract a clew from a wisp of straw or a flake of cigar ash;
241316:but Doctor Watson has to have it taken out for him and dusted,
2414";
2415 assert_eq_printed!(expected, got);
2416 }
2417
2418 #[test]
2419 fn byte_offset() {
2420 let matcher = RegexMatcher::new("Watson").unwrap();
2421 let mut printer = StandardBuilder::new()
2422 .byte_offset(true)
2423 .build(NoColor::new(vec![]));
2424 SearcherBuilder::new()
2425 .line_number(false)
2426 .build()
2427 .search_reader(
2428 &matcher,
2429 SHERLOCK.as_bytes(),
2430 printer.sink(&matcher),
2431 )
2432 .unwrap();
2433
2434 let got = printer_contents(&mut printer);
2435 let expected = "\
24360:For the Doctor Watsons of this world, as opposed to the Sherlock
2437258:but Doctor Watson has to have it taken out for him and dusted,
2438";
2439 assert_eq_printed!(expected, got);
2440 }
2441
2442 #[test]
2443 fn byte_offset_multi_line() {
2444 let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2445 let mut printer = StandardBuilder::new()
2446 .byte_offset(true)
2447 .build(NoColor::new(vec![]));
2448 SearcherBuilder::new()
2449 .line_number(false)
2450 .multi_line(true)
2451 .build()
2452 .search_reader(
2453 &matcher,
2454 SHERLOCK.as_bytes(),
2455 printer.sink(&matcher),
2456 )
2457 .unwrap();
2458
2459 let got = printer_contents(&mut printer);
2460 let expected = "\
24610:For the Doctor Watsons of this world, as opposed to the Sherlock
246265:Holmeses, success in the province of detective work must always
2463129:be, to a very large extent, the result of luck. Sherlock Holmes
2464193:can extract a clew from a wisp of straw or a flake of cigar ash;
2465258:but Doctor Watson has to have it taken out for him and dusted,
2466";
2467 assert_eq_printed!(expected, got);
2468 }
2469
2470 #[test]
2471 fn max_columns() {
2472 let matcher = RegexMatcher::new("ash|dusted").unwrap();
2473 let mut printer = StandardBuilder::new()
2474 .max_columns(Some(63))
2475 .build(NoColor::new(vec![]));
2476 SearcherBuilder::new()
2477 .line_number(false)
2478 .build()
2479 .search_reader(
2480 &matcher,
2481 SHERLOCK.as_bytes(),
2482 printer.sink(&matcher),
2483 )
2484 .unwrap();
2485
2486 let got = printer_contents(&mut printer);
2487 let expected = "\
2488[Omitted long matching line]
2489but Doctor Watson has to have it taken out for him and dusted,
2490";
2491 assert_eq_printed!(expected, got);
2492 }
2493
2494 #[test]
2495 fn max_columns_preview() {
2496 let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
2497 let mut printer = StandardBuilder::new()
2498 .max_columns(Some(46))
2499 .max_columns_preview(true)
2500 .build(NoColor::new(vec![]));
2501 SearcherBuilder::new()
2502 .line_number(false)
2503 .build()
2504 .search_reader(
2505 &matcher,
2506 SHERLOCK.as_bytes(),
2507 printer.sink(&matcher),
2508 )
2509 .unwrap();
2510
2511 let got = printer_contents(&mut printer);
2512 let expected = "\
2513but Doctor Watson has to have it taken out for [... omitted end of long line]
2514and exhibited clearly, with a label attached.
2515";
2516 assert_eq_printed!(expected, got);
2517 }
2518
2519 #[test]
2520 fn max_columns_with_count() {
2521 let matcher = RegexMatcher::new("cigar|ash|dusted").unwrap();
2522 let mut printer = StandardBuilder::new()
2523 .stats(true)
2524 .max_columns(Some(63))
2525 .build(NoColor::new(vec![]));
2526 SearcherBuilder::new()
2527 .line_number(false)
2528 .build()
2529 .search_reader(
2530 &matcher,
2531 SHERLOCK.as_bytes(),
2532 printer.sink(&matcher),
2533 )
2534 .unwrap();
2535
2536 let got = printer_contents(&mut printer);
2537 let expected = "\
2538[Omitted long line with 2 matches]
2539but Doctor Watson has to have it taken out for him and dusted,
2540";
2541 assert_eq_printed!(expected, got);
2542 }
2543
2544 #[test]
2545 fn max_columns_with_count_preview_no_match() {
2546 let matcher = RegexMatcher::new("exhibited|has to have it").unwrap();
2547 let mut printer = StandardBuilder::new()
2548 .stats(true)
2549 .max_columns(Some(46))
2550 .max_columns_preview(true)
2551 .build(NoColor::new(vec![]));
2552 SearcherBuilder::new()
2553 .line_number(false)
2554 .build()
2555 .search_reader(
2556 &matcher,
2557 SHERLOCK.as_bytes(),
2558 printer.sink(&matcher),
2559 )
2560 .unwrap();
2561
2562 let got = printer_contents(&mut printer);
2563 let expected = "\
2564but Doctor Watson has to have it taken out for [... 0 more matches]
2565and exhibited clearly, with a label attached.
2566";
2567 assert_eq_printed!(expected, got);
2568 }
2569
2570 #[test]
2571 fn max_columns_with_count_preview_one_match() {
2572 let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
2573 let mut printer = StandardBuilder::new()
2574 .stats(true)
2575 .max_columns(Some(46))
2576 .max_columns_preview(true)
2577 .build(NoColor::new(vec![]));
2578 SearcherBuilder::new()
2579 .line_number(false)
2580 .build()
2581 .search_reader(
2582 &matcher,
2583 SHERLOCK.as_bytes(),
2584 printer.sink(&matcher),
2585 )
2586 .unwrap();
2587
2588 let got = printer_contents(&mut printer);
2589 let expected = "\
2590but Doctor Watson has to have it taken out for [... 1 more match]
2591and exhibited clearly, with a label attached.
2592";
2593 assert_eq_printed!(expected, got);
2594 }
2595
2596 #[test]
2597 fn max_columns_with_count_preview_two_matches() {
2598 let matcher =
2599 RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
2600 let mut printer = StandardBuilder::new()
2601 .stats(true)
2602 .max_columns(Some(46))
2603 .max_columns_preview(true)
2604 .build(NoColor::new(vec![]));
2605 SearcherBuilder::new()
2606 .line_number(false)
2607 .build()
2608 .search_reader(
2609 &matcher,
2610 SHERLOCK.as_bytes(),
2611 printer.sink(&matcher),
2612 )
2613 .unwrap();
2614
2615 let got = printer_contents(&mut printer);
2616 let expected = "\
2617but Doctor Watson has to have it taken out for [... 1 more match]
2618and exhibited clearly, with a label attached.
2619";
2620 assert_eq_printed!(expected, got);
2621 }
2622
2623 #[test]
2624 fn max_columns_multi_line() {
2625 let matcher = RegexMatcher::new("(?s)ash.+dusted").unwrap();
2626 let mut printer = StandardBuilder::new()
2627 .max_columns(Some(63))
2628 .build(NoColor::new(vec![]));
2629 SearcherBuilder::new()
2630 .line_number(false)
2631 .multi_line(true)
2632 .build()
2633 .search_reader(
2634 &matcher,
2635 SHERLOCK.as_bytes(),
2636 printer.sink(&matcher),
2637 )
2638 .unwrap();
2639
2640 let got = printer_contents(&mut printer);
2641 let expected = "\
2642[Omitted long matching line]
2643but Doctor Watson has to have it taken out for him and dusted,
2644";
2645 assert_eq_printed!(expected, got);
2646 }
2647
2648 #[test]
2649 fn max_columns_multi_line_preview() {
2650 let matcher =
2651 RegexMatcher::new("(?s)clew|cigar ash.+have it|exhibited")
2652 .unwrap();
2653 let mut printer = StandardBuilder::new()
2654 .stats(true)
2655 .max_columns(Some(46))
2656 .max_columns_preview(true)
2657 .build(NoColor::new(vec![]));
2658 SearcherBuilder::new()
2659 .line_number(false)
2660 .multi_line(true)
2661 .build()
2662 .search_reader(
2663 &matcher,
2664 SHERLOCK.as_bytes(),
2665 printer.sink(&matcher),
2666 )
2667 .unwrap();
2668
2669 let got = printer_contents(&mut printer);
2670 let expected = "\
2671can extract a clew from a wisp of straw or a f [... 1 more match]
2672but Doctor Watson has to have it taken out for [... 0 more matches]
2673and exhibited clearly, with a label attached.
2674";
2675 assert_eq_printed!(expected, got);
2676 }
2677
2678 #[test]
2679 fn max_matches() {
2680 let matcher = RegexMatcher::new("Sherlock").unwrap();
2681 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2682 SearcherBuilder::new()
2683 .line_number(false)
2684 .max_matches(Some(1))
2685 .build()
2686 .search_reader(
2687 &matcher,
2688 SHERLOCK.as_bytes(),
2689 printer.sink(&matcher),
2690 )
2691 .unwrap();
2692
2693 let got = printer_contents(&mut printer);
2694 let expected = "\
2695For the Doctor Watsons of this world, as opposed to the Sherlock
2696";
2697 assert_eq_printed!(expected, got);
2698 }
2699
2700 #[test]
2701 fn max_matches_context() {
2702 // after context: 1
2703 let matcher = RegexMatcher::new("Doctor Watsons").unwrap();
2704 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2705 SearcherBuilder::new()
2706 .max_matches(Some(1))
2707 .line_number(false)
2708 .after_context(1)
2709 .build()
2710 .search_reader(
2711 &matcher,
2712 SHERLOCK.as_bytes(),
2713 printer.sink(&matcher),
2714 )
2715 .unwrap();
2716
2717 let got = printer_contents(&mut printer);
2718 let expected = "\
2719For the Doctor Watsons of this world, as opposed to the Sherlock
2720Holmeses, success in the province of detective work must always
2721";
2722 assert_eq_printed!(expected, got);
2723
2724 // after context: 4
2725 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2726 SearcherBuilder::new()
2727 .max_matches(Some(1))
2728 .line_number(false)
2729 .after_context(4)
2730 .build()
2731 .search_reader(
2732 &matcher,
2733 SHERLOCK.as_bytes(),
2734 printer.sink(&matcher),
2735 )
2736 .unwrap();
2737
2738 let got = printer_contents(&mut printer);
2739 let expected = "\
2740For the Doctor Watsons of this world, as opposed to the Sherlock
2741Holmeses, success in the province of detective work must always
2742be, to a very large extent, the result of luck. Sherlock Holmes
2743can extract a clew from a wisp of straw or a flake of cigar ash;
2744but Doctor Watson has to have it taken out for him and dusted,
2745";
2746 assert_eq_printed!(expected, got);
2747
2748 // after context: 1, max matches: 2
2749 let matcher = RegexMatcher::new("Doctor Watsons|but Doctor").unwrap();
2750 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2751 SearcherBuilder::new()
2752 .max_matches(Some(2))
2753 .line_number(false)
2754 .after_context(1)
2755 .build()
2756 .search_reader(
2757 &matcher,
2758 SHERLOCK.as_bytes(),
2759 printer.sink(&matcher),
2760 )
2761 .unwrap();
2762
2763 let got = printer_contents(&mut printer);
2764 let expected = "\
2765For the Doctor Watsons of this world, as opposed to the Sherlock
2766Holmeses, success in the province of detective work must always
2767--
2768but Doctor Watson has to have it taken out for him and dusted,
2769and exhibited clearly, with a label attached.
2770";
2771 assert_eq_printed!(expected, got);
2772
2773 // after context: 4, max matches: 2
2774 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2775 SearcherBuilder::new()
2776 .max_matches(Some(2))
2777 .line_number(false)
2778 .after_context(4)
2779 .build()
2780 .search_reader(
2781 &matcher,
2782 SHERLOCK.as_bytes(),
2783 printer.sink(&matcher),
2784 )
2785 .unwrap();
2786
2787 let got = printer_contents(&mut printer);
2788 let expected = "\
2789For the Doctor Watsons of this world, as opposed to the Sherlock
2790Holmeses, success in the province of detective work must always
2791be, to a very large extent, the result of luck. Sherlock Holmes
2792can extract a clew from a wisp of straw or a flake of cigar ash;
2793but Doctor Watson has to have it taken out for him and dusted,
2794and exhibited clearly, with a label attached.
2795";
2796 assert_eq_printed!(expected, got);
2797 }
2798
2799 #[test]
2800 fn max_matches_context_invert() {
2801 // after context: 1
2802 let matcher =
2803 RegexMatcher::new("success|extent|clew|dusted|exhibited").unwrap();
2804 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2805 SearcherBuilder::new()
2806 .invert_match(true)
2807 .max_matches(Some(1))
2808 .line_number(false)
2809 .after_context(1)
2810 .build()
2811 .search_reader(
2812 &matcher,
2813 SHERLOCK.as_bytes(),
2814 printer.sink(&matcher),
2815 )
2816 .unwrap();
2817
2818 let got = printer_contents(&mut printer);
2819 let expected = "\
2820For the Doctor Watsons of this world, as opposed to the Sherlock
2821Holmeses, success in the province of detective work must always
2822";
2823 assert_eq_printed!(expected, got);
2824
2825 // after context: 4
2826 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2827 SearcherBuilder::new()
2828 .invert_match(true)
2829 .max_matches(Some(1))
2830 .line_number(false)
2831 .after_context(4)
2832 .build()
2833 .search_reader(
2834 &matcher,
2835 SHERLOCK.as_bytes(),
2836 printer.sink(&matcher),
2837 )
2838 .unwrap();
2839
2840 let got = printer_contents(&mut printer);
2841 let expected = "\
2842For the Doctor Watsons of this world, as opposed to the Sherlock
2843Holmeses, success in the province of detective work must always
2844be, to a very large extent, the result of luck. Sherlock Holmes
2845can extract a clew from a wisp of straw or a flake of cigar ash;
2846but Doctor Watson has to have it taken out for him and dusted,
2847";
2848 assert_eq_printed!(expected, got);
2849
2850 // after context: 1, max matches: 2
2851 let matcher =
2852 RegexMatcher::new("success|extent|clew|exhibited").unwrap();
2853 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2854 SearcherBuilder::new()
2855 .invert_match(true)
2856 .max_matches(Some(2))
2857 .line_number(false)
2858 .after_context(1)
2859 .build()
2860 .search_reader(
2861 &matcher,
2862 SHERLOCK.as_bytes(),
2863 printer.sink(&matcher),
2864 )
2865 .unwrap();
2866
2867 let got = printer_contents(&mut printer);
2868 let expected = "\
2869For the Doctor Watsons of this world, as opposed to the Sherlock
2870Holmeses, success in the province of detective work must always
2871--
2872but Doctor Watson has to have it taken out for him and dusted,
2873and exhibited clearly, with a label attached.
2874";
2875 assert_eq_printed!(expected, got);
2876
2877 // after context: 4, max matches: 2
2878 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2879 SearcherBuilder::new()
2880 .invert_match(true)
2881 .max_matches(Some(2))
2882 .line_number(false)
2883 .after_context(4)
2884 .build()
2885 .search_reader(
2886 &matcher,
2887 SHERLOCK.as_bytes(),
2888 printer.sink(&matcher),
2889 )
2890 .unwrap();
2891
2892 let got = printer_contents(&mut printer);
2893 let expected = "\
2894For the Doctor Watsons of this world, as opposed to the Sherlock
2895Holmeses, success in the province of detective work must always
2896be, to a very large extent, the result of luck. Sherlock Holmes
2897can extract a clew from a wisp of straw or a flake of cigar ash;
2898but Doctor Watson has to have it taken out for him and dusted,
2899and exhibited clearly, with a label attached.
2900";
2901 assert_eq_printed!(expected, got);
2902 }
2903
2904 #[test]
2905 fn max_matches_multi_line1() {
2906 let matcher = RegexMatcher::new("(?s:.{0})Sherlock").unwrap();
2907 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2908 SearcherBuilder::new()
2909 .line_number(false)
2910 .multi_line(true)
2911 .max_matches(Some(1))
2912 .build()
2913 .search_reader(
2914 &matcher,
2915 SHERLOCK.as_bytes(),
2916 printer.sink(&matcher),
2917 )
2918 .unwrap();
2919
2920 let got = printer_contents(&mut printer);
2921 let expected = "\
2922For the Doctor Watsons of this world, as opposed to the Sherlock
2923";
2924 assert_eq_printed!(expected, got);
2925 }
2926
2927 #[test]
2928 fn max_matches_multi_line2() {
2929 let matcher =
2930 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
2931 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2932 SearcherBuilder::new()
2933 .line_number(false)
2934 .multi_line(true)
2935 .max_matches(Some(1))
2936 .build()
2937 .search_reader(
2938 &matcher,
2939 SHERLOCK.as_bytes(),
2940 printer.sink(&matcher),
2941 )
2942 .unwrap();
2943
2944 let got = printer_contents(&mut printer);
2945 let expected = "\
2946For the Doctor Watsons of this world, as opposed to the Sherlock
2947Holmeses, success in the province of detective work must always
2948";
2949 assert_eq_printed!(expected, got);
2950 }
2951
2952 #[test]
2953 fn max_matches_multi_line3() {
2954 let matcher = RegexMatcher::new(r"line 2\nline 3").unwrap();
2955 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2956 SearcherBuilder::new()
2957 .line_number(false)
2958 .multi_line(true)
2959 .max_matches(Some(1))
2960 .build()
2961 .search_reader(
2962 &matcher,
2963 "line 2\nline 3 x\nline 2\nline 3\n".as_bytes(),
2964 printer.sink(&matcher),
2965 )
2966 .unwrap();
2967
2968 let got = printer_contents(&mut printer);
2969 let expected = "\
2970line 2
2971line 3 x
2972";
2973 assert_eq_printed!(expected, got);
2974 }
2975
2976 #[test]
2977 fn max_matches_multi_line4() {
2978 let matcher =
2979 RegexMatcher::new(r"line 2\nline 3|x\nline 2\n").unwrap();
2980 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2981 SearcherBuilder::new()
2982 .line_number(false)
2983 .multi_line(true)
2984 .max_matches(Some(1))
2985 .build()
2986 .search_reader(
2987 &matcher,
2988 "line 2\nline 3 x\nline 2\nline 3 x\n".as_bytes(),
2989 printer.sink(&matcher),
2990 )
2991 .unwrap();
2992
2993 let got = printer_contents(&mut printer);
2994 let expected = "\
2995line 2
2996line 3 x
2997";
2998 assert_eq_printed!(expected, got);
2999 }
3000
3001 #[test]
3002 fn only_matching() {
3003 let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3004 let mut printer = StandardBuilder::new()
3005 .only_matching(true)
3006 .column(true)
3007 .build(NoColor::new(vec![]));
3008 SearcherBuilder::new()
3009 .line_number(true)
3010 .build()
3011 .search_reader(
3012 &matcher,
3013 SHERLOCK.as_bytes(),
3014 printer.sink(&matcher),
3015 )
3016 .unwrap();
3017
3018 let got = printer_contents(&mut printer);
3019 let expected = "\
30201:9:Doctor Watsons
30211:57:Sherlock
30223:49:Sherlock
3023";
3024 assert_eq_printed!(expected, got);
3025 }
3026
3027 #[test]
3028 fn only_matching_multi_line1() {
3029 let matcher =
3030 RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3031 let mut printer = StandardBuilder::new()
3032 .only_matching(true)
3033 .column(true)
3034 .build(NoColor::new(vec![]));
3035 SearcherBuilder::new()
3036 .multi_line(true)
3037 .line_number(true)
3038 .build()
3039 .search_reader(
3040 &matcher,
3041 SHERLOCK.as_bytes(),
3042 printer.sink(&matcher),
3043 )
3044 .unwrap();
3045
3046 let got = printer_contents(&mut printer);
3047 let expected = "\
30481:9:Doctor Watsons
30491:57:Sherlock
30503:49:Sherlock
3051";
3052 assert_eq_printed!(expected, got);
3053 }
3054
3055 #[test]
3056 fn only_matching_multi_line2() {
3057 let matcher =
3058 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3059 let mut printer = StandardBuilder::new()
3060 .only_matching(true)
3061 .column(true)
3062 .build(NoColor::new(vec![]));
3063 SearcherBuilder::new()
3064 .multi_line(true)
3065 .line_number(true)
3066 .build()
3067 .search_reader(
3068 &matcher,
3069 SHERLOCK.as_bytes(),
3070 printer.sink(&matcher),
3071 )
3072 .unwrap();
3073
3074 let got = printer_contents(&mut printer);
3075 let expected = "\
30761:16:Watsons of this world, as opposed to the Sherlock
30772:16:Holmeses
30785:12:Watson has to have it taken out for him and dusted,
30796:12:and exhibited clearly
3080";
3081 assert_eq_printed!(expected, got);
3082 }
3083
3084 #[test]
3085 fn only_matching_max_columns() {
3086 let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3087 let mut printer = StandardBuilder::new()
3088 .only_matching(true)
3089 .max_columns(Some(10))
3090 .column(true)
3091 .build(NoColor::new(vec![]));
3092 SearcherBuilder::new()
3093 .line_number(true)
3094 .build()
3095 .search_reader(
3096 &matcher,
3097 SHERLOCK.as_bytes(),
3098 printer.sink(&matcher),
3099 )
3100 .unwrap();
3101
3102 let got = printer_contents(&mut printer);
3103 let expected = "\
31041:9:[Omitted long matching line]
31051:57:Sherlock
31063:49:Sherlock
3107";
3108 assert_eq_printed!(expected, got);
3109 }
3110
3111 #[test]
3112 fn only_matching_max_columns_preview() {
3113 let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3114 let mut printer = StandardBuilder::new()
3115 .only_matching(true)
3116 .max_columns(Some(10))
3117 .max_columns_preview(true)
3118 .column(true)
3119 .build(NoColor::new(vec![]));
3120 SearcherBuilder::new()
3121 .line_number(true)
3122 .build()
3123 .search_reader(
3124 &matcher,
3125 SHERLOCK.as_bytes(),
3126 printer.sink(&matcher),
3127 )
3128 .unwrap();
3129
3130 let got = printer_contents(&mut printer);
3131 let expected = "\
31321:9:Doctor Wat [... 0 more matches]
31331:57:Sherlock
31343:49:Sherlock
3135";
3136 assert_eq_printed!(expected, got);
3137 }
3138
3139 #[test]
3140 fn only_matching_max_columns_multi_line1() {
3141 // The `(?s:.{0})` trick fools the matcher into thinking that it
3142 // can match across multiple lines without actually doing so. This is
3143 // so we can test multi-line handling in the case of a match on only
3144 // one line.
3145 let matcher =
3146 RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3147 let mut printer = StandardBuilder::new()
3148 .only_matching(true)
3149 .max_columns(Some(10))
3150 .column(true)
3151 .build(NoColor::new(vec![]));
3152 SearcherBuilder::new()
3153 .multi_line(true)
3154 .line_number(true)
3155 .build()
3156 .search_reader(
3157 &matcher,
3158 SHERLOCK.as_bytes(),
3159 printer.sink(&matcher),
3160 )
3161 .unwrap();
3162
3163 let got = printer_contents(&mut printer);
3164 let expected = "\
31651:9:[Omitted long matching line]
31661:57:Sherlock
31673:49:Sherlock
3168";
3169 assert_eq_printed!(expected, got);
3170 }
3171
3172 #[test]
3173 fn only_matching_max_columns_preview_multi_line1() {
3174 // The `(?s:.{0})` trick fools the matcher into thinking that it
3175 // can match across multiple lines without actually doing so. This is
3176 // so we can test multi-line handling in the case of a match on only
3177 // one line.
3178 let matcher =
3179 RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3180 let mut printer = StandardBuilder::new()
3181 .only_matching(true)
3182 .max_columns(Some(10))
3183 .max_columns_preview(true)
3184 .column(true)
3185 .build(NoColor::new(vec![]));
3186 SearcherBuilder::new()
3187 .multi_line(true)
3188 .line_number(true)
3189 .build()
3190 .search_reader(
3191 &matcher,
3192 SHERLOCK.as_bytes(),
3193 printer.sink(&matcher),
3194 )
3195 .unwrap();
3196
3197 let got = printer_contents(&mut printer);
3198 let expected = "\
31991:9:Doctor Wat [... 0 more matches]
32001:57:Sherlock
32013:49:Sherlock
3202";
3203 assert_eq_printed!(expected, got);
3204 }
3205
3206 #[test]
3207 fn only_matching_max_columns_multi_line2() {
3208 let matcher =
3209 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3210 let mut printer = StandardBuilder::new()
3211 .only_matching(true)
3212 .max_columns(Some(50))
3213 .column(true)
3214 .build(NoColor::new(vec![]));
3215 SearcherBuilder::new()
3216 .multi_line(true)
3217 .line_number(true)
3218 .build()
3219 .search_reader(
3220 &matcher,
3221 SHERLOCK.as_bytes(),
3222 printer.sink(&matcher),
3223 )
3224 .unwrap();
3225
3226 let got = printer_contents(&mut printer);
3227 let expected = "\
32281:16:Watsons of this world, as opposed to the Sherlock
32292:16:Holmeses
32305:12:[Omitted long matching line]
32316:12:and exhibited clearly
3232";
3233 assert_eq_printed!(expected, got);
3234 }
3235
3236 #[test]
3237 fn only_matching_max_columns_preview_multi_line2() {
3238 let matcher =
3239 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3240 let mut printer = StandardBuilder::new()
3241 .only_matching(true)
3242 .max_columns(Some(50))
3243 .max_columns_preview(true)
3244 .column(true)
3245 .build(NoColor::new(vec![]));
3246 SearcherBuilder::new()
3247 .multi_line(true)
3248 .line_number(true)
3249 .build()
3250 .search_reader(
3251 &matcher,
3252 SHERLOCK.as_bytes(),
3253 printer.sink(&matcher),
3254 )
3255 .unwrap();
3256
3257 let got = printer_contents(&mut printer);
3258 let expected = "\
32591:16:Watsons of this world, as opposed to the Sherlock
32602:16:Holmeses
32615:12:Watson has to have it taken out for him and dusted [... 0 more matches]
32626:12:and exhibited clearly
3263";
3264 assert_eq_printed!(expected, got);
3265 }
3266
3267 #[test]
3268 fn per_match() {
3269 let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3270 let mut printer = StandardBuilder::new()
3271 .per_match(true)
3272 .column(true)
3273 .build(NoColor::new(vec![]));
3274 SearcherBuilder::new()
3275 .line_number(true)
3276 .build()
3277 .search_reader(
3278 &matcher,
3279 SHERLOCK.as_bytes(),
3280 printer.sink(&matcher),
3281 )
3282 .unwrap();
3283
3284 let got = printer_contents(&mut printer);
3285 let expected = "\
32861:9:For the Doctor Watsons of this world, as opposed to the Sherlock
32871:57:For the Doctor Watsons of this world, as opposed to the Sherlock
32883:49:be, to a very large extent, the result of luck. Sherlock Holmes
3289";
3290 assert_eq_printed!(expected, got);
3291 }
3292
3293 #[test]
3294 fn per_match_multi_line1() {
3295 let matcher =
3296 RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3297 let mut printer = StandardBuilder::new()
3298 .per_match(true)
3299 .column(true)
3300 .build(NoColor::new(vec![]));
3301 SearcherBuilder::new()
3302 .multi_line(true)
3303 .line_number(true)
3304 .build()
3305 .search_reader(
3306 &matcher,
3307 SHERLOCK.as_bytes(),
3308 printer.sink(&matcher),
3309 )
3310 .unwrap();
3311
3312 let got = printer_contents(&mut printer);
3313 let expected = "\
33141:9:For the Doctor Watsons of this world, as opposed to the Sherlock
33151:57:For the Doctor Watsons of this world, as opposed to the Sherlock
33163:49:be, to a very large extent, the result of luck. Sherlock Holmes
3317";
3318 assert_eq_printed!(expected, got);
3319 }
3320
3321 #[test]
3322 fn per_match_multi_line2() {
3323 let matcher =
3324 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3325 let mut printer = StandardBuilder::new()
3326 .per_match(true)
3327 .column(true)
3328 .build(NoColor::new(vec![]));
3329 SearcherBuilder::new()
3330 .multi_line(true)
3331 .line_number(true)
3332 .build()
3333 .search_reader(
3334 &matcher,
3335 SHERLOCK.as_bytes(),
3336 printer.sink(&matcher),
3337 )
3338 .unwrap();
3339
3340 let got = printer_contents(&mut printer);
3341 let expected = "\
33421:16:For the Doctor Watsons of this world, as opposed to the Sherlock
33432:1:Holmeses, success in the province of detective work must always
33445:12:but Doctor Watson has to have it taken out for him and dusted,
33456:1:and exhibited clearly, with a label attached.
3346";
3347 assert_eq_printed!(expected, got);
3348 }
3349
3350 #[test]
3351 fn per_match_multi_line3() {
3352 let matcher =
3353 RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
3354 let mut printer = StandardBuilder::new()
3355 .per_match(true)
3356 .column(true)
3357 .build(NoColor::new(vec![]));
3358 SearcherBuilder::new()
3359 .multi_line(true)
3360 .line_number(true)
3361 .build()
3362 .search_reader(
3363 &matcher,
3364 SHERLOCK.as_bytes(),
3365 printer.sink(&matcher),
3366 )
3367 .unwrap();
3368
3369 let got = printer_contents(&mut printer);
3370 let expected = "\
33711:16:For the Doctor Watsons of this world, as opposed to the Sherlock
33722:1:Holmeses, success in the province of detective work must always
33732:58:Holmeses, success in the province of detective work must always
33743:1:be, to a very large extent, the result of luck. Sherlock Holmes
3375";
3376 assert_eq_printed!(expected, got);
3377 }
3378
3379 #[test]
3380 fn per_match_multi_line1_only_first_line() {
3381 let matcher =
3382 RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3383 let mut printer = StandardBuilder::new()
3384 .per_match(true)
3385 .per_match_one_line(true)
3386 .column(true)
3387 .build(NoColor::new(vec![]));
3388 SearcherBuilder::new()
3389 .multi_line(true)
3390 .line_number(true)
3391 .build()
3392 .search_reader(
3393 &matcher,
3394 SHERLOCK.as_bytes(),
3395 printer.sink(&matcher),
3396 )
3397 .unwrap();
3398
3399 let got = printer_contents(&mut printer);
3400 let expected = "\
34011:9:For the Doctor Watsons of this world, as opposed to the Sherlock
34021:57:For the Doctor Watsons of this world, as opposed to the Sherlock
34033:49:be, to a very large extent, the result of luck. Sherlock Holmes
3404";
3405 assert_eq_printed!(expected, got);
3406 }
3407
3408 #[test]
3409 fn per_match_multi_line2_only_first_line() {
3410 let matcher =
3411 RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3412 let mut printer = StandardBuilder::new()
3413 .per_match(true)
3414 .per_match_one_line(true)
3415 .column(true)
3416 .build(NoColor::new(vec![]));
3417 SearcherBuilder::new()
3418 .multi_line(true)
3419 .line_number(true)
3420 .build()
3421 .search_reader(
3422 &matcher,
3423 SHERLOCK.as_bytes(),
3424 printer.sink(&matcher),
3425 )
3426 .unwrap();
3427
3428 let got = printer_contents(&mut printer);
3429 let expected = "\
34301:16:For the Doctor Watsons of this world, as opposed to the Sherlock
34315:12:but Doctor Watson has to have it taken out for him and dusted,
3432";
3433 assert_eq_printed!(expected, got);
3434 }
3435
3436 #[test]
3437 fn per_match_multi_line3_only_first_line() {
3438 let matcher =
3439 RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
3440 let mut printer = StandardBuilder::new()
3441 .per_match(true)
3442 .per_match_one_line(true)
3443 .column(true)
3444 .build(NoColor::new(vec![]));
3445 SearcherBuilder::new()
3446 .multi_line(true)
3447 .line_number(true)
3448 .build()
3449 .search_reader(
3450 &matcher,
3451 SHERLOCK.as_bytes(),
3452 printer.sink(&matcher),
3453 )
3454 .unwrap();
3455
3456 let got = printer_contents(&mut printer);
3457 let expected = "\
34581:16:For the Doctor Watsons of this world, as opposed to the Sherlock
34592:58:Holmeses, success in the province of detective work must always
3460";
3461 assert_eq_printed!(expected, got);
3462 }
3463
3464 #[test]
3465 fn replacement_passthru() {
3466 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3467 let mut printer = StandardBuilder::new()
3468 .replacement(Some(b"doctah $1 MD".to_vec()))
3469 .build(NoColor::new(vec![]));
3470 SearcherBuilder::new()
3471 .line_number(true)
3472 .passthru(true)
3473 .build()
3474 .search_reader(
3475 &matcher,
3476 SHERLOCK.as_bytes(),
3477 printer.sink(&matcher),
3478 )
3479 .unwrap();
3480
3481 let got = printer_contents(&mut printer);
3482 let expected = "\
34831:For the doctah Watsons MD of this world, as opposed to the doctah MD
34842-Holmeses, success in the province of detective work must always
34853:be, to a very large extent, the result of luck. doctah MD Holmes
34864-can extract a clew from a wisp of straw or a flake of cigar ash;
34875:but doctah Watson MD has to have it taken out for him and dusted,
34886-and exhibited clearly, with a label attached.
3489";
3490 assert_eq_printed!(expected, got);
3491 }
3492
3493 #[test]
3494 fn replacement() {
3495 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3496 let mut printer = StandardBuilder::new()
3497 .replacement(Some(b"doctah $1 MD".to_vec()))
3498 .build(NoColor::new(vec![]));
3499 SearcherBuilder::new()
3500 .line_number(true)
3501 .build()
3502 .search_reader(
3503 &matcher,
3504 SHERLOCK.as_bytes(),
3505 printer.sink(&matcher),
3506 )
3507 .unwrap();
3508
3509 let got = printer_contents(&mut printer);
3510 let expected = "\
35111:For the doctah Watsons MD of this world, as opposed to the doctah MD
35123:be, to a very large extent, the result of luck. doctah MD Holmes
35135:but doctah Watson MD has to have it taken out for him and dusted,
3514";
3515 assert_eq_printed!(expected, got);
3516 }
3517
3518 // This is a somewhat weird test that checks the behavior of attempting
3519 // to replace a line terminator with something else.
3520 //
3521 // See: https://github.com/BurntSushi/ripgrep/issues/1311
3522 #[test]
3523 fn replacement_multi_line() {
3524 let matcher = RegexMatcher::new(r"\n").unwrap();
3525 let mut printer = StandardBuilder::new()
3526 .replacement(Some(b"?".to_vec()))
3527 .build(NoColor::new(vec![]));
3528 SearcherBuilder::new()
3529 .line_number(true)
3530 .multi_line(true)
3531 .build()
3532 .search_reader(
3533 &matcher,
3534 "hello\nworld\n".as_bytes(),
3535 printer.sink(&matcher),
3536 )
3537 .unwrap();
3538
3539 let got = printer_contents(&mut printer);
3540 let expected = "1:hello?world?\n";
3541 assert_eq_printed!(expected, got);
3542 }
3543
3544 #[test]
3545 fn replacement_multi_line_diff_line_term() {
3546 let matcher = RegexMatcherBuilder::new()
3547 .line_terminator(Some(b'\x00'))
3548 .build(r"\n")
3549 .unwrap();
3550 let mut printer = StandardBuilder::new()
3551 .replacement(Some(b"?".to_vec()))
3552 .build(NoColor::new(vec![]));
3553 SearcherBuilder::new()
3554 .line_terminator(LineTerminator::byte(b'\x00'))
3555 .line_number(true)
3556 .multi_line(true)
3557 .build()
3558 .search_reader(
3559 &matcher,
3560 "hello\nworld\n".as_bytes(),
3561 printer.sink(&matcher),
3562 )
3563 .unwrap();
3564
3565 let got = printer_contents(&mut printer);
3566 let expected = "1:hello?world?\x00";
3567 assert_eq_printed!(expected, got);
3568 }
3569
3570 #[test]
3571 fn replacement_multi_line_combine_lines() {
3572 let matcher = RegexMatcher::new(r"\n(.)?").unwrap();
3573 let mut printer = StandardBuilder::new()
3574 .replacement(Some(b"?$1".to_vec()))
3575 .build(NoColor::new(vec![]));
3576 SearcherBuilder::new()
3577 .line_number(true)
3578 .multi_line(true)
3579 .build()
3580 .search_reader(
3581 &matcher,
3582 "hello\nworld\n".as_bytes(),
3583 printer.sink(&matcher),
3584 )
3585 .unwrap();
3586
3587 let got = printer_contents(&mut printer);
3588 let expected = "1:hello?world?\n";
3589 assert_eq_printed!(expected, got);
3590 }
3591
3592 #[test]
3593 fn replacement_max_columns() {
3594 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3595 let mut printer = StandardBuilder::new()
3596 .max_columns(Some(67))
3597 .replacement(Some(b"doctah $1 MD".to_vec()))
3598 .build(NoColor::new(vec![]));
3599 SearcherBuilder::new()
3600 .line_number(true)
3601 .build()
3602 .search_reader(
3603 &matcher,
3604 SHERLOCK.as_bytes(),
3605 printer.sink(&matcher),
3606 )
3607 .unwrap();
3608
3609 let got = printer_contents(&mut printer);
3610 let expected = "\
36111:[Omitted long line with 2 matches]
36123:be, to a very large extent, the result of luck. doctah MD Holmes
36135:but doctah Watson MD has to have it taken out for him and dusted,
3614";
3615 assert_eq_printed!(expected, got);
3616 }
3617
3618 #[test]
3619 fn replacement_max_columns_preview1() {
3620 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3621 let mut printer = StandardBuilder::new()
3622 .max_columns(Some(67))
3623 .max_columns_preview(true)
3624 .replacement(Some(b"doctah $1 MD".to_vec()))
3625 .build(NoColor::new(vec![]));
3626 SearcherBuilder::new()
3627 .line_number(true)
3628 .build()
3629 .search_reader(
3630 &matcher,
3631 SHERLOCK.as_bytes(),
3632 printer.sink(&matcher),
3633 )
3634 .unwrap();
3635
3636 let got = printer_contents(&mut printer);
3637 let expected = "\
36381:For the doctah Watsons MD of this world, as opposed to the doctah [... 0 more matches]
36393:be, to a very large extent, the result of luck. doctah MD Holmes
36405:but doctah Watson MD has to have it taken out for him and dusted,
3641";
3642 assert_eq_printed!(expected, got);
3643 }
3644
3645 #[test]
3646 fn replacement_max_columns_preview2() {
3647 let matcher =
3648 RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
3649 let mut printer = StandardBuilder::new()
3650 .max_columns(Some(43))
3651 .max_columns_preview(true)
3652 .replacement(Some(b"xxx".to_vec()))
3653 .build(NoColor::new(vec![]));
3654 SearcherBuilder::new()
3655 .line_number(false)
3656 .build()
3657 .search_reader(
3658 &matcher,
3659 SHERLOCK.as_bytes(),
3660 printer.sink(&matcher),
3661 )
3662 .unwrap();
3663
3664 let got = printer_contents(&mut printer);
3665 let expected = "\
3666but Doctor Watson xxx taken out for him and [... 1 more match]
3667and xxx clearly, with a label attached.
3668";
3669 assert_eq_printed!(expected, got);
3670 }
3671
3672 #[test]
3673 fn replacement_only_matching() {
3674 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3675 let mut printer = StandardBuilder::new()
3676 .only_matching(true)
3677 .replacement(Some(b"doctah $1 MD".to_vec()))
3678 .build(NoColor::new(vec![]));
3679 SearcherBuilder::new()
3680 .line_number(true)
3681 .build()
3682 .search_reader(
3683 &matcher,
3684 SHERLOCK.as_bytes(),
3685 printer.sink(&matcher),
3686 )
3687 .unwrap();
3688
3689 let got = printer_contents(&mut printer);
3690 let expected = "\
36911:doctah Watsons MD
36921:doctah MD
36933:doctah MD
36945:doctah Watson MD
3695";
3696 assert_eq_printed!(expected, got);
3697 }
3698
3699 #[test]
3700 fn replacement_per_match() {
3701 let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3702 let mut printer = StandardBuilder::new()
3703 .per_match(true)
3704 .replacement(Some(b"doctah $1 MD".to_vec()))
3705 .build(NoColor::new(vec![]));
3706 SearcherBuilder::new()
3707 .line_number(true)
3708 .build()
3709 .search_reader(
3710 &matcher,
3711 SHERLOCK.as_bytes(),
3712 printer.sink(&matcher),
3713 )
3714 .unwrap();
3715
3716 let got = printer_contents(&mut printer);
3717 let expected = "\
37181:For the doctah Watsons MD of this world, as opposed to the doctah MD
37191:For the doctah Watsons MD of this world, as opposed to the doctah MD
37203:be, to a very large extent, the result of luck. doctah MD Holmes
37215:but doctah Watson MD has to have it taken out for him and dusted,
3722";
3723 assert_eq_printed!(expected, got);
3724 }
3725
3726 #[test]
3727 fn invert() {
3728 let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3729 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3730 SearcherBuilder::new()
3731 .line_number(true)
3732 .invert_match(true)
3733 .build()
3734 .search_reader(
3735 &matcher,
3736 SHERLOCK.as_bytes(),
3737 printer.sink(&matcher),
3738 )
3739 .unwrap();
3740
3741 let got = printer_contents(&mut printer);
3742 let expected = "\
37432:Holmeses, success in the province of detective work must always
37444:can extract a clew from a wisp of straw or a flake of cigar ash;
37455:but Doctor Watson has to have it taken out for him and dusted,
37466:and exhibited clearly, with a label attached.
3747";
3748 assert_eq_printed!(expected, got);
3749 }
3750
3751 #[test]
3752 fn invert_multi_line() {
3753 let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3754 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3755 SearcherBuilder::new()
3756 .multi_line(true)
3757 .line_number(true)
3758 .invert_match(true)
3759 .build()
3760 .search_reader(
3761 &matcher,
3762 SHERLOCK.as_bytes(),
3763 printer.sink(&matcher),
3764 )
3765 .unwrap();
3766
3767 let got = printer_contents(&mut printer);
3768 let expected = "\
37692:Holmeses, success in the province of detective work must always
37704:can extract a clew from a wisp of straw or a flake of cigar ash;
37715:but Doctor Watson has to have it taken out for him and dusted,
37726:and exhibited clearly, with a label attached.
3773";
3774 assert_eq_printed!(expected, got);
3775 }
3776
3777 #[test]
3778 fn invert_context() {
3779 let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3780 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3781 SearcherBuilder::new()
3782 .line_number(true)
3783 .invert_match(true)
3784 .before_context(1)
3785 .after_context(1)
3786 .build()
3787 .search_reader(
3788 &matcher,
3789 SHERLOCK.as_bytes(),
3790 printer.sink(&matcher),
3791 )
3792 .unwrap();
3793
3794 let got = printer_contents(&mut printer);
3795 let expected = "\
37961-For the Doctor Watsons of this world, as opposed to the Sherlock
37972:Holmeses, success in the province of detective work must always
37983-be, to a very large extent, the result of luck. Sherlock Holmes
37994:can extract a clew from a wisp of straw or a flake of cigar ash;
38005:but Doctor Watson has to have it taken out for him and dusted,
38016:and exhibited clearly, with a label attached.
3802";
3803 assert_eq_printed!(expected, got);
3804 }
3805
3806 #[test]
3807 fn invert_context_multi_line() {
3808 let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3809 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3810 SearcherBuilder::new()
3811 .multi_line(true)
3812 .line_number(true)
3813 .invert_match(true)
3814 .before_context(1)
3815 .after_context(1)
3816 .build()
3817 .search_reader(
3818 &matcher,
3819 SHERLOCK.as_bytes(),
3820 printer.sink(&matcher),
3821 )
3822 .unwrap();
3823
3824 let got = printer_contents(&mut printer);
3825 let expected = "\
38261-For the Doctor Watsons of this world, as opposed to the Sherlock
38272:Holmeses, success in the province of detective work must always
38283-be, to a very large extent, the result of luck. Sherlock Holmes
38294:can extract a clew from a wisp of straw or a flake of cigar ash;
38305:but Doctor Watson has to have it taken out for him and dusted,
38316:and exhibited clearly, with a label attached.
3832";
3833 assert_eq_printed!(expected, got);
3834 }
3835
3836 #[test]
3837 fn invert_context_only_matching() {
3838 let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3839 let mut printer = StandardBuilder::new()
3840 .only_matching(true)
3841 .build(NoColor::new(vec![]));
3842 SearcherBuilder::new()
3843 .line_number(true)
3844 .invert_match(true)
3845 .before_context(1)
3846 .after_context(1)
3847 .build()
3848 .search_reader(
3849 &matcher,
3850 SHERLOCK.as_bytes(),
3851 printer.sink(&matcher),
3852 )
3853 .unwrap();
3854
3855 let got = printer_contents(&mut printer);
3856 let expected = "\
38571-Sherlock
38582:Holmeses, success in the province of detective work must always
38593-Sherlock
38604:can extract a clew from a wisp of straw or a flake of cigar ash;
38615:but Doctor Watson has to have it taken out for him and dusted,
38626:and exhibited clearly, with a label attached.
3863";
3864 assert_eq_printed!(expected, got);
3865 }
3866
3867 #[test]
3868 fn invert_context_only_matching_multi_line() {
3869 let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3870 let mut printer = StandardBuilder::new()
3871 .only_matching(true)
3872 .build(NoColor::new(vec![]));
3873 SearcherBuilder::new()
3874 .multi_line(true)
3875 .line_number(true)
3876 .invert_match(true)
3877 .before_context(1)
3878 .after_context(1)
3879 .build()
3880 .search_reader(
3881 &matcher,
3882 SHERLOCK.as_bytes(),
3883 printer.sink(&matcher),
3884 )
3885 .unwrap();
3886
3887 let got = printer_contents(&mut printer);
3888 let expected = "\
38891-Sherlock
38902:Holmeses, success in the province of detective work must always
38913-Sherlock
38924:can extract a clew from a wisp of straw or a flake of cigar ash;
38935:but Doctor Watson has to have it taken out for him and dusted,
38946:and exhibited clearly, with a label attached.
3895";
3896 assert_eq_printed!(expected, got);
3897 }
3898
3899 #[test]
3900 fn regression_search_empty_with_crlf() {
3901 let matcher =
3902 RegexMatcherBuilder::new().crlf(true).build(r"x?").unwrap();
3903 let mut printer = StandardBuilder::new()
3904 .color_specs(ColorSpecs::default_with_color())
3905 .build(Ansi::new(vec![]));
3906 SearcherBuilder::new()
3907 .line_terminator(LineTerminator::crlf())
3908 .build()
3909 .search_reader(&matcher, &b"\n"[..], printer.sink(&matcher))
3910 .unwrap();
3911
3912 let got = printer_contents_ansi(&mut printer);
3913 assert!(!got.is_empty());
3914 }
3915
3916 #[test]
3917 fn regression_after_context_with_match() {
3918 let haystack = "\
3919a
3920b
3921c
3922d
3923e
3924d
3925e
3926d
3927e
3928d
3929e
3930";
3931
3932 let matcher = RegexMatcherBuilder::new().build(r"d").unwrap();
3933 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3934 SearcherBuilder::new()
3935 .max_matches(Some(1))
3936 .line_number(true)
3937 .after_context(2)
3938 .build()
3939 .search_reader(
3940 &matcher,
3941 haystack.as_bytes(),
3942 printer.sink(&matcher),
3943 )
3944 .unwrap();
3945
3946 let got = printer_contents(&mut printer);
3947 let expected = "4:d\n5-e\n6:d\n";
3948 assert_eq_printed!(expected, got);
3949 }
3950
3951 #[test]
3952 fn regression_crlf_preserve() {
3953 let haystack = "hello\nworld\r\n";
3954 let matcher =
3955 RegexMatcherBuilder::new().crlf(true).build(r".").unwrap();
3956 let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3957 let mut searcher = SearcherBuilder::new()
3958 .line_number(false)
3959 .line_terminator(LineTerminator::crlf())
3960 .build();
3961
3962 searcher
3963 .search_reader(
3964 &matcher,
3965 haystack.as_bytes(),
3966 printer.sink(&matcher),
3967 )
3968 .unwrap();
3969 let got = printer_contents(&mut printer);
3970 let expected = "hello\nworld\r\n";
3971 assert_eq_printed!(expected, got);
3972
3973 let mut printer = StandardBuilder::new()
3974 .replacement(Some(b"$0".to_vec()))
3975 .build(NoColor::new(vec![]));
3976 searcher
3977 .search_reader(
3978 &matcher,
3979 haystack.as_bytes(),
3980 printer.sink(&matcher),
3981 )
3982 .unwrap();
3983 let got = printer_contents(&mut printer);
3984 let expected = "hello\nworld\r\n";
3985 assert_eq_printed!(expected, got);
3986 }
3987}