use std::fs::File;
use std::io::{self, stdin, Read};
use clap::Parser;
use std::{borrow::Cow, io::{BufRead, BufReader}};
pub struct Cursor<'a, R: Read> {
reader: BufReader<R>,
line: usize,
case_sensitive: bool,
query: Cow<'a,str>,
}
impl<'a,R: Read> Cursor<'a,R> {
pub fn new(reader: R, case_sensitive: bool, query: &'a str) -> Self {
let reader = BufReader::new(reader);
let mut query = Cow::Borrowed(query);
if !case_sensitive {
query.to_mut().make_ascii_lowercase();
}
Self { reader, line: 0, case_sensitive, query }
}
}
impl<R: Read> Iterator for Cursor<'_, R> {
type Item = Match;
fn next(&mut self) -> Option<Match> {
self.line += 1;
let mut buf = String::new();
let n = self.reader.read_line(&mut buf).unwrap_or(0);
if n == 0 {
return None;
}
if buf.ends_with('\n') {
buf.pop();
if buf.ends_with('\r') {
buf.pop();
}
}
if !self.case_sensitive {
buf.make_ascii_lowercase();
}
buf.find(self.query.as_ref())
.map(|start| Match::new(buf, self.line, start))
.or_else(|| self.next())
}
}
#[derive(Debug,PartialEq)]
pub struct Match {
text: String,
line: usize,
start: usize,
}
impl Match {
pub fn new(text: String, line: usize, start: usize) -> Self {
Self { text, line, start }
}
pub fn print(&self, show_ctx: bool, name: &str) {
if show_ctx {
print!("{name} [{},{}]: ", self.line, self.start);
}
println!("{}", self.text);
}
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Config {
query: String,
#[arg(short = 'i', long)]
case_sensitive: bool,
#[arg(short, long)]
show_ctx: bool,
file_paths: Vec<String>,
}
pub fn run() -> crate::Result<()>{
let conf = Config::parse();
if conf.file_paths.is_empty() {
search(&conf, stdin(), "stdin")?;
}
for filename in &conf.file_paths {
match filename.as_str() {
"-" => search(&conf, stdin(), "stdin")?,
filename => {
let file = File::open(filename)?;
search(&conf, file, filename)?;
}
}
}
Ok(())
}
fn search(conf: &Config, reader: impl Read, name: &str) -> io::Result<()> {
let cursor = Cursor::new(reader, conf.case_sensitive, &conf.query);
cursor.for_each(|m| m.print(conf.show_ctx, name));
Ok(())
}