use crate::print;
use crate::print::{Printer, StdoutPrinter};
use grep::searcher::{Searcher, Sink, SinkContext, SinkError, SinkMatch};
use std::fmt::Display;
use std::io;
use std::panic;
use termion::color::{Fg, LightRed};
use thiserror::Error;
const PASSTHRU_PANIC_MSG: &str = "passthru is not enabled on the given searcher";
pub(crate) struct ContextPrintingSink<P: Printer> {
printer: P,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Print failure: {0}")]
PrintFailed(
io::Error,
),
#[error("{0}")]
SearchError(
String,
),
}
impl From<print::Error> for Error {
fn from(err: print::Error) -> Self {
let io_err = match err {
print::Error::BrokenPipe(wrapped) | print::Error::Other(wrapped) => wrapped,
};
Error::PrintFailed(io_err)
}
}
impl SinkError for Error {
fn error_message<T: Display>(message: T) -> Self {
Error::SearchError(message.to_string())
}
}
impl<P: Printer> ContextPrintingSink<P> {
fn get_sink_result_for_print_result(res: print::Result) -> Result<bool, Error> {
match res {
Err(print::Error::Other(_)) => Err(Error::from(res.unwrap_err())),
Err(print::Error::BrokenPipe(_)) => Ok(false),
Ok(_) => Ok(true),
}
}
}
impl<P: Printer> ContextPrintingSink<P> {
#[must_use]
pub fn new(printer: P) -> Self {
ContextPrintingSink { printer }
}
fn validate_searcher(searcher: &Searcher) {
if !searcher.passthru() {
panic!("{}", PASSTHRU_PANIC_MSG)
}
}
}
impl Default for ContextPrintingSink<StdoutPrinter> {
fn default() -> Self {
ContextPrintingSink {
printer: StdoutPrinter {},
}
}
}
impl<P: Printer> Sink for ContextPrintingSink<P> {
type Error = Error;
fn matched(
&mut self,
searcher: &Searcher,
sink_match: &SinkMatch,
) -> Result<bool, Self::Error> {
Self::validate_searcher(searcher);
let print_res = self
.printer
.colored_print(Fg(LightRed), String::from_utf8_lossy(sink_match.bytes()));
Self::get_sink_result_for_print_result(print_res)
}
fn context(
&mut self,
searcher: &Searcher,
context: &SinkContext<'_>,
) -> Result<bool, Self::Error> {
Self::validate_searcher(searcher);
let data = String::from_utf8_lossy(context.bytes());
let print_res = self.printer.print(data);
Self::get_sink_result_for_print_result(print_res)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testutil::mock_print::MockPrinter;
use grep::regex::RegexMatcher;
use grep::searcher::SearcherBuilder;
use test_case::test_case;
const SEARCH_TEXT: &str = "The quick \n\
brown fox \n\
jumped over \n\
the lazy \n\
dog.";
enum RequiredSearcherSettings {
Passthru,
}
#[test_case(&[RequiredSearcherSettings::Passthru], true; "passthru")]
#[test_case(&[], false; "none")]
fn test_requires_properly_configured_searcher(
settings: &[RequiredSearcherSettings],
valid: bool,
) {
let perform_search = || {
let matcher = RegexMatcher::new("fox").expect("regexp doesn't compile");
let mock_printer = MockPrinter::default();
let sink = ContextPrintingSink {
printer: &mock_printer,
};
let mut builder = SearcherBuilder::new();
for setting in settings {
match setting {
RequiredSearcherSettings::Passthru => builder.passthru(true),
};
}
let mut searcher = builder.build();
searcher.search_slice(matcher, SEARCH_TEXT.as_bytes(), sink)
};
if valid {
let search_res = perform_search();
assert!(search_res.is_ok());
} else {
let search_res = panic::catch_unwind(perform_search);
assert!(search_res.is_err());
match search_res.unwrap_err().downcast_ref::<String>() {
Some(err) => assert_eq!(err, PASSTHRU_PANIC_MSG),
None => panic!("Panicked error was not of expected type"),
};
}
}
}