Compare commits

..

No commits in common. "de79955778b0e94b335653271cf245857953e9ae" and "c7002aef34524b91583b24f0df0f8be89a12fc08" have entirely different histories.

5 changed files with 44 additions and 144 deletions

View file

@ -1,23 +0,0 @@
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser)]
pub struct Cli {
pub(crate) pattern: String,
#[arg(value_name = "FILES", num_args = 1..)]
pub(crate) file_paths: Vec<PathBuf>,
#[arg(short, long)]
pub(crate) ignore_case: bool,
}
pub struct CliOptions {
pub ignore_case: bool,
}
impl Cli {
pub fn get_options(&self) -> CliOptions {
CliOptions { ignore_case: self.ignore_case }
}
}

View file

@ -1,35 +1,23 @@
use std::process::ExitCode; use clap::Parser;
use crate::{cli::Cli, matches::Matches, parser::process_files};
mod cli;
mod matches; mod matches;
mod parser; mod parser;
use clap::Parser; #[derive(Parser)]
struct Cli {
fn main() -> ExitCode { pattern: String,
match run() { file_path: String,
Ok(matches) if matches.is_empty() => ExitCode::FAILURE,
Ok(matches) => {
println!("{}", matches);
ExitCode::SUCCESS
}
Err(error) => {
eprintln!("{}", error);
ExitCode::from(2)
}
}
} }
fn run() -> anyhow::Result<Matches> { fn main() -> anyhow::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
// println!("{:?} {}", cli.file_paths, cli.pattern); let lines_iter = parser::read_lines(&cli.file_path)?;
let matches = parser::match_pattern(lines_iter, &cli.pattern, &cli.file_path);
let matches = process_files(&cli.file_paths, &cli.pattern, &cli.get_options())?; println!("Pattern: {}", cli.pattern);
if !matches.is_empty() { println!("File path: {}", cli.file_path);
println!("Matches for pattern {}:", cli.pattern); println!("---");
} println!("{}", matches?);
Ok(matches) Ok(())
} }

View file

@ -1,33 +1,26 @@
use std::{fmt::Display, path::PathBuf}; use std::fmt::Display;
pub struct Match { pub struct Match {
pub file_path: PathBuf, pub file_name: String,
pub line_number: usize, pub line_number: usize,
pub text: String, pub text: String,
} }
pub struct Matches { pub struct Matches {
pub items: Vec<Match>, pub items: Vec<Match>,
} pub pattern: String,
impl Matches {
pub fn is_empty(&self) -> bool {
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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: line {}: {}", self.file_path.display(), self.line_number, self.text) write!(f, "{}: line {}: {}", self.file_name, self.line_number, self.text)
} }
} }
impl Display for Matches { impl Display for Matches {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Matches for pattern {}:", self.pattern)?;
for (idx, item) in self.items.iter().enumerate() { for (idx, item) in self.items.iter().enumerate() {
if idx > 0 { if idx > 0 {
writeln!(f)?; writeln!(f)?;

View file

@ -1,112 +1,56 @@
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
path::{Path, PathBuf}, path::Path,
}; };
use anyhow::Context; use anyhow::Context;
use regex::{Regex, RegexBuilder}; use regex::Regex;
use crate::{ use crate::matches::{Match, Matches};
cli::CliOptions,
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![] };
// fails and stops everything when one file fails
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( pub fn read_lines(
file_path: &Path, file_path: &str,
) -> anyhow::Result<impl Iterator<Item = std::io::Result<String>>, anyhow::Error> { ) -> anyhow::Result<impl Iterator<Item = std::io::Result<String>>, anyhow::Error> {
let file = File::open(file_path) if file_path.is_empty() {
.with_context(|| format!("failed to open file: {}", file_path.display()))?; 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); let reader = BufReader::new(file);
Ok(reader.lines()) Ok(reader.lines())
} }
// fn get_file_name_from_path(file_path: &Path) -> String { fn get_file_name_from_path(file_path: &str) -> String {
// match file_path.file_name() { match Path::new(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"),
// } }
// } }
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: &Path, file_path: &str,
regex: &Regex,
) -> anyhow::Result<Matches, anyhow::Error> { ) -> anyhow::Result<Matches, anyhow::Error> {
let reg = Regex::new(pattern).with_context(|| format!("invalid regex: {:?}", pattern))?;
let mut matches = Vec::new(); let mut matches = Vec::new();
for (idx, line) in iter.enumerate() { for (idx, line) in iter.enumerate() {
let line = line?; let line = line?;
if regex.is_match(&line) { if reg.is_match(&line) {
let m = Match { line_number: idx + 1, text: line, file_path: PathBuf::from(file_path) }; let m = Match {
line_number: idx + 1,
text: line,
file_name: get_file_name_from_path(file_path),
};
matches.push(m); matches.push(m);
} }
} }
Ok(Matches { items: matches }) 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::new("/path/to/file.txt");
let file_name = get_file_name_from_path(path);
assert_eq!(file_name, "file.txt");
let path = Path::new("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<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();
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);
}
} }

View file

@ -1,4 +1,2 @@
hola hola
no no
hola again
yep it's me