This is my first Rust project (I'm primarily a C guy). I want to know if my code is properly idiomatic (Rusty?). Also, are there any inefficiencies?
The code defines an iterator (Searcher
) that takes a regex, a starting directory, and an optional maximum search depth. It generates locations of regex matches within the file system.
use std::fs; use std::io; use std::io::BufRead; use std::iter; use std::path; use std::vec::Vec; use regex; pub struct Location { pub file: path::PathBuf, pub line: usize, pub text: String, } struct FileScanner<'a> { path: path::PathBuf, pattern: &'a regex::Regex, lines: iter::Enumerate<io::Lines<io::BufReader<fs::File>>>, } fn is_line_printable(line: &str) -> bool { line.chars() .all(|c| c.is_ascii_graphic() || c.is_whitespace()) } impl<'a> FileScanner<'a> { fn build(path: path::PathBuf, pattern: &'a regex::Regex) -> Option<Self> { let handle = match fs::File::open(&path) { Ok(h) => h, Err(_) => return None, }; let reader = io::BufReader::new(handle); Some(FileScanner { path, pattern, lines: reader.lines().into_iter().enumerate(), }) } } impl<'a> Iterator for FileScanner<'a> { type Item = Location; fn next(&mut self) -> Option<Location> { loop { let (index, line) = match self.lines.next() { Some((i, Ok(l))) => (i, l), _ => return None, }; if !is_line_printable(&line) { return None; } let pattern_match = match self.pattern.find(&line) { Some(m) => m, None => continue, }; let start = pattern_match.start(); let end = pattern_match.end(); return Some(Location { file: self.path.to_path_buf(), line: index + 1, text: String::from(&line[start..end]), }); } } } pub struct Searcher<'a> { pattern: &'a regex::Regex, max_depth: Option<u8>, readers: Vec<fs::ReadDir>, current_scanner: Option<FileScanner<'a>>, } impl<'a> Searcher<'a> { pub fn build( pattern: &'a regex::Regex, directory: &'a path::Path, depth: Option<u8>, ) -> Result<Self, String> { match depth { Some(0) => return Err(String::from("Depth cannot be 0")), _ => (), }; let reader = match fs::read_dir(directory) { Ok(r) => r, Err(error) => return Err(error.to_string()), }; let readers = vec![reader]; Ok(Searcher { pattern, max_depth: depth, readers, current_scanner: None, }) } fn push_directory(&mut self, directory: path::PathBuf) { match self.max_depth { Some(depth) if usize::from(depth) == self.readers.len() => return, _ => (), } let reader = match fs::read_dir(directory) { Ok(r) => r, Err(_) => return, }; self.readers.push(reader); } fn next_match_from_file(&mut self) -> Option<Location> { let scanner = match &mut self.current_scanner { Some(s) => s, None => return None, }; let location = scanner.next(); if location.is_none() { self.current_scanner = None; } location } } impl<'a> Iterator for Searcher<'a> { type Item = Location; fn next(&mut self) -> Option<Location> { let location = self.next_match_from_file(); if location.is_some() { return location; } while self.readers.len() > 0 { let current_reader = self.readers.last_mut().unwrap(); let entry = match current_reader.next() { Some(Ok(ent)) => ent, Some(Err(_)) | None => { self.readers.pop(); continue; } }; let path = entry.path(); if path.is_dir() { self.push_directory(path); continue; } self.current_scanner = FileScanner::build(path, self.pattern); let location = self.next_match_from_file(); if location.is_some() { return location; } } None } }