From 0daa2f4149b721d15e43d0bba67f413eeeedf73a Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Tue, 19 May 2020 18:20:48 -0400 Subject: [PATCH] Ignore case and allowing the user to specify a path Also ran rustfmt over the code. --- src/config.rs | 33 +++++++++++++++-------- src/main.rs | 74 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2144d92..a70f261 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,11 @@ extern crate clap; -use clap::{Arg, App}; +use clap::{App, Arg}; #[derive(Debug)] pub struct Config { pub pattern: String, - pub ignore_case: bool + pub ignore_case: bool, + pub path: String, } impl Config { @@ -12,19 +13,29 @@ impl Config { let matches = App::new("Rack") .version("0.1") .about("Like Ack but in Rust") - .arg(Arg::with_name("ignore case") - .short("i") - .help("Ignore case when looking for matches")) - .arg(Arg::with_name("pattern") - .help("The pattern you're looking for") - .index(1) - .required(true)) + .arg( + Arg::with_name("ignore case") + .short("i") + .help("Ignore case when looking for matches"), + ) + .arg( + Arg::with_name("pattern") + .help("The pattern you're looking for") + .index(1) + .required(true), + ) + .arg( + Arg::with_name("path") + .help("Path to search in") + .index(2) + .default_value("."), + ) .get_matches(); Config { pattern: String::from(matches.value_of("pattern").unwrap()), - ignore_case: matches.is_present("ignore case") + ignore_case: matches.is_present("ignore case"), + path: String::from(matches.value_of("path").unwrap()), } } } - diff --git a/src/main.rs b/src/main.rs index 076a4a1..b2925a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ +use std::fs::{self, File}; use std::io; -use std::fs::{self}; +use std::io::prelude::*; use std::path::Path; extern crate term; @@ -9,13 +10,20 @@ mod config; fn main() { let config = config::Config::from_args(); - let path = Path::new("."); + let search_path = &config.path; + let path = match search_path.starts_with('/') { + true => Path::new(search_path), + false => { + let p: &'static str = Box::leak(Box::new(String::from("./") + search_path)); + Path::new(p) + } + }; let results = traverse_dir(&config, path, |config, path| check_file(config, path)); match results { Result::Ok(matches) if matches.len() == 0 => println!("No matches :("), Result::Ok(matches) => print_report(&matches), - Result::Err(msg) => println!("Error while parsing: {}", msg) + Result::Err(msg) => println!("Error while parsing: {}", msg), } } @@ -55,7 +63,7 @@ fn traverse_dir( #[derive(Debug)] struct FileMatch { file_name: String, - matches: Vec + matches: Vec, } impl FileMatch { @@ -73,12 +81,18 @@ impl FileMatch { let mut last_position = 0; for (idx, match_index) in mat.match_indexes.iter().enumerate() { - print!("{}", mat.text.get(last_position..match_index.start).unwrap()); + print!( + "{}", + mat.text.get(last_position..match_index.start).unwrap() + ); term.fg(term::color::BRIGHT_RED).unwrap(); - print!("{}", mat.text.get(match_index.start..match_index.end).unwrap()); + print!( + "{}", + mat.text.get(match_index.start..match_index.end).unwrap() + ); term.reset().unwrap(); - if idx == mat.match_indexes.len() -1 { + if idx == mat.match_indexes.len() - 1 { println!("{}", mat.text.get(match_index.end..mat.text.len()).unwrap()); last_position = 0; } else { @@ -94,43 +108,55 @@ impl FileMatch { struct Match { line_number: usize, match_indexes: Vec, - text: String + text: String, } #[derive(Debug, Clone)] struct MatchIndex { start: usize, - end: usize + end: usize, } fn check_file(config: &config::Config, path: &Path) -> io::Result { - let pattern = &config.pattern; + let mut pattern = config.pattern.clone(); - // TODO: Use BufRead and read_line instead of reading the entire file into memory. - let content = fs::read_to_string(&path)?; + let file = File::open(&path)?; + let file = io::BufReader::new(file); let mut matches = Vec::new(); - for (number, line) in content.lines().enumerate() { - if line.contains(pattern) { + + for (number, line) in file.lines().enumerate() { + let mut line = line?; + let orig_line = line.clone(); + if config.ignore_case { + line = line.to_lowercase(); + pattern = pattern.to_lowercase(); + } + + if line.contains(&pattern) { let mut indexes = Vec::::new(); - for m in line.trim().match_indices(pattern) { - indexes.push(MatchIndex { start: m.0, end: m.0 + pattern.len() }); + for m in line.trim().match_indices(&pattern) { + indexes.push(MatchIndex { + start: m.0, + end: m.0 + pattern.len(), + }); } - matches.push(Match { line_number: number + 1, text: line.trim().to_string(), match_indexes: indexes }); + matches.push(Match { + line_number: number + 1, + text: orig_line.trim().to_string(), + match_indexes: indexes, + }); } } - Ok(FileMatch { file_name: path.to_str().unwrap().to_string(), matches: matches }) + Ok(FileMatch { + file_name: path.to_str().unwrap().trim_start_matches("./").to_string(), + matches: matches, + }) } fn print_report(matches: &Vec) { for file in matches { file.print() - // println!("{}:", file.file_name); - // for mat in &file.matches { - // println!("{}", mat.text); - // } - // println!(); } } -