Move app to its own module
Moved blocks into their own functions and made a small public api.
This commit is contained in:
255
src/main.rs
255
src/main.rs
@@ -1,144 +1,157 @@
|
|||||||
use rack::config::Config;
|
use crate::rack::Rack;
|
||||||
use rack::filematch::*;
|
|
||||||
use std::fs::{self, DirEntry, File};
|
|
||||||
use std::io;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
extern crate term;
|
extern crate term;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let config = Config::from_args();
|
let rack = Rack::new();
|
||||||
|
rack.run();
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn traverse_dir(
|
mod rack {
|
||||||
config: &Config,
|
use ::rack::config::Config;
|
||||||
dir: &PathBuf,
|
use ::rack::filematch::*;
|
||||||
gitignore_entries: Option<Vec<String>>,
|
use std::fs::{self, DirEntry, File};
|
||||||
operation: fn(&Config, &Path) -> io::Result<FileMatch>,
|
use std::io;
|
||||||
) -> io::Result<Vec<FileMatch>> {
|
use std::io::prelude::*;
|
||||||
let mut matches: Vec<FileMatch> = Vec::new();
|
use std::path::{Path, PathBuf};
|
||||||
let entries = fs::read_dir(dir)?.collect::<Vec<io::Result<DirEntry>>>();
|
|
||||||
|
|
||||||
let gitignore: Option<&DirEntry> = entries
|
pub struct Rack {
|
||||||
.iter()
|
config: Config,
|
||||||
.filter_map(|entry| entry.as_ref().ok())
|
matches: Vec<FileMatch>,
|
||||||
.find(|entry| entry.path().file_name().unwrap() == ".gitignore");
|
}
|
||||||
|
|
||||||
let gitignore = match gitignore {
|
impl Rack {
|
||||||
Some(g) => {
|
pub fn new() -> Self {
|
||||||
let file = io::BufReader::new(File::open(g.path())?);
|
Rack {
|
||||||
file.lines()
|
config: Config::from_args(),
|
||||||
.map(|line| line.unwrap().trim().to_owned())
|
matches: Vec::new(),
|
||||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
}
|
||||||
.collect::<Vec<String>>()
|
|
||||||
}
|
}
|
||||||
None => gitignore_entries.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
pub fn run(mut self) {
|
||||||
let entry = entry?;
|
let config = &self.config;
|
||||||
let file_type = entry.file_type()?;
|
|
||||||
let path = entry.path().to_str().unwrap().to_owned();
|
let search_path = &config.path;
|
||||||
if file_type.is_dir() {
|
let path = if search_path.starts_with('/') {
|
||||||
if config.use_gitignore {
|
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<Vec<String>>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let entries = fs::read_dir(dir)?.collect::<Vec<io::Result<DirEntry>>>();
|
||||||
|
|
||||||
|
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::<Vec<String>>()
|
||||||
|
}
|
||||||
|
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<String>, path: &str, entry: &DirEntry) -> bool {
|
||||||
|
if self.config.use_gitignore {
|
||||||
let should_ignore = gitignore.iter().find(|&line| path.contains(&line.clone()));
|
let should_ignore = gitignore.iter().find(|&line| path.contains(&line.clone()));
|
||||||
|
|
||||||
if !should_ignore.unwrap_or(&String::from("")).is_empty()
|
if !should_ignore.unwrap_or(&String::from("")).is_empty()
|
||||||
|| entry.path().to_str().unwrap().contains("/.git")
|
|| entry.path().to_str().unwrap().contains("/.git")
|
||||||
{
|
{
|
||||||
continue;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_file(&self, path: &Path) -> io::Result<FileMatch> {
|
||||||
|
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::<MatchIndex>::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..
|
Ok(FileMatch {
|
||||||
let mut results =
|
file_name: path.to_str().unwrap().trim_start_matches("./").to_string(),
|
||||||
traverse_dir(&config, &entry.path(), Some(gitignore.clone()), operation)?;
|
matches,
|
||||||
let has_results = results.iter().filter(|mat| !mat.matches.is_empty()).count() > 0;
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if has_results {
|
fn print_report(&self) {
|
||||||
matches.append(&mut results);
|
for file in &self.matches {
|
||||||
}
|
file.print(&self.config)
|
||||||
} 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<FileMatch> {
|
|
||||||
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::<MatchIndex>::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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user