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
}
}