use rack::config::Config; use rack::filematch::*; use std::fs::{self, DirEntry, File}; use std::io; use std::io::prelude::*; use std::path::Path; extern crate term; fn main() { let config = Config::from_args(); let search_path = &config.path; let path = if search_path.starts_with('/') { Path::new(search_path) } else { let p: &'static str = Box::leak(Box::new(String::from("./") + search_path)); Path::new(p) }; let results = traverse_dir(&config, path, Option::None, |config, path| { check_file(config, path) }); match results { Result::Ok(matches) if matches.is_empty() => println!("No matches :("), Result::Ok(matches) => print_report(&config, &matches), Result::Err(msg) => println!("Error while parsing: {}", msg), } } fn traverse_dir( config: &Config, dir: &Path, gitignore_entries: Option>, operation: fn(&Config, &Path) -> io::Result, ) -> io::Result> { let mut matches: Vec = Vec::new(); let entries = fs::read_dir(dir)?.collect::>>(); let gitignore: Option<&DirEntry> = entries .iter() .filter_map(|entry| entry.as_ref().ok()) .find(|entry| entry.path().file_name().unwrap() == ".gitignore"); let gitignore = match gitignore { Some(g) => { let file = io::BufReader::new(File::open(g.path())?); file.lines() .map(|line| line.unwrap().trim().to_owned()) .filter(|line| !line.is_empty() && !line.starts_with('#')) .collect::>() } None => gitignore_entries.unwrap_or_default(), }; for entry in entries { let entry = entry?; let file_type = entry.file_type()?; let path = entry.path().to_str().unwrap().to_owned(); if file_type.is_dir() { if config.use_gitignore { let should_ignore = gitignore.iter().find(|&line| path.contains(&line.clone())); if should_ignore.unwrap_or(&String::from("")).len() > 0 || entry.path().to_str().unwrap().contains("/.git") { continue; } } // TODO: Should borrow gitignore instead of cloning.. let mut results = traverse_dir(&config, &entry.path(), Some(gitignore.clone()), operation)?; let has_results = results .iter() .filter(|mat| !mat.matches.is_empty()) .count() > 0; if has_results { matches.append(&mut results); } } else { if config.use_gitignore { let should_ignore: Option<&String> = gitignore.iter().find(|&line| path.contains(&line.clone())); if !should_ignore.unwrap_or(&String::from("")).is_empty() || entry.path().to_str().unwrap().contains("/.git") { continue; } } let result = operation(&config, &entry.path()); match result { Result::Ok(result) if !result.matches.is_empty() => matches.push(result), _ => (), } } } Ok(matches) } fn check_file(config: &Config, path: &Path) -> io::Result { let mut pattern = config.pattern.clone(); let file = File::open(&path)?; let file = io::BufReader::new(file); let mut matches = Vec::new(); 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(), }); } matches.push(Match { line_number: number + 1, text: orig_line.trim().to_string(), match_indexes: indexes, }); } } Ok(FileMatch { file_name: path.to_str().unwrap().trim_start_matches("./").to_string(), matches, }) } fn print_report(config: &Config, matches: &[FileMatch]) { for file in matches { file.print(config) } }