watchexec/src/notification_filter.rs

155 lines
4.4 KiB
Rust
Raw Normal View History

2016-09-21 23:02:20 +02:00
extern crate glob;
2016-10-12 04:43:53 +02:00
use gitignore;
2016-09-27 15:43:28 +02:00
use std::io;
use std::path::{Path, PathBuf};
2016-09-21 23:02:20 +02:00
use self::glob::{Pattern, PatternError};
2016-09-21 23:02:20 +02:00
2016-09-23 21:52:50 +02:00
pub struct NotificationFilter {
2016-09-21 23:02:20 +02:00
filters: Vec<Pattern>,
2016-10-12 04:43:53 +02:00
ignores: Vec<Pattern>,
ignore_file: Option<gitignore::PatternSet>,
2016-09-21 23:02:20 +02:00
}
2016-09-27 15:43:28 +02:00
#[derive(Debug)]
2016-10-27 14:27:16 +02:00
pub enum Error {
2016-09-27 15:43:28 +02:00
BadPattern(PatternError),
Io(io::Error),
2016-09-27 15:43:28 +02:00
}
2016-09-23 21:52:50 +02:00
impl NotificationFilter {
pub fn new(current_dir: &Path,
2016-10-27 14:27:16 +02:00
filters: Vec<String>,
ignores: Vec<String>,
ignore_file: Option<gitignore::PatternSet>)
2016-10-27 14:27:16 +02:00
-> Result<NotificationFilter, Error> {
let cwd = try!(current_dir.canonicalize());
2016-09-27 15:43:28 +02:00
2016-10-27 14:27:16 +02:00
let compiled_filters = try!(filters.iter()
2016-10-28 14:46:35 +02:00
.map(|p| NotificationFilter::pattern_for(&cwd, p))
.collect());
let compiled_ignores = try!(ignores.iter()
.map(|p| NotificationFilter::pattern_for(&cwd, p))
.collect());
2016-10-14 01:47:04 +02:00
2016-10-27 14:27:16 +02:00
for compiled_filter in &compiled_filters {
debug!("Adding filter: {}", compiled_filter);
}
2016-09-21 23:02:20 +02:00
2016-10-27 14:27:16 +02:00
for compiled_ignore in &compiled_ignores {
debug!("Adding ignore: {}", compiled_ignore);
}
2016-10-14 01:47:04 +02:00
2016-10-27 14:27:16 +02:00
Ok(NotificationFilter {
filters: compiled_filters,
ignores: compiled_ignores,
ignore_file: ignore_file,
})
2016-09-21 23:02:20 +02:00
}
2016-10-27 14:27:16 +02:00
fn pattern_for(cwd: &PathBuf, p: &str) -> Result<Pattern, PatternError> {
2016-09-21 23:02:20 +02:00
let mut path = PathBuf::from(p);
if path.is_relative() {
2016-10-27 14:27:16 +02:00
path = cwd.join(path.as_path());
2016-09-21 23:02:20 +02:00
}
if let Ok(metadata) = path.metadata() {
if metadata.is_dir() {
path = path.join("*");
}
}
Pattern::new(path.to_str().unwrap())
}
pub fn is_excluded(&self, path: &Path) -> bool {
let path_as_str = path.to_str().unwrap();
for pattern in &self.ignores {
if pattern.matches(path_as_str) {
debug!("Ignoring {:?}: matched ignore filter", path);
2016-09-21 23:02:20 +02:00
return true;
}
}
for pattern in &self.filters {
if pattern.matches(path_as_str) {
return false;
}
}
2016-10-12 04:43:53 +02:00
if let Some(ref ignore_file) = self.ignore_file {
if ignore_file.is_excluded(path) {
debug!("Ignoring {:?}: matched gitignore file", path);
2016-10-12 04:43:53 +02:00
return true;
}
}
2016-10-18 15:39:40 +02:00
if !self.filters.is_empty() {
debug!("Ignoring {:?}: did not match any given filters", path);
}
2016-10-18 15:39:40 +02:00
!self.filters.is_empty()
2016-09-21 23:02:20 +02:00
}
}
2016-10-14 01:47:04 +02:00
2016-10-27 14:27:16 +02:00
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
2016-10-14 01:47:04 +02:00
}
}
2016-10-27 14:27:16 +02:00
impl From<PatternError> for Error {
fn from(err: PatternError) -> Error {
Error::BadPattern(err)
2016-10-14 01:47:04 +02:00
}
}
2016-10-29 15:37:50 +02:00
#[cfg(test)]
mod tests {
use super::NotificationFilter;
use std::path::Path;
#[test]
fn test_allows_everything_by_default() {
let filter = NotificationFilter::new(&Path::new("."), vec![], vec![], None).unwrap();
assert!(!filter.is_excluded(&Path::new("foo")));
}
#[test]
fn test_multiple_filters() {
let filters = vec![String::from("*.rs"), String::from("*.toml")];
let filter = NotificationFilter::new(&Path::new("."), filters, vec![], None).unwrap();
let cwd = Path::new(".").canonicalize().unwrap();
assert!(!filter.is_excluded(&cwd.join("hello.rs")));
assert!(!filter.is_excluded(&cwd.join("Cargo.toml")));
assert!(filter.is_excluded(&cwd.join("README.md")));
}
#[test]
fn test_multiple_ignores() {
let ignores = vec![String::from("*.rs"), String::from("*.toml")];
let filter = NotificationFilter::new(&Path::new("."), vec![], ignores, None).unwrap();
let cwd = Path::new(".").canonicalize().unwrap();
assert!(filter.is_excluded(&cwd.join("hello.rs")));
assert!(filter.is_excluded(&cwd.join("Cargo.toml")));
assert!(!filter.is_excluded(&cwd.join("README.md")));
}
#[test]
fn test_ignores_take_precedence() {
let ignores = vec![String::from("*.rs"), String::from("*.toml")];
let filter = NotificationFilter::new(&Path::new("."), ignores.clone(), ignores, None).unwrap();
let cwd = Path::new(".").canonicalize().unwrap();
assert!(filter.is_excluded(&cwd.join("hello.rs")));
assert!(filter.is_excluded(&cwd.join("Cargo.toml")));
assert!(filter.is_excluded(&cwd.join("README.md")));
}
}