use std::{ fs::File, io::{BufRead, BufReader}, path::Path, }; use anyhow::Context; use regex::Regex; use crate::matches::{Match, Matches}; pub fn read_lines( file_path: &str, ) -> anyhow::Result>, anyhow::Error> { if file_path.is_empty() { return Err(anyhow::anyhow!("File path cannot be empty")); } let file = File::open(file_path).with_context(|| format!("failed to open file: {file_path}"))?; let reader = BufReader::new(file); Ok(reader.lines()) } fn get_file_name_from_path(file_path: &str) -> String { match Path::new(file_path).file_name() { Some(name) => name.to_string_lossy().to_string(), None => String::from("unknown"), } } pub fn match_pattern( iter: impl Iterator>, pattern: &str, file_path: &str, ) -> anyhow::Result { let reg = Regex::new(pattern).with_context(|| format!("invalid regex: {:?}", pattern))?; let mut matches = Vec::new(); for (idx, line) in iter.enumerate() { let line = line?; if reg.is_match(&line) { let m = Match { line_number: idx + 1, text: line, file_name: get_file_name_from_path(file_path), }; matches.push(m); } } Ok(Matches { items: matches, pattern: String::from(pattern) }) } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_file_name_from_path() { let path = "/path/to/file.txt"; let file_name = get_file_name_from_path(path); assert_eq!(file_name, "file.txt"); let path = "file.txt"; let file_name = get_file_name_from_path(path); assert_eq!(file_name, "file.txt"); } #[test] fn matches_lines_and_tracks_line_numbers() { let input: Vec> = vec![Ok("hola".into()), Ok("no".into()), Ok("hola again".into())]; let result = match_pattern(input.into_iter(), r"^hola", "/tmp/test.txt").unwrap(); assert_eq!(result.items.len(), 2); assert_eq!(result.items[0].text, "hola"); assert_eq!(result.items[0].line_number, 1); assert_eq!(result.items[0].file_name, "test.txt"); assert_eq!(result.items[1].text, "hola again"); assert_eq!(result.items[1].line_number, 3); } }