Ignore case and allowing the user to specify a path

Also ran rustfmt over the code.
This commit is contained in:
Cameron Cordes
2020-05-19 18:20:48 -04:00
parent 0dc8383d2e
commit 0daa2f4149
2 changed files with 72 additions and 35 deletions

View File

@@ -1,10 +1,11 @@
extern crate clap; extern crate clap;
use clap::{Arg, App}; use clap::{App, Arg};
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub pattern: String, pub pattern: String,
pub ignore_case: bool pub ignore_case: bool,
pub path: String,
} }
impl Config { impl Config {
@@ -12,19 +13,29 @@ impl Config {
let matches = App::new("Rack") let matches = App::new("Rack")
.version("0.1") .version("0.1")
.about("Like Ack but in Rust") .about("Like Ack but in Rust")
.arg(Arg::with_name("ignore case") .arg(
Arg::with_name("ignore case")
.short("i") .short("i")
.help("Ignore case when looking for matches")) .help("Ignore case when looking for matches"),
.arg(Arg::with_name("pattern") )
.arg(
Arg::with_name("pattern")
.help("The pattern you're looking for") .help("The pattern you're looking for")
.index(1) .index(1)
.required(true)) .required(true),
)
.arg(
Arg::with_name("path")
.help("Path to search in")
.index(2)
.default_value("."),
)
.get_matches(); .get_matches();
Config { Config {
pattern: String::from(matches.value_of("pattern").unwrap()), 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()),
} }
} }
} }

View File

@@ -1,5 +1,6 @@
use std::fs::{self, File};
use std::io; use std::io;
use std::fs::{self}; use std::io::prelude::*;
use std::path::Path; use std::path::Path;
extern crate term; extern crate term;
@@ -9,13 +10,20 @@ mod config;
fn main() { fn main() {
let config = config::Config::from_args(); 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)); let results = traverse_dir(&config, path, |config, path| check_file(config, path));
match results { match results {
Result::Ok(matches) if matches.len() == 0 => println!("No matches :("), Result::Ok(matches) if matches.len() == 0 => println!("No matches :("),
Result::Ok(matches) => print_report(&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)] #[derive(Debug)]
struct FileMatch { struct FileMatch {
file_name: String, file_name: String,
matches: Vec<Match> matches: Vec<Match>,
} }
impl FileMatch { impl FileMatch {
@@ -73,9 +81,15 @@ impl FileMatch {
let mut last_position = 0; let mut last_position = 0;
for (idx, match_index) in mat.match_indexes.iter().enumerate() { 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(); 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(); term.reset().unwrap();
if idx == mat.match_indexes.len() - 1 { if idx == mat.match_indexes.len() - 1 {
@@ -94,43 +108,55 @@ impl FileMatch {
struct Match { struct Match {
line_number: usize, line_number: usize,
match_indexes: Vec<MatchIndex>, match_indexes: Vec<MatchIndex>,
text: String text: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct MatchIndex { struct MatchIndex {
start: usize, start: usize,
end: usize end: usize,
} }
fn check_file(config: &config::Config, path: &Path) -> io::Result<FileMatch> { fn check_file(config: &config::Config, path: &Path) -> io::Result<FileMatch> {
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 file = File::open(&path)?;
let content = fs::read_to_string(&path)?; let file = io::BufReader::new(file);
let mut matches = Vec::new(); 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::<MatchIndex>::new(); let mut indexes = Vec::<MatchIndex>::new();
for m in line.trim().match_indices(pattern) { for m in line.trim().match_indices(&pattern) {
indexes.push(MatchIndex { start: m.0, end: m.0 + pattern.len() }); 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<FileMatch>) { fn print_report(matches: &Vec<FileMatch>) {
for file in matches { for file in matches {
file.print() file.print()
// println!("{}:", file.file_name);
// for mat in &file.matches {
// println!("{}", mat.text);
// }
// println!();
} }
} }