#![warn(clippy::all, clippy::pedantic)]
use grep::regex;
use grep::regex::RegexMatcher;
use grep::searcher::SearcherBuilder;
use print::{Printer, StdoutPrinter};
use std::io;
use std::io::Read;
use thiserror::Error;
pub mod file;
mod lines;
pub mod print;
mod sink;
#[cfg(test)]
mod testutil;
#[derive(Error, Debug)]
pub enum Error {
#[error("Regular expression engine failed: {0}")]
RegexError(
regex::Error,
),
#[error("Search process failed: {0}")]
SearchError(
String,
),
#[error("Printing results failed: {0}")]
PrintFailure(
io::Error,
),
}
impl From<sink::Error> for Error {
fn from(err: sink::Error) -> Self {
match err {
sink::Error::SearchError(msg) => Error::SearchError(msg),
sink::Error::PrintFailed(io_err) => Error::PrintFailure(io_err),
}
}
}
impl From<regex::Error> for Error {
fn from(err: regex::Error) -> Self {
Self::RegexError(err)
}
}
pub fn scan_pattern<R: Read>(reader: R, pattern: &str) -> Result<(), Error> {
scan_pattern_to_printer(reader, pattern, StdoutPrinter::new())
}
pub fn scan_pattern_to_printer<R: Read, P: Printer>(
reader: R,
pattern: &str,
printer: P,
) -> Result<(), Error> {
let matcher = RegexMatcher::new(pattern)?;
let mut searcher = SearcherBuilder::new().passthru(true).build();
let context_sink = sink::ContextPrintingSink::new(printer);
searcher.search_reader(matcher, reader, context_sink)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testutil;
use std::io;
use std::io::Cursor;
use test_case::test_case;
use testutil::mock_print::MockPrinter;
const SEARCH_TEXT: &str = "The \"computable\" numbers may be described briefly as the real \n\
numbers whose expressions as a decimal are calculable by finite means. \n\
Although the subject of this paper is ostensibly the computable numbers. \n\
it is almost equally easy to define and investigate computable functions \n\
of an integral variable or a real or computable variable, computable \n\
predicates, and so forth. The fundamental problems involved are, \n\
however, the same in each case, and I have chosen the computable numbers \n\
for explicit treatment as involving the least cumbrous technique. I hope \n\
shortly to give an account of the relations of the computable numbers, \n\
functions, and so forth to one another. This will include a development \n\
of the theory of functions of a real variable expressed in terms of \n\
computable numbers. According to my definition, a number is computable \n\
if its decimal can be written down by a machine.\n";
#[test]
fn test_highlights_matches() {
let mock_printer = MockPrinter::default();
let mut lipsum_reader = Cursor::new(SEARCH_TEXT);
let res = scan_pattern_to_printer(
&mut lipsum_reader,
r#""?computable"?\snumbers"#,
&mock_printer,
);
if let Err(err) = res {
panic!("failed to search: {}", err)
}
let colored_messages = mock_printer.colored_messages.borrow();
#[rustfmt::skip]
let expected_colored_messages = [
"The \"computable\" numbers may be described briefly as the real \n".to_string(),
"Although the subject of this paper is ostensibly the computable numbers. \n".to_string(),
"however, the same in each case, and I have chosen the computable numbers \n".to_string(),
"shortly to give an account of the relations of the computable numbers, \n".to_string(),
"computable numbers. According to my definition, a number is computable \n".to_string(),
];
testutil::assert_slices_eq!(&colored_messages, &expected_colored_messages);
let uncolored_messages = mock_printer.uncolored_messages.borrow();
#[rustfmt::skip]
let expected_uncolored_messages = [
"numbers whose expressions as a decimal are calculable by finite means. \n".to_string(),
"it is almost equally easy to define and investigate computable functions \n".to_string(),
"of an integral variable or a real or computable variable, computable \n".to_string(),
"predicates, and so forth. The fundamental problems involved are, \n".to_string(),
"for explicit treatment as involving the least cumbrous technique. I hope \n".to_string(),
"functions, and so forth to one another. This will include a development \n".to_string(),
"of the theory of functions of a real variable expressed in terms of \n".to_string(),
"if its decimal can be written down by a machine.\n".to_string(),
];
testutil::assert_slices_eq!(&uncolored_messages, &expected_uncolored_messages);
}
#[test]
fn case_insensitive_pattern_matches() {
let mock_printer = MockPrinter::default();
let mut lipsum_reader = Cursor::new(SEARCH_TEXT);
let res = scan_pattern_to_printer(&mut lipsum_reader, "(?i)INTEGRAL", &mock_printer);
if let Err(err) = res {
panic!("failed to search: {}", err)
}
let colored_messages = mock_printer.colored_messages.borrow();
let expected_colored_messages = [
"of an integral variable or a real or computable variable, computable \n".to_string(),
];
testutil::assert_slices_eq!(&colored_messages, &expected_colored_messages);
let uncolored_messages = mock_printer.uncolored_messages.borrow();
#[rustfmt::skip]
let expected_uncolored_messages = [
"The \"computable\" numbers may be described briefly as the real \n".to_string(),
"numbers whose expressions as a decimal are calculable by finite means. \n".to_string(),
"Although the subject of this paper is ostensibly the computable numbers. \n".to_string(),
"it is almost equally easy to define and investigate computable functions \n".to_string(),
"predicates, and so forth. The fundamental problems involved are, \n".to_string(),
"however, the same in each case, and I have chosen the computable numbers \n".to_string(),
"for explicit treatment as involving the least cumbrous technique. I hope \n".to_string(),
"shortly to give an account of the relations of the computable numbers, \n".to_string(),
"functions, and so forth to one another. This will include a development \n".to_string(),
"of the theory of functions of a real variable expressed in terms of \n".to_string(),
"computable numbers. According to my definition, a number is computable \n".to_string(),
"if its decimal can be written down by a machine.\n".to_string()
];
testutil::assert_slices_eq!(&uncolored_messages, &expected_uncolored_messages);
}
#[test_case(".", 0, 1; "failure on first match will only attempt to print that match")]
#[test_case("hello I am alan turing", 1, 0; "never matching will only attempt to print the first line")]
fn test_does_not_attempt_to_print_after_broken_pipe_error(
pattern: &str,
num_uncolored_messages: usize,
num_colored_messages: usize,
) {
let mut mock_printer = MockPrinter::default();
let broken_pipe_err =
print::Error::from(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"));
mock_printer.fail_next(broken_pipe_err);
let mut lipsum_reader = Cursor::new(SEARCH_TEXT);
let res = scan_pattern_to_printer(&mut lipsum_reader, pattern, &mock_printer);
assert!(!res.is_err(), "failed to search: {:?}", res.unwrap_err());
assert_eq!(
num_colored_messages,
mock_printer.colored_messages.borrow().len()
);
assert_eq!(
num_uncolored_messages,
mock_printer.uncolored_messages.borrow().len()
);
}
}