feat: add support for multiple files

refactor: replace String with PathBuf
This commit is contained in:
Mohammad Kanaan 2026-07-27 19:38:46 +03:00
parent 3f1a5c5ef3
commit ad6dc24d7e
3 changed files with 48 additions and 18 deletions

View file

@ -1,15 +1,17 @@
use std::process::ExitCode; use std::{path::PathBuf, process::ExitCode};
use clap::Parser; use clap::Parser;
use crate::matches::Matches; use crate::{matches::Matches, parser::process_files};
mod matches; mod matches;
mod parser; mod parser;
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
pattern: String, pattern: String,
file_path: String,
#[arg(value_name = "FILES", num_args = 1..)]
file_paths: Vec<PathBuf>,
} }
fn main() -> ExitCode { fn main() -> ExitCode {
@ -29,8 +31,9 @@ fn main() -> ExitCode {
fn run() -> anyhow::Result<Matches> { fn run() -> anyhow::Result<Matches> {
let cli = Cli::parse(); let cli = Cli::parse();
let lines_iter = parser::read_lines(&cli.file_path)?; // println!("{:?} {}", cli.file_paths, cli.pattern);
let matches = parser::match_pattern(lines_iter, &cli.pattern, &cli.file_path)?;
let matches = process_files(&cli.file_paths, &cli.pattern)?;
Ok(matches) Ok(matches)
} }

View file

@ -15,6 +15,10 @@ impl Matches {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.items.is_empty() self.items.is_empty()
} }
pub fn append(&mut self, matches: &mut Self) -> () {
self.items.append(&mut matches.items);
}
} }
impl Display for Match { impl Display for Match {

View file

@ -1,7 +1,7 @@
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
path::Path, path::{Path, PathBuf},
}; };
use anyhow::Context; use anyhow::Context;
@ -9,22 +9,44 @@ use regex::Regex;
use crate::matches::{Match, Matches}; use crate::matches::{Match, Matches};
pub fn read_lines( pub fn process_files(
file_path: &str, file_paths: &Vec<PathBuf>,
) -> anyhow::Result<impl Iterator<Item = std::io::Result<String>>, anyhow::Error> { pattern: &str,
if file_path.is_empty() { ) -> anyhow::Result<Matches, anyhow::Error> {
return Err(anyhow::anyhow!("File path cannot be empty")); 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 = let file =
File::open(file_path).with_context(|| format!("failed to open file: {file_path}"))?; File::open(file_path).with_context(|| format!("failed to open file: {path_string}"))?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
Ok(reader.lines()) Ok(reader.lines())
} }
fn get_file_name_from_path(file_path: &str) -> String { fn get_file_name_from_path(file_path: &Path) -> String {
match Path::new(file_path).file_name() { match file_path.file_name() {
Some(name) => name.to_string_lossy().to_string(), Some(name) => name.to_string_lossy().to_string(),
None => String::from("unknown"), None => String::from("unknown"),
} }
@ -33,7 +55,7 @@ fn get_file_name_from_path(file_path: &str) -> String {
pub fn match_pattern( pub fn match_pattern(
iter: impl Iterator<Item = std::io::Result<String>>, iter: impl Iterator<Item = std::io::Result<String>>,
pattern: &str, pattern: &str,
file_path: &str, file_path: &Path,
) -> anyhow::Result<Matches, anyhow::Error> { ) -> anyhow::Result<Matches, anyhow::Error> {
let reg = Regex::new(pattern).with_context(|| format!("invalid regex: {:?}", pattern))?; let reg = Regex::new(pattern).with_context(|| format!("invalid regex: {:?}", pattern))?;
let mut matches = Vec::new(); let mut matches = Vec::new();
@ -61,11 +83,11 @@ mod tests {
#[test] #[test]
fn test_get_file_name_from_path() { fn test_get_file_name_from_path() {
let path = "/path/to/file.txt"; let path = Path::new("/path/to/file.txt");
let file_name = get_file_name_from_path(path); let file_name = get_file_name_from_path(path);
assert_eq!(file_name, "file.txt"); assert_eq!(file_name, "file.txt");
let path = "file.txt"; let path = Path::new("file.txt");
let file_name = get_file_name_from_path(path); let file_name = get_file_name_from_path(path);
assert_eq!(file_name, "file.txt"); assert_eq!(file_name, "file.txt");
} }
@ -75,7 +97,8 @@ mod tests {
let input: Vec<std::io::Result<String>> = let input: Vec<std::io::Result<String>> =
vec![Ok("hola".into()), Ok("no".into()), Ok("hola again".into())]; vec![Ok("hola".into()), Ok("no".into()), Ok("hola again".into())];
let result = match_pattern(input.into_iter(), r"^hola", "/tmp/test.txt").unwrap(); let result =
match_pattern(input.into_iter(), r"^hola", Path::new("/tmp/test.txt")).unwrap();
assert_eq!(result.items.len(), 2); assert_eq!(result.items.len(), 2);