use std::{
cell::{Cell, RefCell},
cmp,
io::{self, Write},
path::Path,
sync::Arc,
time::Instant,
};
use {
bstr::ByteSlice,
grep_matcher::{Match, Matcher},
grep_searcher::{
LineStep, Searcher, Sink, SinkContext, SinkContextKind, SinkFinish,
SinkMatch,
},
termcolor::{ColorSpec, NoColor, WriteColor},
};
use crate::{
color::ColorSpecs,
counter::CounterWriter,
hyperlink::{self, HyperlinkConfig},
stats::Stats,
util::{
find_iter_at_in_context, trim_ascii_prefix, trim_line_terminator,
DecimalFormatter, PrinterPath, Replacer, Sunk,
},
};
#[derive(Debug, Clone)]
struct Config {
colors: ColorSpecs,
hyperlink: HyperlinkConfig,
stats: bool,
heading: bool,
path: bool,
only_matching: bool,
per_match: bool,
per_match_one_line: bool,
replacement: Arc<Option<Vec<u8>>>,
max_columns: Option<u64>,
max_columns_preview: bool,
max_matches: Option<u64>,
column: bool,
byte_offset: bool,
trim_ascii: bool,
separator_search: Arc<Option<Vec<u8>>>,
separator_context: Arc<Option<Vec<u8>>>,
separator_field_match: Arc<Vec<u8>>,
separator_field_context: Arc<Vec<u8>>,
separator_path: Option<u8>,
path_terminator: Option<u8>,
}
impl Default for Config {
fn default() -> Config {
Config {
colors: ColorSpecs::default(),
hyperlink: HyperlinkConfig::default(),
stats: false,
heading: false,
path: true,
only_matching: false,
per_match: false,
per_match_one_line: false,
replacement: Arc::new(None),
max_columns: None,
max_columns_preview: false,
max_matches: None,
column: false,
byte_offset: false,
trim_ascii: false,
separator_search: Arc::new(None),
separator_context: Arc::new(Some(b"--".to_vec())),
separator_field_match: Arc::new(b":".to_vec()),
separator_field_context: Arc::new(b"-".to_vec()),
separator_path: None,
path_terminator: None,
}
}
}
#[derive(Clone, Debug)]
pub struct StandardBuilder {
config: Config,
}
impl StandardBuilder {
pub fn new() -> StandardBuilder {
StandardBuilder { config: Config::default() }
}
pub fn build<W: WriteColor>(&self, wtr: W) -> Standard<W> {
Standard {
config: self.config.clone(),
wtr: RefCell::new(CounterWriter::new(wtr)),
matches: vec![],
}
}
pub fn build_no_color<W: io::Write>(
&self,
wtr: W,
) -> Standard<NoColor<W>> {
self.build(NoColor::new(wtr))
}
pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut StandardBuilder {
self.config.colors = specs;
self
}
pub fn hyperlink(
&mut self,
config: HyperlinkConfig,
) -> &mut StandardBuilder {
self.config.hyperlink = config;
self
}
pub fn stats(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.stats = yes;
self
}
pub fn heading(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.heading = yes;
self
}
pub fn path(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.path = yes;
self
}
pub fn only_matching(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.only_matching = yes;
self
}
pub fn per_match(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.per_match = yes;
self
}
pub fn per_match_one_line(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.per_match_one_line = yes;
self
}
pub fn replacement(
&mut self,
replacement: Option<Vec<u8>>,
) -> &mut StandardBuilder {
self.config.replacement = Arc::new(replacement);
self
}
pub fn max_columns(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
self.config.max_columns = limit;
self
}
pub fn max_columns_preview(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.max_columns_preview = yes;
self
}
pub fn max_matches(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
self.config.max_matches = limit;
self
}
pub fn column(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.column = yes;
self
}
pub fn byte_offset(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.byte_offset = yes;
self
}
pub fn trim_ascii(&mut self, yes: bool) -> &mut StandardBuilder {
self.config.trim_ascii = yes;
self
}
pub fn separator_search(
&mut self,
sep: Option<Vec<u8>>,
) -> &mut StandardBuilder {
self.config.separator_search = Arc::new(sep);
self
}
pub fn separator_context(
&mut self,
sep: Option<Vec<u8>>,
) -> &mut StandardBuilder {
self.config.separator_context = Arc::new(sep);
self
}
pub fn separator_field_match(
&mut self,
sep: Vec<u8>,
) -> &mut StandardBuilder {
self.config.separator_field_match = Arc::new(sep);
self
}
pub fn separator_field_context(
&mut self,
sep: Vec<u8>,
) -> &mut StandardBuilder {
self.config.separator_field_context = Arc::new(sep);
self
}
pub fn separator_path(&mut self, sep: Option<u8>) -> &mut StandardBuilder {
self.config.separator_path = sep;
self
}
pub fn path_terminator(
&mut self,
terminator: Option<u8>,
) -> &mut StandardBuilder {
self.config.path_terminator = terminator;
self
}
}
#[derive(Clone, Debug)]
pub struct Standard<W> {
config: Config,
wtr: RefCell<CounterWriter<W>>,
matches: Vec<Match>,
}
impl<W: WriteColor> Standard<W> {
pub fn new(wtr: W) -> Standard<W> {
StandardBuilder::new().build(wtr)
}
}
impl<W: io::Write> Standard<NoColor<W>> {
pub fn new_no_color(wtr: W) -> Standard<NoColor<W>> {
StandardBuilder::new().build_no_color(wtr)
}
}
impl<W: WriteColor> Standard<W> {
pub fn sink<'s, M: Matcher>(
&'s mut self,
matcher: M,
) -> StandardSink<'static, 's, M, W> {
let interpolator =
hyperlink::Interpolator::new(&self.config.hyperlink);
let stats = if self.config.stats { Some(Stats::new()) } else { None };
let needs_match_granularity = self.needs_match_granularity();
StandardSink {
matcher,
standard: self,
replacer: Replacer::new(),
interpolator,
path: None,
start_time: Instant::now(),
match_count: 0,
after_context_remaining: 0,
binary_byte_offset: None,
stats,
needs_match_granularity,
}
}
pub fn sink_with_path<'p, 's, M, P>(
&'s mut self,
matcher: M,
path: &'p P,
) -> StandardSink<'p, 's, M, W>
where
M: Matcher,
P: ?Sized + AsRef<Path>,
{
if !self.config.path {
return self.sink(matcher);
}
let interpolator =
hyperlink::Interpolator::new(&self.config.hyperlink);
let stats = if self.config.stats { Some(Stats::new()) } else { None };
let ppath = PrinterPath::new(path.as_ref())
.with_separator(self.config.separator_path);
let needs_match_granularity = self.needs_match_granularity();
StandardSink {
matcher,
standard: self,
replacer: Replacer::new(),
interpolator,
path: Some(ppath),
start_time: Instant::now(),
match_count: 0,
after_context_remaining: 0,
binary_byte_offset: None,
stats,
needs_match_granularity,
}
}
fn needs_match_granularity(&self) -> bool {
let supports_color = self.wtr.borrow().supports_color();
let match_colored = !self.config.colors.matched().is_none();
(supports_color && match_colored)
|| self.config.column
|| self.config.replacement.is_some()
|| self.config.per_match
|| self.config.only_matching
|| self.config.stats
}
}
impl<W> Standard<W> {
pub fn has_written(&self) -> bool {
self.wtr.borrow().total_count() > 0
}
pub fn get_mut(&mut self) -> &mut W {
self.wtr.get_mut().get_mut()
}
pub fn into_inner(self) -> W {
self.wtr.into_inner().into_inner()
}
}
#[derive(Debug)]
pub struct StandardSink<'p, 's, M: Matcher, W> {
matcher: M,
standard: &'s mut Standard<W>,
replacer: Replacer<M>,
interpolator: hyperlink::Interpolator,
path: Option<PrinterPath<'p>>,
start_time: Instant,
match_count: u64,
after_context_remaining: u64,
binary_byte_offset: Option<u64>,
stats: Option<Stats>,
needs_match_granularity: bool,
}
impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
pub fn has_match(&self) -> bool {
self.match_count > 0
}
pub fn match_count(&self) -> u64 {
self.match_count
}
pub fn binary_byte_offset(&self) -> Option<u64> {
self.binary_byte_offset
}
pub fn stats(&self) -> Option<&Stats> {
self.stats.as_ref()
}
fn record_matches(
&mut self,
searcher: &Searcher,
bytes: &[u8],
range: std::ops::Range<usize>,
) -> io::Result<()> {
self.standard.matches.clear();
if !self.needs_match_granularity {
return Ok(());
}
let matches = &mut self.standard.matches;
find_iter_at_in_context(
searcher,
&self.matcher,
bytes,
range.clone(),
|m| {
let (s, e) = (m.start() - range.start, m.end() - range.start);
matches.push(Match::new(s, e));
true
},
)?;
if !matches.is_empty()
&& matches.last().unwrap().is_empty()
&& matches.last().unwrap().start() >= range.end
{
matches.pop().unwrap();
}
Ok(())
}
fn replace(
&mut self,
searcher: &Searcher,
bytes: &[u8],
range: std::ops::Range<usize>,
) -> io::Result<()> {
self.replacer.clear();
if self.standard.config.replacement.is_some() {
let replacement = (*self.standard.config.replacement)
.as_ref()
.map(|r| &*r)
.unwrap();
self.replacer.replace_all(
searcher,
&self.matcher,
bytes,
range,
replacement,
)?;
}
Ok(())
}
fn should_quit(&self) -> bool {
let limit = match self.standard.config.max_matches {
None => return false,
Some(limit) => limit,
};
if self.match_count < limit {
return false;
}
self.after_context_remaining == 0
}
fn match_more_than_limit(&self) -> bool {
let limit = match self.standard.config.max_matches {
None => return false,
Some(limit) => limit,
};
self.match_count > limit
}
}
impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
type Error = io::Error;
fn matched(
&mut self,
searcher: &Searcher,
mat: &SinkMatch<'_>,
) -> Result<bool, io::Error> {
self.match_count += 1;
if self.match_more_than_limit() {
self.after_context_remaining =
self.after_context_remaining.saturating_sub(1);
} else {
self.after_context_remaining = searcher.after_context() as u64;
}
self.record_matches(
searcher,
mat.buffer(),
mat.bytes_range_in_buffer(),
)?;
self.replace(searcher, mat.buffer(), mat.bytes_range_in_buffer())?;
if let Some(ref mut stats) = self.stats {
stats.add_matches(self.standard.matches.len() as u64);
stats.add_matched_lines(mat.lines().count() as u64);
}
if searcher.binary_detection().convert_byte().is_some() {
if self.binary_byte_offset.is_some() {
return Ok(false);
}
}
StandardImpl::from_match(searcher, self, mat).sink()?;
Ok(!self.should_quit())
}
fn context(
&mut self,
searcher: &Searcher,
ctx: &SinkContext<'_>,
) -> Result<bool, io::Error> {
self.standard.matches.clear();
self.replacer.clear();
if ctx.kind() == &SinkContextKind::After {
self.after_context_remaining =
self.after_context_remaining.saturating_sub(1);
}
if searcher.invert_match() {
self.record_matches(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
self.replace(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
}
if searcher.binary_detection().convert_byte().is_some() {
if self.binary_byte_offset.is_some() {
return Ok(false);
}
}
StandardImpl::from_context(searcher, self, ctx).sink()?;
Ok(!self.should_quit())
}
fn context_break(
&mut self,
searcher: &Searcher,
) -> Result<bool, io::Error> {
StandardImpl::new(searcher, self).write_context_separator()?;
Ok(true)
}
fn binary_data(
&mut self,
searcher: &Searcher,
binary_byte_offset: u64,
) -> Result<bool, io::Error> {
if searcher.binary_detection().quit_byte().is_some() {
if let Some(ref path) = self.path {
log::debug!(
"ignoring {path}: found binary data at \
offset {binary_byte_offset}",
path = path.as_path().display(),
);
}
}
self.binary_byte_offset = Some(binary_byte_offset);
Ok(true)
}
fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
self.standard.wtr.borrow_mut().reset_count();
self.start_time = Instant::now();
self.match_count = 0;
self.after_context_remaining = 0;
self.binary_byte_offset = None;
if self.standard.config.max_matches == Some(0) {
return Ok(false);
}
Ok(true)
}
fn finish(
&mut self,
searcher: &Searcher,
finish: &SinkFinish,
) -> Result<(), io::Error> {
if let Some(offset) = self.binary_byte_offset {
StandardImpl::new(searcher, self).write_binary_message(offset)?;
}
if let Some(stats) = self.stats.as_mut() {
stats.add_elapsed(self.start_time.elapsed());
stats.add_searches(1);
if self.match_count > 0 {
stats.add_searches_with_match(1);
}
stats.add_bytes_searched(finish.byte_count());
stats.add_bytes_printed(self.standard.wtr.borrow().count());
}
Ok(())
}
}
#[derive(Debug)]
struct StandardImpl<'a, M: Matcher, W> {
searcher: &'a Searcher,
sink: &'a StandardSink<'a, 'a, M, W>,
sunk: Sunk<'a>,
in_color_match: Cell<bool>,
}
impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
fn new(
searcher: &'a Searcher,
sink: &'a StandardSink<'_, '_, M, W>,
) -> StandardImpl<'a, M, W> {
StandardImpl {
searcher,
sink,
sunk: Sunk::empty(),
in_color_match: Cell::new(false),
}
}
fn from_match(
searcher: &'a Searcher,
sink: &'a StandardSink<'_, '_, M, W>,
mat: &'a SinkMatch<'a>,
) -> StandardImpl<'a, M, W> {
let sunk = Sunk::from_sink_match(
mat,
&sink.standard.matches,
sink.replacer.replacement(),
);
StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
}
fn from_context(
searcher: &'a Searcher,
sink: &'a StandardSink<'_, '_, M, W>,
ctx: &'a SinkContext<'a>,
) -> StandardImpl<'a, M, W> {
let sunk = Sunk::from_sink_context(
ctx,
&sink.standard.matches,
sink.replacer.replacement(),
);
StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
}
fn sink(&self) -> io::Result<()> {
self.write_search_prelude()?;
if self.sunk.matches().is_empty() {
if self.multi_line() && !self.is_context() {
self.sink_fast_multi_line()
} else {
self.sink_fast()
}
} else {
if self.multi_line() && !self.is_context() {
self.sink_slow_multi_line()
} else {
self.sink_slow()
}
}
}
fn sink_fast(&self) -> io::Result<()> {
debug_assert!(self.sunk.matches().is_empty());
debug_assert!(!self.multi_line() || self.is_context());
self.write_prelude(
self.sunk.absolute_byte_offset(),
self.sunk.line_number(),
None,
)?;
self.write_line(self.sunk.bytes())
}
fn sink_fast_multi_line(&self) -> io::Result<()> {
debug_assert!(self.sunk.matches().is_empty());
debug_assert!(self.multi_line());
let line_term = self.searcher.line_terminator().as_byte();
let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
for (i, line) in self.sunk.lines(line_term).enumerate() {
self.write_prelude(
absolute_byte_offset,
self.sunk.line_number().map(|n| n + i as u64),
None,
)?;
absolute_byte_offset += line.len() as u64;
self.write_line(line)?;
}
Ok(())
}
fn sink_slow(&self) -> io::Result<()> {
debug_assert!(!self.sunk.matches().is_empty());
debug_assert!(!self.multi_line() || self.is_context());
if self.config().only_matching {
for &m in self.sunk.matches() {
self.write_prelude(
self.sunk.absolute_byte_offset() + m.start() as u64,
self.sunk.line_number(),
Some(m.start() as u64 + 1),
)?;
let buf = &self.sunk.bytes()[m];
self.write_colored_line(&[Match::new(0, buf.len())], buf)?;
}
} else if self.config().per_match {
for &m in self.sunk.matches() {
self.write_prelude(
self.sunk.absolute_byte_offset() + m.start() as u64,
self.sunk.line_number(),
Some(m.start() as u64 + 1),
)?;
self.write_colored_line(&[m], self.sunk.bytes())?;
}
} else {
self.write_prelude(
self.sunk.absolute_byte_offset(),
self.sunk.line_number(),
Some(self.sunk.matches()[0].start() as u64 + 1),
)?;
self.write_colored_line(self.sunk.matches(), self.sunk.bytes())?;
}
Ok(())
}
fn sink_slow_multi_line(&self) -> io::Result<()> {
debug_assert!(!self.sunk.matches().is_empty());
debug_assert!(self.multi_line());
if self.config().only_matching {
return self.sink_slow_multi_line_only_matching();
} else if self.config().per_match {
return self.sink_slow_multi_per_match();
}
let line_term = self.searcher.line_terminator().as_byte();
let bytes = self.sunk.bytes();
let matches = self.sunk.matches();
let mut midx = 0;
let mut count = 0;
let mut stepper = LineStep::new(line_term, 0, bytes.len());
while let Some((start, end)) = stepper.next(bytes) {
let mut line = Match::new(start, end);
self.write_prelude(
self.sunk.absolute_byte_offset() + line.start() as u64,
self.sunk.line_number().map(|n| n + count),
Some(matches[0].start() as u64 + 1),
)?;
count += 1;
self.trim_ascii_prefix(bytes, &mut line);
if self.exceeds_max_columns(&bytes[line]) {
self.write_exceeded_line(bytes, line, matches, &mut midx)?;
} else {
self.write_colored_matches(bytes, line, matches, &mut midx)?;
self.write_line_term()?;
}
}
Ok(())
}
fn sink_slow_multi_line_only_matching(&self) -> io::Result<()> {
let line_term = self.searcher.line_terminator().as_byte();
let spec = self.config().colors.matched();
let bytes = self.sunk.bytes();
let matches = self.sunk.matches();
let mut midx = 0;
let mut count = 0;
let mut stepper = LineStep::new(line_term, 0, bytes.len());
while let Some((start, end)) = stepper.next(bytes) {
let mut line = Match::new(start, end);
self.trim_line_terminator(bytes, &mut line);
self.trim_ascii_prefix(bytes, &mut line);
while !line.is_empty() {
if matches[midx].end() <= line.start() {
if midx + 1 < matches.len() {
midx += 1;
continue;
} else {
break;
}
}
let m = matches[midx];
if line.start() < m.start() {
let upto = cmp::min(line.end(), m.start());
line = line.with_start(upto);
} else {
let upto = cmp::min(line.end(), m.end());
self.write_prelude(
self.sunk.absolute_byte_offset() + m.start() as u64,
self.sunk.line_number().map(|n| n + count),
Some(m.start() as u64 + 1),
)?;
let this_line = line.with_end(upto);
line = line.with_start(upto);
if self.exceeds_max_columns(&bytes[this_line]) {
self.write_exceeded_line(
bytes, this_line, matches, &mut midx,
)?;
} else {
self.write_spec(spec, &bytes[this_line])?;
self.write_line_term()?;
}
}
}
count += 1;
}
Ok(())
}
fn sink_slow_multi_per_match(&self) -> io::Result<()> {
let line_term = self.searcher.line_terminator().as_byte();
let spec = self.config().colors.matched();
let bytes = self.sunk.bytes();
for &m in self.sunk.matches() {
let mut count = 0;
let mut stepper = LineStep::new(line_term, 0, bytes.len());
while let Some((start, end)) = stepper.next(bytes) {
let mut line = Match::new(start, end);
if line.start() >= m.end() {
break;
} else if line.end() <= m.start() {
count += 1;
continue;
}
self.write_prelude(
self.sunk.absolute_byte_offset() + line.start() as u64,
self.sunk.line_number().map(|n| n + count),
Some(m.start().saturating_sub(line.start()) as u64 + 1),
)?;
count += 1;
self.trim_line_terminator(bytes, &mut line);
self.trim_ascii_prefix(bytes, &mut line);
if self.exceeds_max_columns(&bytes[line]) {
self.write_exceeded_line(bytes, line, &[m], &mut 0)?;
continue;
}
while !line.is_empty() {
if m.end() <= line.start() {
self.write(&bytes[line])?;
line = line.with_start(line.end());
} else if line.start() < m.start() {
let upto = cmp::min(line.end(), m.start());
self.write(&bytes[line.with_end(upto)])?;
line = line.with_start(upto);
} else {
let upto = cmp::min(line.end(), m.end());
self.write_spec(spec, &bytes[line.with_end(upto)])?;
line = line.with_start(upto);
}
}
self.write_line_term()?;
if self.config().per_match_one_line {
break;
}
}
}
Ok(())
}
#[inline(always)]
fn write_prelude(
&self,
absolute_byte_offset: u64,
line_number: Option<u64>,
column: Option<u64>,
) -> io::Result<()> {
let mut prelude = PreludeWriter::new(self);
prelude.start(line_number, column)?;
prelude.write_path()?;
prelude.write_line_number(line_number)?;
prelude.write_column_number(column)?;
prelude.write_byte_offset(absolute_byte_offset)?;
prelude.end()
}
#[inline(always)]
fn write_line(&self, line: &[u8]) -> io::Result<()> {
let line = if !self.config().trim_ascii {
line
} else {
let lineterm = self.searcher.line_terminator();
let full_range = Match::new(0, line.len());
let range = trim_ascii_prefix(lineterm, line, full_range);
&line[range]
};
if self.exceeds_max_columns(line) {
let range = Match::new(0, line.len());
self.write_exceeded_line(
line,
range,
self.sunk.matches(),
&mut 0,
)?;
} else {
self.write(line)?;
if !self.has_line_terminator(line) {
self.write_line_term()?;
}
}
Ok(())
}
fn write_colored_line(
&self,
matches: &[Match],
bytes: &[u8],
) -> io::Result<()> {
let spec = self.config().colors.matched();
if !self.wtr().borrow().supports_color() || spec.is_none() {
return self.write_line(bytes);
}
let mut line = Match::new(0, bytes.len());
self.trim_ascii_prefix(bytes, &mut line);
if self.exceeds_max_columns(bytes) {
self.write_exceeded_line(bytes, line, matches, &mut 0)
} else {
self.write_colored_matches(bytes, line, matches, &mut 0)?;
self.write_line_term()?;
Ok(())
}
}
fn write_colored_matches(
&self,
bytes: &[u8],
mut line: Match,
matches: &[Match],
match_index: &mut usize,
) -> io::Result<()> {
self.trim_line_terminator(bytes, &mut line);
if matches.is_empty() {
self.write(&bytes[line])?;
return Ok(());
}
while !line.is_empty() {
if matches[*match_index].end() <= line.start() {
if *match_index + 1 < matches.len() {
*match_index += 1;
continue;
} else {
self.end_color_match()?;
self.write(&bytes[line])?;
break;
}
}
let m = matches[*match_index];
if line.start() < m.start() {
let upto = cmp::min(line.end(), m.start());
self.end_color_match()?;
self.write(&bytes[line.with_end(upto)])?;
line = line.with_start(upto);
} else {
let upto = cmp::min(line.end(), m.end());
self.start_color_match()?;
self.write(&bytes[line.with_end(upto)])?;
line = line.with_start(upto);
}
}
self.end_color_match()?;
Ok(())
}
fn write_exceeded_line(
&self,
bytes: &[u8],
mut line: Match,
matches: &[Match],
match_index: &mut usize,
) -> io::Result<()> {
if self.config().max_columns_preview {
let original = line;
let end = bytes[line]
.grapheme_indices()
.map(|(_, end, _)| end)
.take(self.config().max_columns.unwrap_or(0) as usize)
.last()
.unwrap_or(0)
+ line.start();
line = line.with_end(end);
self.write_colored_matches(bytes, line, matches, match_index)?;
if matches.is_empty() {
self.write(b" [... omitted end of long line]")?;
} else {
let remaining = matches
.iter()
.filter(|m| {
m.start() >= line.end() && m.start() < original.end()
})
.count();
let tense = if remaining == 1 { "match" } else { "matches" };
write!(
self.wtr().borrow_mut(),
" [... {} more {}]",
remaining,
tense,
)?;
}
self.write_line_term()?;
return Ok(());
}
if self.sunk.original_matches().is_empty() {
if self.is_context() {
self.write(b"[Omitted long context line]")?;
} else {
self.write(b"[Omitted long matching line]")?;
}
} else {
if self.config().only_matching {
if self.is_context() {
self.write(b"[Omitted long context line]")?;
} else {
self.write(b"[Omitted long matching line]")?;
}
} else {
write!(
self.wtr().borrow_mut(),
"[Omitted long line with {} matches]",
self.sunk.original_matches().len(),
)?;
}
}
self.write_line_term()?;
Ok(())
}
fn write_path_line(&self) -> io::Result<()> {
if let Some(path) = self.path() {
self.write_path_hyperlink(path)?;
if let Some(term) = self.config().path_terminator {
self.write(&[term])?;
} else {
self.write_line_term()?;
}
}
Ok(())
}
fn write_search_prelude(&self) -> io::Result<()> {
let this_search_written = self.wtr().borrow().count() > 0;
if this_search_written {
return Ok(());
}
if let Some(ref sep) = *self.config().separator_search {
let ever_written = self.wtr().borrow().total_count() > 0;
if ever_written {
self.write(sep)?;
self.write_line_term()?;
}
}
if self.config().heading {
self.write_path_line()?;
}
Ok(())
}
fn write_binary_message(&self, offset: u64) -> io::Result<()> {
if self.sink.match_count == 0 {
return Ok(());
}
let bin = self.searcher.binary_detection();
if let Some(byte) = bin.quit_byte() {
if let Some(path) = self.path() {
self.write_path_hyperlink(path)?;
self.write(b": ")?;
}
let remainder = format!(
"WARNING: stopped searching binary file after match \
(found {:?} byte around offset {})\n",
[byte].as_bstr(),
offset,
);
self.write(remainder.as_bytes())?;
} else if let Some(byte) = bin.convert_byte() {
if let Some(path) = self.path() {
self.write_path_hyperlink(path)?;
self.write(b": ")?;
}
let remainder = format!(
"binary file matches (found {:?} byte around offset {})\n",
[byte].as_bstr(),
offset,
);
self.write(remainder.as_bytes())?;
}
Ok(())
}
fn write_context_separator(&self) -> io::Result<()> {
if let Some(ref sep) = *self.config().separator_context {
self.write(sep)?;
self.write_line_term()?;
}
Ok(())
}
fn write_line_term(&self) -> io::Result<()> {
self.write(self.searcher.line_terminator().as_bytes())
}
fn write_spec(&self, spec: &ColorSpec, buf: &[u8]) -> io::Result<()> {
let mut wtr = self.wtr().borrow_mut();
wtr.set_color(spec)?;
wtr.write_all(buf)?;
wtr.reset()?;
Ok(())
}
fn write_path(&self, path: &PrinterPath) -> io::Result<()> {
let mut wtr = self.wtr().borrow_mut();
wtr.set_color(self.config().colors.path())?;
wtr.write_all(path.as_bytes())?;
wtr.reset()
}
fn write_path_hyperlink(&self, path: &PrinterPath) -> io::Result<()> {
let status = self.start_hyperlink(path, None, None)?;
self.write_path(path)?;
self.end_hyperlink(status)
}
fn start_hyperlink(
&self,
path: &PrinterPath,
line_number: Option<u64>,
column: Option<u64>,
) -> io::Result<hyperlink::InterpolatorStatus> {
let Some(hyperpath) = path.as_hyperlink() else {
return Ok(hyperlink::InterpolatorStatus::inactive());
};
let values =
hyperlink::Values::new(hyperpath).line(line_number).column(column);
self.sink.interpolator.begin(&values, &mut *self.wtr().borrow_mut())
}
fn end_hyperlink(
&self,
status: hyperlink::InterpolatorStatus,
) -> io::Result<()> {
self.sink.interpolator.finish(status, &mut *self.wtr().borrow_mut())
}
fn start_color_match(&self) -> io::Result<()> {
if self.in_color_match.get() {
return Ok(());
}
self.wtr().borrow_mut().set_color(self.config().colors.matched())?;
self.in_color_match.set(true);
Ok(())
}
fn end_color_match(&self) -> io::Result<()> {
if !self.in_color_match.get() {
return Ok(());
}
self.wtr().borrow_mut().reset()?;
self.in_color_match.set(false);
Ok(())
}
fn write(&self, buf: &[u8]) -> io::Result<()> {
self.wtr().borrow_mut().write_all(buf)
}
fn trim_line_terminator(&self, buf: &[u8], line: &mut Match) {
trim_line_terminator(&self.searcher, buf, line);
}
fn has_line_terminator(&self, buf: &[u8]) -> bool {
self.searcher.line_terminator().is_suffix(buf)
}
fn is_context(&self) -> bool {
self.sunk.context_kind().is_some()
}
fn config(&self) -> &'a Config {
&self.sink.standard.config
}
fn wtr(&self) -> &'a RefCell<CounterWriter<W>> {
&self.sink.standard.wtr
}
fn path(&self) -> Option<&'a PrinterPath<'a>> {
self.sink.path.as_ref()
}
fn separator_field(&self) -> &[u8] {
if self.is_context() {
&self.config().separator_field_context
} else {
&self.config().separator_field_match
}
}
fn exceeds_max_columns(&self, line: &[u8]) -> bool {
self.config().max_columns.map_or(false, |m| line.len() as u64 > m)
}
fn multi_line(&self) -> bool {
self.searcher.multi_line_with_matcher(&self.sink.matcher)
}
fn trim_ascii_prefix(&self, slice: &[u8], range: &mut Match) {
if !self.config().trim_ascii {
return;
}
let lineterm = self.searcher.line_terminator();
*range = trim_ascii_prefix(lineterm, slice, *range)
}
}
struct PreludeWriter<'a, M: Matcher, W> {
std: &'a StandardImpl<'a, M, W>,
next_separator: PreludeSeparator,
field_separator: &'a [u8],
interp_status: hyperlink::InterpolatorStatus,
}
enum PreludeSeparator {
None,
FieldSeparator,
PathTerminator,
}
impl<'a, M: Matcher, W: WriteColor> PreludeWriter<'a, M, W> {
#[inline(always)]
fn new(std: &'a StandardImpl<'a, M, W>) -> PreludeWriter<'a, M, W> {
PreludeWriter {
std,
next_separator: PreludeSeparator::None,
field_separator: std.separator_field(),
interp_status: hyperlink::InterpolatorStatus::inactive(),
}
}
#[inline(always)]
fn start(
&mut self,
line_number: Option<u64>,
column: Option<u64>,
) -> io::Result<()> {
let Some(path) = self.std.path() else { return Ok(()) };
if self.config().hyperlink.format().is_line_dependent()
|| !self.config().heading
{
self.interp_status =
self.std.start_hyperlink(path, line_number, column)?;
}
Ok(())
}
#[inline(always)]
fn end(&mut self) -> io::Result<()> {
self.std.end_hyperlink(std::mem::replace(
&mut self.interp_status,
hyperlink::InterpolatorStatus::inactive(),
))?;
self.write_separator()
}
#[inline(always)]
fn write_path(&mut self) -> io::Result<()> {
if self.config().heading {
return Ok(());
}
let Some(path) = self.std.path() else { return Ok(()) };
self.write_separator()?;
self.std.write_path(path)?;
self.next_separator = if self.config().path_terminator.is_some() {
PreludeSeparator::PathTerminator
} else {
PreludeSeparator::FieldSeparator
};
Ok(())
}
#[inline(always)]
fn write_line_number(&mut self, line: Option<u64>) -> io::Result<()> {
let Some(line_number) = line else { return Ok(()) };
self.write_separator()?;
let n = DecimalFormatter::new(line_number);
self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
self.next_separator = PreludeSeparator::FieldSeparator;
Ok(())
}
#[inline(always)]
fn write_column_number(&mut self, column: Option<u64>) -> io::Result<()> {
if !self.config().column {
return Ok(());
}
let Some(column_number) = column else { return Ok(()) };
self.write_separator()?;
let n = DecimalFormatter::new(column_number);
self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
self.next_separator = PreludeSeparator::FieldSeparator;
Ok(())
}
#[inline(always)]
fn write_byte_offset(&mut self, offset: u64) -> io::Result<()> {
if !self.config().byte_offset {
return Ok(());
}
self.write_separator()?;
let n = DecimalFormatter::new(offset);
self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
self.next_separator = PreludeSeparator::FieldSeparator;
Ok(())
}
#[inline(always)]
fn write_separator(&mut self) -> io::Result<()> {
match self.next_separator {
PreludeSeparator::None => {}
PreludeSeparator::FieldSeparator => {
self.std.write(self.field_separator)?;
}
PreludeSeparator::PathTerminator => {
if let Some(term) = self.config().path_terminator {
self.std.write(&[term])?;
}
}
}
self.next_separator = PreludeSeparator::None;
Ok(())
}
#[inline(always)]
fn config(&self) -> &Config {
self.std.config()
}
}
#[cfg(test)]
mod tests {
use grep_matcher::LineTerminator;
use grep_regex::{RegexMatcher, RegexMatcherBuilder};
use grep_searcher::SearcherBuilder;
use termcolor::{Ansi, NoColor};
use super::{ColorSpecs, Standard, StandardBuilder};
const SHERLOCK: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.\
";
#[allow(dead_code)]
const SHERLOCK_CRLF: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock\r
Holmeses, success in the province of detective work must always\r
be, to a very large extent, the result of luck. Sherlock Holmes\r
can extract a clew from a wisp of straw or a flake of cigar ash;\r
but Doctor Watson has to have it taken out for him and dusted,\r
and exhibited clearly, with a label attached.\
";
fn printer_contents(printer: &mut Standard<NoColor<Vec<u8>>>) -> String {
String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
}
fn printer_contents_ansi(printer: &mut Standard<Ansi<Vec<u8>>>) -> String {
String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
}
#[test]
fn reports_match() {
let matcher = RegexMatcher::new("Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
assert!(sink.has_match());
let matcher = RegexMatcher::new("zzzzz").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
assert!(!sink.has_match());
}
#[test]
fn reports_binary() {
use grep_searcher::BinaryDetection;
let matcher = RegexMatcher::new("Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
assert!(sink.binary_byte_offset().is_none());
let matcher = RegexMatcher::new(".+").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.binary_detection(BinaryDetection::quit(b'\x00'))
.build()
.search_reader(&matcher, &b"abc\x00"[..], &mut sink)
.unwrap();
assert_eq!(sink.binary_byte_offset(), Some(3));
}
#[test]
fn reports_stats() {
use std::time::Duration;
let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
let mut printer =
StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
let stats = {
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
sink.stats().unwrap().clone()
};
let buf = printer_contents(&mut printer);
assert!(stats.elapsed() > Duration::default());
assert_eq!(stats.searches(), 1);
assert_eq!(stats.searches_with_match(), 1);
assert_eq!(stats.bytes_searched(), SHERLOCK.len() as u64);
assert_eq!(stats.bytes_printed(), buf.len() as u64);
assert_eq!(stats.matched_lines(), 2);
assert_eq!(stats.matches(), 3);
}
#[test]
fn reports_stats_multiple() {
use std::time::Duration;
let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
let mut printer =
StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
let stats = {
let mut sink = printer.sink(&matcher);
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, &b"zzzzzzzzzz"[..], &mut sink)
.unwrap();
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
.unwrap();
sink.stats().unwrap().clone()
};
let buf = printer_contents(&mut printer);
assert!(stats.elapsed() > Duration::default());
assert_eq!(stats.searches(), 3);
assert_eq!(stats.searches_with_match(), 2);
assert_eq!(stats.bytes_searched(), 10 + 2 * SHERLOCK.len() as u64);
assert_eq!(stats.bytes_printed(), buf.len() as u64);
assert_eq!(stats.matched_lines(), 4);
assert_eq!(stats.matches(), 6);
}
#[test]
fn context_break() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.separator_context(Some(b"--abc--".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--abc--
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn context_break_multiple_no_heading() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.separator_search(Some(b"--xyz--".to_vec()))
.separator_context(Some(b"--abc--".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--abc--
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
--xyz--
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--abc--
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn context_break_multiple_heading() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.heading(true)
.separator_search(Some(b"--xyz--".to_vec()))
.separator_context(Some(b"--abc--".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--abc--
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
--xyz--
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--abc--
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn path() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().path(false).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
5:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn separator_field() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.separator_field_match(b"!!".to_vec())
.separator_field_context(b"^^".to_vec())
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
sherlock!!For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock^^Holmeses, success in the province of detective work must always
--
sherlock^^can extract a clew from a wisp of straw or a flake of cigar ash;
sherlock!!but Doctor Watson has to have it taken out for him and dusted,
sherlock^^and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn separator_path() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.separator_path(Some(b'Z'))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "books/sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
booksZsherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
booksZsherlock:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn path_terminator() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.path_terminator(Some(b'Z'))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "books/sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
books/sherlockZFor the Doctor Watsons of this world, as opposed to the Sherlock
books/sherlockZbut Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn heading() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
sherlock
For the Doctor Watsons of this world, as opposed to the Sherlock
but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn no_heading() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn no_heading_multiple() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let matcher = RegexMatcher::new("Sherlock").unwrap();
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:but Doctor Watson has to have it taken out for him and dusted,
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn heading_multiple() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let matcher = RegexMatcher::new("Sherlock").unwrap();
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
sherlock
For the Doctor Watsons of this world, as opposed to the Sherlock
but Doctor Watson has to have it taken out for him and dusted,
sherlock
For the Doctor Watsons of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn trim_ascii() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.trim_ascii(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
" Watson".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
Watson
";
assert_eq_printed!(expected, got);
}
#[test]
fn trim_ascii_multi_line() {
let matcher = RegexMatcher::new("(?s:.{0})Watson").unwrap();
let mut printer = StandardBuilder::new()
.trim_ascii(true)
.stats(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
" Watson".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
Watson
";
assert_eq_printed!(expected, got);
}
#[test]
fn trim_ascii_with_line_term() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.trim_ascii(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.before_context(1)
.build()
.search_reader(
&matcher,
"\n Watson".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1-
2:Watson
";
assert_eq_printed!(expected, got);
}
#[test]
fn line_number() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
5:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn line_number_multi_line() {
let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
2:Holmeses, success in the province of detective work must always
3:be, to a very large extent, the result of luck. Sherlock Holmes
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn column_number() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer =
StandardBuilder::new().column(true).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
16:For the Doctor Watsons of this world, as opposed to the Sherlock
12:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn column_number_multi_line() {
let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
let mut printer =
StandardBuilder::new().column(true).build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
16:For the Doctor Watsons of this world, as opposed to the Sherlock
16:Holmeses, success in the province of detective work must always
16:be, to a very large extent, the result of luck. Sherlock Holmes
16:can extract a clew from a wisp of straw or a flake of cigar ash;
16:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn byte_offset() {
let matcher = RegexMatcher::new("Watson").unwrap();
let mut printer = StandardBuilder::new()
.byte_offset(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
0:For the Doctor Watsons of this world, as opposed to the Sherlock
258:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn byte_offset_multi_line() {
let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
let mut printer = StandardBuilder::new()
.byte_offset(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
0:For the Doctor Watsons of this world, as opposed to the Sherlock
65:Holmeses, success in the province of detective work must always
129:be, to a very large extent, the result of luck. Sherlock Holmes
193:can extract a clew from a wisp of straw or a flake of cigar ash;
258:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns() {
let matcher = RegexMatcher::new("ash|dusted").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(63))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
[Omitted long matching line]
but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_preview() {
let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(46))
.max_columns_preview(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
but Doctor Watson has to have it taken out for [... omitted end of long line]
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_with_count() {
let matcher = RegexMatcher::new("cigar|ash|dusted").unwrap();
let mut printer = StandardBuilder::new()
.stats(true)
.max_columns(Some(63))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
[Omitted long line with 2 matches]
but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_with_count_preview_no_match() {
let matcher = RegexMatcher::new("exhibited|has to have it").unwrap();
let mut printer = StandardBuilder::new()
.stats(true)
.max_columns(Some(46))
.max_columns_preview(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
but Doctor Watson has to have it taken out for [... 0 more matches]
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_with_count_preview_one_match() {
let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
let mut printer = StandardBuilder::new()
.stats(true)
.max_columns(Some(46))
.max_columns_preview(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
but Doctor Watson has to have it taken out for [... 1 more match]
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_with_count_preview_two_matches() {
let matcher =
RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
let mut printer = StandardBuilder::new()
.stats(true)
.max_columns(Some(46))
.max_columns_preview(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
but Doctor Watson has to have it taken out for [... 1 more match]
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_multi_line() {
let matcher = RegexMatcher::new("(?s)ash.+dusted").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(63))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
[Omitted long matching line]
but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_columns_multi_line_preview() {
let matcher =
RegexMatcher::new("(?s)clew|cigar ash.+have it|exhibited")
.unwrap();
let mut printer = StandardBuilder::new()
.stats(true)
.max_columns(Some(46))
.max_columns_preview(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
can extract a clew from a wisp of straw or a f [... 1 more match]
but Doctor Watson has to have it taken out for [... 0 more matches]
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_matches() {
let matcher = RegexMatcher::new("Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_matches_context() {
let matcher = RegexMatcher::new("Doctor Watsons").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
";
assert_eq_printed!(expected, got);
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.after_context(4)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
let matcher = RegexMatcher::new("Doctor Watsons|but Doctor").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(2))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
let mut printer = StandardBuilder::new()
.max_matches(Some(2))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.after_context(4)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_matches_multi_line1() {
let matcher = RegexMatcher::new("(?s:.{0})Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn max_matches_multi_line2() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.multi_line(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching() {
let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:Doctor Watsons
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_multi_line1() {
let matcher =
RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:Doctor Watsons
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_multi_line2() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:Watsons of this world, as opposed to the Sherlock
2:16:Holmeses
5:12:Watson has to have it taken out for him and dusted,
6:12:and exhibited clearly
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns() {
let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(10))
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:[Omitted long matching line]
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns_preview() {
let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(10))
.max_columns_preview(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:Doctor Wat [... 0 more matches]
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns_multi_line1() {
let matcher =
RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(10))
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:[Omitted long matching line]
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns_preview_multi_line1() {
let matcher =
RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(10))
.max_columns_preview(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:Doctor Wat [... 0 more matches]
1:57:Sherlock
3:49:Sherlock
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns_multi_line2() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(50))
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:Watsons of this world, as opposed to the Sherlock
2:16:Holmeses
5:12:[Omitted long matching line]
6:12:and exhibited clearly
";
assert_eq_printed!(expected, got);
}
#[test]
fn only_matching_max_columns_preview_multi_line2() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.max_columns(Some(50))
.max_columns_preview(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:Watsons of this world, as opposed to the Sherlock
2:16:Holmeses
5:12:Watson has to have it taken out for him and dusted [... 0 more matches]
6:12:and exhibited clearly
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match() {
let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:For the Doctor Watsons of this world, as opposed to the Sherlock
1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
3:49:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line1() {
let matcher =
RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:For the Doctor Watsons of this world, as opposed to the Sherlock
1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
3:49:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line2() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
2:1:Holmeses, success in the province of detective work must always
5:12:but Doctor Watson has to have it taken out for him and dusted,
6:1:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line3() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
2:1:Holmeses, success in the province of detective work must always
2:58:Holmeses, success in the province of detective work must always
3:1:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line1_only_first_line() {
let matcher =
RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.per_match_one_line(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:9:For the Doctor Watsons of this world, as opposed to the Sherlock
1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
3:49:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line2_only_first_line() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.per_match_one_line(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
5:12:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn per_match_multi_line3_only_first_line() {
let matcher =
RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.per_match_one_line(true)
.column(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
2:58:Holmeses, success in the province of detective work must always
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_passthru() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.passthru(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the doctah Watsons MD of this world, as opposed to the doctah MD
2-Holmeses, success in the province of detective work must always
3:be, to a very large extent, the result of luck. doctah MD Holmes
4-can extract a clew from a wisp of straw or a flake of cigar ash;
5:but doctah Watson MD has to have it taken out for him and dusted,
6-and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the doctah Watsons MD of this world, as opposed to the doctah MD
3:be, to a very large extent, the result of luck. doctah MD Holmes
5:but doctah Watson MD has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_multi_line() {
let matcher = RegexMatcher::new(r"\n").unwrap();
let mut printer = StandardBuilder::new()
.replacement(Some(b"?".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.multi_line(true)
.build()
.search_reader(
&matcher,
"hello\nworld\n".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "1:hello?world?\n";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_multi_line_diff_line_term() {
let matcher = RegexMatcherBuilder::new()
.line_terminator(Some(b'\x00'))
.build(r"\n")
.unwrap();
let mut printer = StandardBuilder::new()
.replacement(Some(b"?".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_terminator(LineTerminator::byte(b'\x00'))
.line_number(true)
.multi_line(true)
.build()
.search_reader(
&matcher,
"hello\nworld\n".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "1:hello?world?\x00";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_multi_line_combine_lines() {
let matcher = RegexMatcher::new(r"\n(.)?").unwrap();
let mut printer = StandardBuilder::new()
.replacement(Some(b"?$1".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.multi_line(true)
.build()
.search_reader(
&matcher,
"hello\nworld\n".as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "1:hello?world?\n";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_max_columns() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(67))
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:[Omitted long line with 2 matches]
3:be, to a very large extent, the result of luck. doctah MD Holmes
5:but doctah Watson MD has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_max_columns_preview1() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(67))
.max_columns_preview(true)
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the doctah Watsons MD of this world, as opposed to the doctah [... 0 more matches]
3:be, to a very large extent, the result of luck. doctah MD Holmes
5:but doctah Watson MD has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_max_columns_preview2() {
let matcher =
RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
let mut printer = StandardBuilder::new()
.max_columns(Some(43))
.max_columns_preview(true)
.replacement(Some(b"xxx".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(false)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
but Doctor Watson xxx taken out for him and [... 1 more match]
and xxx clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_only_matching() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:doctah Watsons MD
1:doctah MD
3:doctah MD
5:doctah Watson MD
";
assert_eq_printed!(expected, got);
}
#[test]
fn replacement_per_match() {
let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
let mut printer = StandardBuilder::new()
.per_match(true)
.replacement(Some(b"doctah $1 MD".to_vec()))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1:For the doctah Watsons MD of this world, as opposed to the doctah MD
1:For the doctah Watsons MD of this world, as opposed to the doctah MD
3:be, to a very large extent, the result of luck. doctah MD Holmes
5:but doctah Watson MD has to have it taken out for him and dusted,
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert() {
let matcher = RegexMatcher::new(r"Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.invert_match(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
2:Holmeses, success in the province of detective work must always
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert_multi_line() {
let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.invert_match(true)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
2:Holmeses, success in the province of detective work must always
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert_context() {
let matcher = RegexMatcher::new(r"Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.invert_match(true)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1-For the Doctor Watsons of this world, as opposed to the Sherlock
2:Holmeses, success in the province of detective work must always
3-be, to a very large extent, the result of luck. Sherlock Holmes
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert_context_multi_line() {
let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.invert_match(true)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1-For the Doctor Watsons of this world, as opposed to the Sherlock
2:Holmeses, success in the province of detective work must always
3-be, to a very large extent, the result of luck. Sherlock Holmes
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert_context_only_matching() {
let matcher = RegexMatcher::new(r"Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.invert_match(true)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1-Sherlock
2:Holmeses, success in the province of detective work must always
3-Sherlock
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn invert_context_only_matching_multi_line() {
let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
let mut printer = StandardBuilder::new()
.only_matching(true)
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.multi_line(true)
.line_number(true)
.invert_match(true)
.before_context(1)
.after_context(1)
.build()
.search_reader(
&matcher,
SHERLOCK.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "\
1-Sherlock
2:Holmeses, success in the province of detective work must always
3-Sherlock
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq_printed!(expected, got);
}
#[test]
fn regression_search_empty_with_crlf() {
let matcher =
RegexMatcherBuilder::new().crlf(true).build(r"x?").unwrap();
let mut printer = StandardBuilder::new()
.color_specs(ColorSpecs::default_with_color())
.build(Ansi::new(vec![]));
SearcherBuilder::new()
.line_terminator(LineTerminator::crlf())
.build()
.search_reader(&matcher, &b"\n"[..], printer.sink(&matcher))
.unwrap();
let got = printer_contents_ansi(&mut printer);
assert!(!got.is_empty());
}
#[test]
fn regression_after_context_with_match() {
let haystack = "\
a
b
c
d
e
d
e
d
e
d
e
";
let matcher = RegexMatcherBuilder::new().build(r"d").unwrap();
let mut printer = StandardBuilder::new()
.max_matches(Some(1))
.build(NoColor::new(vec![]));
SearcherBuilder::new()
.line_number(true)
.after_context(2)
.build()
.search_reader(
&matcher,
haystack.as_bytes(),
printer.sink(&matcher),
)
.unwrap();
let got = printer_contents(&mut printer);
let expected = "4:d\n5-e\n6:d\n";
assert_eq_printed!(expected, got);
}
}