From 4ef70979bc0b0b51277edd0c08fd18aab2d0186a Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 13 Feb 2021 22:45:24 -0500 Subject: [PATCH] Move app to its own module Moved blocks into their own functions and made a small public api. --- src/main.rs | 255 +++++++++++++++++++++++++++------------------------- 1 file changed, 134 insertions(+), 121 deletions(-) diff --git a/src/main.rs b/src/main.rs index fb2ded5..53f0345 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,144 +1,157 @@ -use rack::config::Config; -use rack::filematch::*; -use std::fs::{self, DirEntry, File}; -use std::io; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; +use crate::rack::Rack; extern crate term; fn main() { - let config = Config::from_args(); - - let search_path = &config.path; - let path = if search_path.starts_with('/') { - PathBuf::from(search_path) - } else { - let mut path = String::from("./"); - path.push_str(search_path); - PathBuf::from(path) - }; - - 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), - } + let rack = Rack::new(); + rack.run(); } -fn traverse_dir( - config: &Config, - dir: &PathBuf, - gitignore_entries: Option>, - operation: fn(&Config, &Path) -> io::Result, -) -> io::Result> { - let mut matches: Vec = Vec::new(); - let entries = fs::read_dir(dir)?.collect::>>(); +mod rack { + use ::rack::config::Config; + use ::rack::filematch::*; + use std::fs::{self, DirEntry, File}; + use std::io; + use std::io::prelude::*; + use std::path::{Path, PathBuf}; - let gitignore: Option<&DirEntry> = entries - .iter() - .filter_map(|entry| entry.as_ref().ok()) - .find(|entry| entry.path().file_name().unwrap() == ".gitignore"); + pub struct Rack { + config: Config, + matches: Vec, + } - 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::>() + impl Rack { + pub fn new() -> Self { + Rack { + config: Config::from_args(), + matches: Vec::new(), + } } - 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 { + pub fn run(mut self) { + let config = &self.config; + + let search_path = &config.path; + let path = if search_path.starts_with('/') { + PathBuf::from(search_path) + } else { + let mut path = String::from("./"); + path.push_str(search_path); + PathBuf::from(path) + }; + + match self.traverse_dir(&path, Option::None) { + Result::Ok(_) if self.matches.is_empty() => println!("No matches :("), + Result::Ok(_) => self.print_report(), + Result::Err(msg) => println!("Error while parsing: {}", msg), + } + } + + fn traverse_dir( + &mut self, + dir: &PathBuf, + gitignore_entries: Option>, + ) -> io::Result<()> { + 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(); + let path = path.to_str().unwrap(); + if file_type.is_dir() { + if self.should_ignore(&gitignore, path, &entry) { + continue; + } + + // TODO: Should borrow gitignore instead of cloning.. + self.traverse_dir(&entry.path(), Some(gitignore.clone()))?; + } else { + if self.should_ignore(&gitignore, path, &entry) { + continue; + } + let result = self.check_file(&entry.path()); + match result { + Result::Ok(result) if !result.matches.is_empty() => self.matches.push(result), + _ => (), + } + } + } + + Ok(()) + } + + fn should_ignore(&self, gitignore: &Vec, path: &str, entry: &DirEntry) -> bool { + if self.config.use_gitignore { let should_ignore = 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; + return true; + } + } + false + } + + fn check_file(&self, path: &Path) -> io::Result { + let mut pattern = self.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 self.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, + }); } } - // 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; + Ok(FileMatch { + file_name: path.to_str().unwrap().trim_start_matches("./").to_string(), + matches, + }) + } - 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), - _ => (), + fn print_report(&self) { + for file in &self.matches { + file.print(&self.config) } } } - - 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) - } }