smolgrep/src/parser.rs

113 lines
3.1 KiB
Rust
Raw Normal View History

use std::{
fs::File,
io::{BufRead, BufReader},
path::{Path, PathBuf},
};
use anyhow::Context;
2026-07-27 09:04:19 +00:00
use regex::Regex;
2026-07-27 10:03:14 +00:00
use crate::matches::{Match, Matches};
pub fn process_files(
file_paths: &Vec<PathBuf>,
pattern: &str,
) -> anyhow::Result<Matches, anyhow::Error> {
let mut matches = Matches { items: vec![], pattern: String::from(pattern) };
for file_path in file_paths {
let lines_iter = read_lines(file_path)?;
let mut _matches = match_pattern(lines_iter, pattern, file_path)?;
matches.append(&mut _matches);
}
// let lines_iter = read_lines(file_path)?;
// let matches = match_pattern(lines_iter, pattern, file_path)?;
Ok(matches)
}
pub fn read_lines(
file_path: &Path,
) -> anyhow::Result<impl Iterator<Item = std::io::Result<String>>, anyhow::Error> {
match file_path.try_exists() {
Ok(true) => {}
Ok(false) => return Err(anyhow::anyhow!("File path needs to exist")),
Err(e) => eprintln!("Error checking path: {e}"),
}
let path_string = file_path.to_string_lossy().to_string();
let file =
File::open(file_path).with_context(|| format!("failed to open file: {path_string}"))?;
let reader = BufReader::new(file);
Ok(reader.lines())
}
fn get_file_name_from_path(file_path: &Path) -> String {
match file_path.file_name() {
Some(name) => name.to_string_lossy().to_string(),
None => String::from("unknown"),
}
}
2026-07-27 09:04:19 +00:00
pub fn match_pattern(
iter: impl Iterator<Item = std::io::Result<String>>,
pattern: &str,
file_path: &Path,
) -> anyhow::Result<Matches, anyhow::Error> {
2026-07-27 09:04:19 +00:00
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?;
2026-07-27 09:04:19 +00:00
if reg.is_match(&line) {
let m = Match {
2026-07-27 10:03:14 +00:00
line_number: idx + 1,
text: line,
file_name: get_file_name_from_path(file_path),
};
matches.push(m);
}
}
2026-07-27 09:04:19 +00:00
Ok(Matches { items: matches, pattern: String::from(pattern) })
}
2026-07-27 10:20:13 +00:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_file_name_from_path() {
let path = Path::new("/path/to/file.txt");
2026-07-27 10:20:13 +00:00
let file_name = get_file_name_from_path(path);
assert_eq!(file_name, "file.txt");
let path = Path::new("file.txt");
2026-07-27 10:20:13 +00:00
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<std::io::Result<String>> =
vec![Ok("hola".into()), Ok("no".into()), Ok("hola again".into())];
let result =
match_pattern(input.into_iter(), r"^hola", Path::new("/tmp/test.txt")).unwrap();
2026-07-27 10:20:13 +00:00
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);
}
}