use std::cell::RefCell;
use std::io::{self, Write};
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use grep_matcher::Matcher;
use grep_searcher::{Searcher, Sink, SinkError, SinkFinish, SinkMatch};
use termcolor::{ColorSpec, NoColor, WriteColor};
use crate::color::ColorSpecs;
use crate::counter::CounterWriter;
use crate::stats::Stats;
use crate::util::{find_iter_at_in_context, PrinterPath};
#[derive(Debug, Clone)]
struct Config {
kind: SummaryKind,
colors: ColorSpecs,
stats: bool,
path: bool,
max_matches: Option<u64>,
exclude_zero: bool,
separator_field: Arc<Vec<u8>>,
separator_path: Option<u8>,
path_terminator: Option<u8>,
}
impl Default for Config {
fn default() -> Config {
Config {
kind: SummaryKind::Count,
colors: ColorSpecs::default(),
stats: false,
path: true,
max_matches: None,
exclude_zero: true,
separator_field: Arc::new(b":".to_vec()),
separator_path: None,
path_terminator: None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SummaryKind {
Count,
CountMatches,
PathWithMatch,
PathWithoutMatch,
Quiet,
}
impl SummaryKind {
fn requires_path(&self) -> bool {
use self::SummaryKind::*;
match *self {
PathWithMatch | PathWithoutMatch => true,
Count | CountMatches | Quiet => false,
}
}
fn requires_stats(&self) -> bool {
use self::SummaryKind::*;
match *self {
CountMatches => true,
Count | PathWithMatch | PathWithoutMatch | Quiet => false,
}
}
fn quit_early(&self) -> bool {
use self::SummaryKind::*;
match *self {
PathWithMatch | Quiet => true,
Count | CountMatches | PathWithoutMatch => false,
}
}
}
#[derive(Clone, Debug)]
pub struct SummaryBuilder {
config: Config,
}
impl SummaryBuilder {
pub fn new() -> SummaryBuilder {
SummaryBuilder { config: Config::default() }
}
pub fn build<W: WriteColor>(&self, wtr: W) -> Summary<W> {
Summary {
config: self.config.clone(),
wtr: RefCell::new(CounterWriter::new(wtr)),
}
}
pub fn build_no_color<W: io::Write>(&self, wtr: W) -> Summary<NoColor<W>> {
self.build(NoColor::new(wtr))
}
pub fn kind(&mut self, kind: SummaryKind) -> &mut SummaryBuilder {
self.config.kind = kind;
self
}
pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut SummaryBuilder {
self.config.colors = specs;
self
}
pub fn stats(&mut self, yes: bool) -> &mut SummaryBuilder {
self.config.stats = yes;
self
}
pub fn path(&mut self, yes: bool) -> &mut SummaryBuilder {
self.config.path = yes;
self
}
pub fn max_matches(&mut self, limit: Option<u64>) -> &mut SummaryBuilder {
self.config.max_matches = limit;
self
}
pub fn exclude_zero(&mut self, yes: bool) -> &mut SummaryBuilder {
self.config.exclude_zero = yes;
self
}
pub fn separator_field(&mut self, sep: Vec<u8>) -> &mut SummaryBuilder {
self.config.separator_field = Arc::new(sep);
self
}
pub fn separator_path(&mut self, sep: Option<u8>) -> &mut SummaryBuilder {
self.config.separator_path = sep;
self
}
pub fn path_terminator(
&mut self,
terminator: Option<u8>,
) -> &mut SummaryBuilder {
self.config.path_terminator = terminator;
self
}
}
#[derive(Debug)]
pub struct Summary<W> {
config: Config,
wtr: RefCell<CounterWriter<W>>,
}
impl<W: WriteColor> Summary<W> {
pub fn new(wtr: W) -> Summary<W> {
SummaryBuilder::new().build(wtr)
}
}
impl<W: io::Write> Summary<NoColor<W>> {
pub fn new_no_color(wtr: W) -> Summary<NoColor<W>> {
SummaryBuilder::new().build_no_color(wtr)
}
}
impl<W: WriteColor> Summary<W> {
pub fn sink<'s, M: Matcher>(
&'s mut self,
matcher: M,
) -> SummarySink<'static, 's, M, W> {
let stats = if self.config.stats || self.config.kind.requires_stats() {
Some(Stats::new())
} else {
None
};
SummarySink {
matcher: matcher,
summary: self,
path: None,
start_time: Instant::now(),
match_count: 0,
binary_byte_offset: None,
stats: stats,
}
}
pub fn sink_with_path<'p, 's, M, P>(
&'s mut self,
matcher: M,
path: &'p P,
) -> SummarySink<'p, 's, M, W>
where
M: Matcher,
P: ?Sized + AsRef<Path>,
{
if !self.config.path && !self.config.kind.requires_path() {
return self.sink(matcher);
}
let stats = if self.config.stats || self.config.kind.requires_stats() {
Some(Stats::new())
} else {
None
};
let ppath = PrinterPath::with_separator(
path.as_ref(),
self.config.separator_path,
);
SummarySink {
matcher: matcher,
summary: self,
path: Some(ppath),
start_time: Instant::now(),
match_count: 0,
binary_byte_offset: None,
stats: stats,
}
}
}
impl<W> Summary<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 SummarySink<'p, 's, M: Matcher, W> {
matcher: M,
summary: &'s mut Summary<W>,
path: Option<PrinterPath<'p>>,
start_time: Instant,
match_count: u64,
binary_byte_offset: Option<u64>,
stats: Option<Stats>,
}
impl<'p, 's, M: Matcher, W: WriteColor> SummarySink<'p, 's, M, W> {
pub fn has_match(&self) -> bool {
match self.summary.config.kind {
SummaryKind::PathWithoutMatch => self.match_count == 0,
_ => self.match_count > 0,
}
}
pub fn binary_byte_offset(&self) -> Option<u64> {
self.binary_byte_offset
}
pub fn stats(&self) -> Option<&Stats> {
self.stats.as_ref()
}
fn multi_line(&self, searcher: &Searcher) -> bool {
searcher.multi_line_with_matcher(&self.matcher)
}
fn should_quit(&self) -> bool {
let limit = match self.summary.config.max_matches {
None => return false,
Some(limit) => limit,
};
self.match_count >= limit
}
fn write_path_line(&self, searcher: &Searcher) -> io::Result<()> {
if let Some(ref path) = self.path {
self.write_spec(
self.summary.config.colors.path(),
path.as_bytes(),
)?;
if let Some(term) = self.summary.config.path_terminator {
self.write(&[term])?;
} else {
self.write_line_term(searcher)?;
}
}
Ok(())
}
fn write_path_field(&self) -> io::Result<()> {
if let Some(ref path) = self.path {
self.write_spec(
self.summary.config.colors.path(),
path.as_bytes(),
)?;
if let Some(term) = self.summary.config.path_terminator {
self.write(&[term])?;
} else {
self.write(&self.summary.config.separator_field)?;
}
}
Ok(())
}
fn write_line_term(&self, searcher: &Searcher) -> io::Result<()> {
self.write(searcher.line_terminator().as_bytes())
}
fn write_spec(&self, spec: &ColorSpec, buf: &[u8]) -> io::Result<()> {
self.summary.wtr.borrow_mut().set_color(spec)?;
self.write(buf)?;
self.summary.wtr.borrow_mut().reset()?;
Ok(())
}
fn write(&self, buf: &[u8]) -> io::Result<()> {
self.summary.wtr.borrow_mut().write_all(buf)
}
}
impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
type Error = io::Error;
fn matched(
&mut self,
searcher: &Searcher,
mat: &SinkMatch<'_>,
) -> Result<bool, io::Error> {
let is_multi_line = self.multi_line(searcher);
let sink_match_count = if self.stats.is_none() && !is_multi_line {
1
} else {
let buf = mat.buffer();
let range = mat.bytes_range_in_buffer();
let mut count = 0;
find_iter_at_in_context(
searcher,
&self.matcher,
buf,
range,
|_| {
count += 1;
true
},
)?;
count
};
if is_multi_line {
self.match_count += sink_match_count;
} else {
self.match_count += 1;
}
if let Some(ref mut stats) = self.stats {
stats.add_matches(sink_match_count);
stats.add_matched_lines(mat.lines().count() as u64);
} else if self.summary.config.kind.quit_early() {
return Ok(false);
}
Ok(!self.should_quit())
}
fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
if self.path.is_none() && self.summary.config.kind.requires_path() {
return Err(io::Error::error_message(format!(
"output kind {:?} requires a file path",
self.summary.config.kind,
)));
}
self.summary.wtr.borrow_mut().reset_count();
self.start_time = Instant::now();
self.match_count = 0;
self.binary_byte_offset = None;
if self.summary.config.max_matches == Some(0) {
return Ok(false);
}
Ok(true)
}
fn finish(
&mut self,
searcher: &Searcher,
finish: &SinkFinish,
) -> Result<(), io::Error> {
self.binary_byte_offset = finish.binary_byte_offset();
if let Some(ref mut stats) = self.stats {
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.summary.wtr.borrow().count());
}
if self.binary_byte_offset.is_some()
&& searcher.binary_detection().quit_byte().is_some()
{
self.match_count = 0;
return Ok(());
}
let show_count =
!self.summary.config.exclude_zero || self.match_count > 0;
match self.summary.config.kind {
SummaryKind::Count => {
if show_count {
self.write_path_field()?;
self.write(self.match_count.to_string().as_bytes())?;
self.write_line_term(searcher)?;
}
}
SummaryKind::CountMatches => {
if show_count {
let stats = self
.stats
.as_ref()
.expect("CountMatches should enable stats tracking");
self.write_path_field()?;
self.write(stats.matches().to_string().as_bytes())?;
self.write_line_term(searcher)?;
}
}
SummaryKind::PathWithMatch => {
if self.match_count > 0 {
self.write_path_line(searcher)?;
}
}
SummaryKind::PathWithoutMatch => {
if self.match_count == 0 {
self.write_path_line(searcher)?;
}
}
SummaryKind::Quiet => {}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use grep_regex::RegexMatcher;
use grep_searcher::SearcherBuilder;
use termcolor::NoColor;
use super::{Summary, SummaryBuilder, SummaryKind};
const SHERLOCK: &'static [u8] = b"\
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.
";
fn printer_contents(printer: &mut Summary<NoColor<Vec<u8>>>) -> String {
String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
}
#[test]
fn path_with_match_error() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithMatch)
.build_no_color(vec![]);
let res = SearcherBuilder::new().build().search_reader(
&matcher,
SHERLOCK,
printer.sink(&matcher),
);
assert!(res.is_err());
}
#[test]
fn path_without_match_error() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithoutMatch)
.build_no_color(vec![]);
let res = SearcherBuilder::new().build().search_reader(
&matcher,
SHERLOCK,
printer.sink(&matcher),
);
assert!(res.is_err());
}
#[test]
fn count_no_path() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(&matcher, SHERLOCK, printer.sink(&matcher))
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("2\n", got);
}
#[test]
fn count_no_path_even_with_path() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.path(false)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("2\n", got);
}
#[test]
fn count_path() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock:2\n", got);
}
#[test]
fn count_path_with_zero() {
let matcher = RegexMatcher::new(r"NO MATCH").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.exclude_zero(false)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock:0\n", got);
}
#[test]
fn count_path_without_zero() {
let matcher = RegexMatcher::new(r"NO MATCH").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.exclude_zero(true)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("", got);
}
#[test]
fn count_path_field_separator() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.separator_field(b"ZZ".to_vec())
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlockZZ2\n", got);
}
#[test]
fn count_path_terminator() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.path_terminator(Some(b'\x00'))
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock\x002\n", got);
}
#[test]
fn count_path_separator() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.separator_path(Some(b'\\'))
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "/home/andrew/sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("\\home\\andrew\\sherlock:2\n", got);
}
#[test]
fn count_max_matches() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Count)
.max_matches(Some(1))
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(&matcher, SHERLOCK, printer.sink(&matcher))
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("1\n", got);
}
#[test]
fn count_matches() {
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::CountMatches)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock:4\n", got);
}
#[test]
fn path_with_match_found() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithMatch)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock\n", got);
}
#[test]
fn path_with_match_not_found() {
let matcher = RegexMatcher::new(r"ZZZZZZZZ").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithMatch)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("", got);
}
#[test]
fn path_without_match_found() {
let matcher = RegexMatcher::new(r"ZZZZZZZZZ").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithoutMatch)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("sherlock\n", got);
}
#[test]
fn path_without_match_not_found() {
let matcher = RegexMatcher::new(r"Watson").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::PathWithoutMatch)
.build_no_color(vec![]);
SearcherBuilder::new()
.build()
.search_reader(
&matcher,
SHERLOCK,
printer.sink_with_path(&matcher, "sherlock"),
)
.unwrap();
let got = printer_contents(&mut printer);
assert_eq_printed!("", got);
}
#[test]
fn quiet() {
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Quiet)
.build_no_color(vec![]);
let match_count = {
let mut sink = printer.sink_with_path(&matcher, "sherlock");
SearcherBuilder::new()
.build()
.search_reader(&matcher, SHERLOCK, &mut sink)
.unwrap();
sink.match_count
};
let got = printer_contents(&mut printer);
assert_eq_printed!("", got);
assert_eq!(1, match_count);
}
#[test]
fn quiet_with_stats() {
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Quiet)
.stats(true)
.build_no_color(vec![]);
let match_count = {
let mut sink = printer.sink_with_path(&matcher, "sherlock");
SearcherBuilder::new()
.build()
.search_reader(&matcher, SHERLOCK, &mut sink)
.unwrap();
sink.match_count
};
let got = printer_contents(&mut printer);
assert_eq_printed!("", got);
assert_eq!(3, match_count);
}
}