smolgrep/src/parser.rs

116 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 17:00:11 +00:00
use regex::{Regex, RegexBuilder};
2026-07-27 17:00:11 +00:00
use crate::{
cli::CliOptions,
2026-07-27 17:00:11 +00:00
matches::{Match, Matches},
};
pub fn process_files(
file_paths: &Vec<PathBuf>,
pattern: &str,
cli_options: &CliOptions,
) -> anyhow::Result<Matches, anyhow::Error> {
let reg = RegexBuilder::new(pattern)
.case_insensitive(cli_options.ignore_case)
.build()
.with_context(|| format!("invalid regex: {:?}", pattern))?;
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, &reg)?;
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> {
2026-07-27 18:29:57 +00:00
let file = File::open(file_path)
.with_context(|| format!("failed to open file: {}", file_path.display()))?;
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,
regex: &Regex,
) -> anyhow::Result<Matches, anyhow::Error> {
let mut matches = Vec::new();
for (idx, line) in iter.enumerate() {
let line = line?;
2026-07-27 09:04:19 +00:00
if regex.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 reg = Regex::new("hola").unwrap();
let result =
match_pattern(input.into_iter(), r"^hola", Path::new("/tmp/test.txt"), &reg).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);
}
}