2026-07-27 08:43:48 +00:00
|
|
|
use std::{
|
|
|
|
|
fs::File,
|
|
|
|
|
io::{BufRead, BufReader},
|
2026-07-27 16:38:46 +00:00
|
|
|
path::{Path, PathBuf},
|
2026-07-27 08:43:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use anyhow::Context;
|
2026-07-27 09:04:19 +00:00
|
|
|
use regex::Regex;
|
2026-07-27 08:43:48 +00:00
|
|
|
|
2026-07-27 10:03:14 +00:00
|
|
|
use crate::matches::{Match, Matches};
|
2026-07-27 09:53:58 +00:00
|
|
|
|
2026-07-27 16:38:46 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 08:43:48 +00:00
|
|
|
pub fn read_lines(
|
2026-07-27 16:38:46 +00:00
|
|
|
file_path: &Path,
|
2026-07-27 08:43:48 +00:00
|
|
|
) -> anyhow::Result<impl Iterator<Item = std::io::Result<String>>, anyhow::Error> {
|
2026-07-27 16:38:46 +00:00
|
|
|
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}"),
|
2026-07-27 08:43:48 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-27 16:38:46 +00:00
|
|
|
let path_string = file_path.to_string_lossy().to_string();
|
|
|
|
|
|
2026-07-27 08:43:48 +00:00
|
|
|
let file =
|
2026-07-27 16:38:46 +00:00
|
|
|
File::open(file_path).with_context(|| format!("failed to open file: {path_string}"))?;
|
2026-07-27 08:43:48 +00:00
|
|
|
let reader = BufReader::new(file);
|
|
|
|
|
|
|
|
|
|
Ok(reader.lines())
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 16:38:46 +00:00
|
|
|
fn get_file_name_from_path(file_path: &Path) -> String {
|
|
|
|
|
match file_path.file_name() {
|
2026-07-27 10:08:12 +00:00
|
|
|
Some(name) => name.to_string_lossy().to_string(),
|
|
|
|
|
None => String::from("unknown"),
|
2026-07-27 09:53:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 09:04:19 +00:00
|
|
|
pub fn match_pattern(
|
|
|
|
|
iter: impl Iterator<Item = std::io::Result<String>>,
|
|
|
|
|
pattern: &str,
|
2026-07-27 16:38:46 +00:00
|
|
|
file_path: &Path,
|
2026-07-27 09:53:58 +00:00
|
|
|
) -> anyhow::Result<Matches, anyhow::Error> {
|
2026-07-27 09:04:19 +00:00
|
|
|
let reg = Regex::new(pattern).with_context(|| format!("invalid regex: {:?}", pattern))?;
|
2026-07-27 09:53:58 +00:00
|
|
|
let mut matches = Vec::new();
|
|
|
|
|
|
|
|
|
|
for (idx, line) in iter.enumerate() {
|
|
|
|
|
let line = line?;
|
2026-07-27 09:04:19 +00:00
|
|
|
|
2026-07-27 09:53:58 +00:00
|
|
|
if reg.is_match(&line) {
|
|
|
|
|
let m = Match {
|
2026-07-27 10:03:14 +00:00
|
|
|
line_number: idx + 1,
|
2026-07-27 09:53:58 +00:00
|
|
|
text: line,
|
|
|
|
|
file_name: get_file_name_from_path(file_path),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
matches.push(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-27 09:04:19 +00:00
|
|
|
|
2026-07-27 09:53:58 +00:00
|
|
|
Ok(Matches { items: matches, pattern: String::from(pattern) })
|
2026-07-27 08:43:48 +00:00
|
|
|
}
|
2026-07-27 10:20:13 +00:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_get_file_name_from_path() {
|
2026-07-27 16:38:46 +00:00
|
|
|
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");
|
|
|
|
|
|
2026-07-27 16:38:46 +00:00
|
|
|
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())];
|
|
|
|
|
|
2026-07-27 16:38:46 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|