Formatting

This commit is contained in:
Félix Saparelli 2018-09-08 20:08:36 +12:00
parent 9c60148b66
commit a6163cc599
10 changed files with 263 additions and 181 deletions

View File

@ -135,24 +135,26 @@ pub fn get_args() -> Args {
if let Some(extensions) = args.values_of("extensions") { if let Some(extensions) = args.values_of("extensions") {
for exts in extensions { for exts in extensions {
filters.extend(exts.split(',') filters.extend(
.filter(|ext| !ext.is_empty()) exts.split(',')
.map(|ext| format!("*.{}", ext.replace(".", "")))); .filter(|ext| !ext.is_empty())
.map(|ext| format!("*.{}", ext.replace(".", ""))),
);
} }
} }
let mut ignores = vec![]; let mut ignores = vec![];
let default_ignores = vec![format!("**{}.DS_Store", MAIN_SEPARATOR), let default_ignores = vec![
String::from("*.py[co]"), format!("**{}.DS_Store", MAIN_SEPARATOR),
String::from("#*#"), String::from("*.py[co]"),
String::from(".#*"), String::from("#*#"),
String::from(".*.sw?"), String::from(".#*"),
String::from(".*.sw?x"), String::from(".*.sw?"),
format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), String::from(".*.sw?x"),
format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR)]; format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
];
if args.occurrences_of("no-default-ignore") == 0 { if args.occurrences_of("no-default-ignore") == 0 {
ignores.extend(default_ignores) ignores.extend(default_ignores)
@ -167,9 +169,9 @@ pub fn get_args() -> Args {
let debounce = if args.occurrences_of("debounce") > 0 { let debounce = if args.occurrences_of("debounce") > 0 {
value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit()) value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit())
} else { } else {
500 500
}; };
if signal.is_some() && args.is_present("postpone") { if signal.is_some() && args.is_present("postpone") {
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though // TODO: Error::argument_conflict() might be the better fit, usage was unclear, though

View File

@ -88,7 +88,8 @@ impl Gitignore {
} }
pub fn is_excluded(&self, path: &Path) -> bool { pub fn is_excluded(&self, path: &Path) -> bool {
let mut applicable_files: Vec<&GitignoreFile> = self.files let mut applicable_files: Vec<&GitignoreFile> = self
.files
.iter() .iter()
.filter(|f| path.starts_with(&f.root)) .filter(|f| path.starts_with(&f.root))
.collect(); .collect();
@ -144,11 +145,10 @@ impl GitignoreFile {
} }
Ok(GitignoreFile { Ok(GitignoreFile {
set: try!(builder.build()), set: try!(builder.build()),
patterns: patterns, patterns: patterns,
root: root.to_owned(), root: root.to_owned(),
}) })
} }
#[cfg(test)] #[cfg(test)]
@ -167,9 +167,9 @@ impl GitignoreFile {
for &i in matches.iter().rev() { for &i in matches.iter().rev() {
let pattern = &self.patterns[i]; let pattern = &self.patterns[i];
return match pattern.pattern_type { return match pattern.pattern_type {
PatternType::Whitelist => MatchResult::Whitelist, PatternType::Whitelist => MatchResult::Whitelist,
PatternType::Ignore => MatchResult::Ignore, PatternType::Ignore => MatchResult::Ignore,
}; };
} }
MatchResult::None MatchResult::None
@ -223,7 +223,6 @@ impl Pattern {
} }
} }
impl From<globset::Error> for Error { impl From<globset::Error> for Error {
fn from(error: globset::Error) -> Error { fn from(error: globset::Error) -> Error {
Error::GlobSet(error) Error::GlobSet(error)
@ -236,7 +235,6 @@ impl From<io::Error> for Error {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::GitignoreFile; use super::GitignoreFile;

View File

@ -1,19 +1,19 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
extern crate globset;
extern crate env_logger; extern crate env_logger;
extern crate globset;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate notify; extern crate notify;
#[cfg(windows)]
extern crate kernel32;
#[cfg(unix)] #[cfg(unix)]
extern crate nix; extern crate nix;
#[cfg(windows)] #[cfg(windows)]
extern crate winapi; extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(test)] #[cfg(test)]
extern crate mktemp; extern crate mktemp;
@ -21,10 +21,10 @@ extern crate mktemp;
pub mod cli; pub mod cli;
mod gitignore; mod gitignore;
mod notification_filter; mod notification_filter;
mod pathop;
mod process; mod process;
pub mod run; pub mod run;
mod signal; mod signal;
mod watcher; mod watcher;
mod pathop;
pub use run::run; pub use run::run;

View File

@ -1,7 +1,6 @@
extern crate watchexec; extern crate watchexec;
use watchexec::{cli, run}; use watchexec::{cli, run};
use std::error::Error;
fn main() -> Result<(), Box<Error>> { fn main() -> run::Result<()> {
run(cli::get_args()) run(cli::get_args())
} }

View File

@ -22,10 +22,11 @@ pub enum Error {
} }
impl NotificationFilter { impl NotificationFilter {
pub fn new(filters: Vec<String>, pub fn new(
ignores: Vec<String>, filters: Vec<String>,
ignore_files: gitignore::Gitignore) ignores: Vec<String>,
-> Result<NotificationFilter, Error> { ignore_files: gitignore::Gitignore,
) -> Result<NotificationFilter, Error> {
let mut filter_set_builder = GlobSetBuilder::new(); let mut filter_set_builder = GlobSetBuilder::new();
for f in &filters { for f in &filters {
filter_set_builder.add(try!(Glob::new(f))); filter_set_builder.add(try!(Glob::new(f)));
@ -49,11 +50,11 @@ impl NotificationFilter {
let ignore_set = try!(ignore_set_builder.build()); let ignore_set = try!(ignore_set_builder.build());
Ok(NotificationFilter { Ok(NotificationFilter {
filters: filter_set, filters: filter_set,
filter_count: filters.len(), filter_count: filters.len(),
ignores: ignore_set, ignores: ignore_set,
ignore_files: ignore_files, ignore_files: ignore_files,
}) })
} }
pub fn is_excluded(&self, path: &Path) -> bool { pub fn is_excluded(&self, path: &Path) -> bool {
@ -93,8 +94,8 @@ impl From<globset::Error> for Error {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gitignore;
use super::NotificationFilter; use super::NotificationFilter;
use gitignore;
use std::path::Path; use std::path::Path;
#[test] #[test]
@ -106,7 +107,11 @@ mod tests {
#[test] #[test]
fn test_filename() { fn test_filename() {
let filter = NotificationFilter::new(vec![], vec![String::from("test.json")], gitignore::load(&vec![])).unwrap(); let filter = NotificationFilter::new(
vec![],
vec![String::from("test.json")],
gitignore::load(&vec![]),
).unwrap();
assert!(filter.is_excluded(&Path::new("/path/to/test.json"))); assert!(filter.is_excluded(&Path::new("/path/to/test.json")));
assert!(filter.is_excluded(&Path::new("test.json"))); assert!(filter.is_excluded(&Path::new("test.json")));
@ -135,8 +140,8 @@ mod tests {
#[test] #[test]
fn test_ignores_take_precedence() { fn test_ignores_take_precedence() {
let ignores = vec![String::from("*.rs"), String::from("*.toml")]; let ignores = vec![String::from("*.rs"), String::from("*.toml")];
let filter = NotificationFilter::new(ignores.clone(), ignores, gitignore::load(&vec![])) let filter =
.unwrap(); NotificationFilter::new(ignores.clone(), ignores, gitignore::load(&vec![])).unwrap();
assert!(filter.is_excluded(&Path::new("hello.rs"))); assert!(filter.is_excluded(&Path::new("hello.rs")));
assert!(filter.is_excluded(&Path::new("Cargo.toml"))); assert!(filter.is_excluded(&Path::new("Cargo.toml")));

View File

@ -1,7 +1,6 @@
use notify::op; use notify::op;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Info about a path and its corresponding `notify` event /// Info about a path and its corresponding `notify` event
#[derive(Debug, Clone, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct PathOp { pub struct PathOp {
@ -41,4 +40,3 @@ impl PathOp {
op_.contains(op::CHMOD) op_.contains(op::CHMOD)
} }
} }

View File

@ -1,6 +1,6 @@
use std::path::PathBuf;
use std::collections::{HashMap, HashSet};
use pathop::PathOp; use pathop::PathOp;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
pub fn spawn(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Process { pub fn spawn(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Process {
self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process") self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process")
@ -11,48 +11,56 @@ pub use self::imp::Process;
fn needs_wrapping(s: &String) -> bool { fn needs_wrapping(s: &String) -> bool {
s.contains(|ch| match ch { s.contains(|ch| match ch {
' ' | '\t' | '\'' | '"' => true, ' ' | '\t' | '\'' | '"' => true,
_ => false _ => false,
}) })
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
fn wrap_in_quotes(s: &String) -> String { fn wrap_in_quotes(s: &String) -> String {
format!("'{}'", if s.contains('\'') { format!(
s.replace('\'', "'\"'\"'") "'{}'",
} else { if s.contains('\'') {
s.clone() s.replace('\'', "'\"'\"'")
}) } else {
s.clone()
}
)
} }
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
fn wrap_in_quotes(s: &String) -> String { fn wrap_in_quotes(s: &String) -> String {
format!("\"{}\"", if s.contains('"') { format!(
s.replace('"', "\"\"") "\"{}\"",
} else { if s.contains('"') {
s.clone() s.replace('"', "\"\"")
}) } else {
s.clone()
}
)
} }
fn wrap_commands(cmd: &Vec<String>) -> Vec<String> { fn wrap_commands(cmd: &Vec<String>) -> Vec<String> {
cmd.iter().map(|fragment| { cmd.iter()
if needs_wrapping(fragment) { .map(|fragment| {
wrap_in_quotes(fragment) if needs_wrapping(fragment) {
} else { wrap_in_quotes(fragment)
fragment.clone() } else {
} fragment.clone()
}).collect() }
})
.collect()
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
mod imp { mod imp {
use nix::{self, Error}; use super::wrap_commands;
use nix::libc::*; use nix::libc::*;
use nix::{self, Error};
use pathop::PathOp;
use signal::Signal;
use std::io::{self, Result}; use std::io::{self, Result};
use std::process::Command; use std::process::Command;
use std::sync::*; use std::sync::*;
use super::wrap_commands;
use signal::Signal;
use pathop::PathOp;
pub struct Process { pub struct Process {
pgid: pid_t, pgid: pid_t,
@ -71,7 +79,11 @@ mod imp {
#[allow(unknown_lints)] #[allow(unknown_lints)]
#[allow(mutex_atomic)] #[allow(mutex_atomic)]
impl Process { impl Process {
pub fn new(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> { pub fn new(
cmd: &Vec<String>,
updated_paths: Vec<PathOp>,
no_shell: bool,
) -> Result<Process> {
use nix::unistd::*; use nix::unistd::*;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
@ -99,16 +111,15 @@ mod imp {
} }
command command
.before_exec(|| setpgid(Pid::from_raw(0), Pid::from_raw(0)) .before_exec(|| setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(from_nix_error))
.map_err(from_nix_error))
.spawn() .spawn()
.and_then(|p| { .and_then(|p| {
Ok(Process { Ok(Process {
pgid: p.id() as i32, pgid: p.id() as i32,
lock: Mutex::new(false), lock: Mutex::new(false),
cvar: Condvar::new(), cvar: Condvar::new(),
}) })
}) })
} }
pub fn reap(&self) { pub fn reap(&self) {
@ -118,8 +129,9 @@ mod imp {
let mut finished = true; let mut finished = true;
loop { loop {
match waitpid(Pid::from_raw(-self.pgid), Some(WaitPidFlag::WNOHANG)) { match waitpid(Pid::from_raw(-self.pgid), Some(WaitPidFlag::WNOHANG)) {
Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Signaled(_, _, _)) => {
Ok(WaitStatus::Signaled(_, _, _)) => finished = finished && true, finished = finished && true
}
Ok(_) => { Ok(_) => {
finished = false; finished = false;
break; break;
@ -151,7 +163,6 @@ mod imp {
unsafe { unsafe {
killpg(self.pgid, sig); killpg(self.pgid, sig);
} }
} }
pub fn wait(&self) { pub fn wait(&self) {
@ -165,16 +176,16 @@ mod imp {
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
mod imp { mod imp {
use super::wrap_commands;
use kernel32::*;
use pathop::PathOp;
use signal::Signal;
use std::io; use std::io;
use std::io::Result; use std::io::Result;
use std::mem; use std::mem;
use std::process::Command; use std::process::Command;
use std::ptr; use std::ptr;
use super::wrap_commands;
use kernel32::*;
use winapi::*; use winapi::*;
use signal::Signal;
use pathop::PathOp;
pub struct Process { pub struct Process {
job: HANDLE, job: HANDLE,
@ -188,7 +199,11 @@ mod imp {
} }
impl Process { impl Process {
pub fn new(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> { pub fn new(
cmd: &Vec<String>,
updated_paths: Vec<PathOp>,
no_shell: bool,
) -> Result<Process> {
use std::os::windows::io::IntoRawHandle; use std::os::windows::io::IntoRawHandle;
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
@ -201,31 +216,43 @@ mod imp {
panic!("failed to create job object: {}", last_err()); panic!("failed to create job object: {}", last_err());
} }
let completion_port = unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 1) }; let completion_port =
unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 1) };
if job.is_null() { if job.is_null() {
panic!("unable to create IO completion port for job: {}", last_err()); panic!(
"unable to create IO completion port for job: {}",
last_err()
);
} }
let mut associate_completion: JOBOBJECT_ASSOCIATE_COMPLETION_PORT = unsafe { mem::zeroed() }; let mut associate_completion: JOBOBJECT_ASSOCIATE_COMPLETION_PORT =
unsafe { mem::zeroed() };
associate_completion.completion_key = job; associate_completion.completion_key = job;
associate_completion.completion_port = completion_port; associate_completion.completion_port = completion_port;
unsafe { unsafe {
let r = SetInformationJobObject(job, let r = SetInformationJobObject(
JobObjectAssociateCompletionPortInformation, job,
&mut associate_completion as *mut _ as LPVOID, JobObjectAssociateCompletionPortInformation,
mem::size_of_val(&associate_completion) as DWORD); &mut associate_completion as *mut _ as LPVOID,
mem::size_of_val(&associate_completion) as DWORD,
);
if r == 0 { if r == 0 {
panic!("failed to associate completion port with job: {}", last_err()); panic!(
"failed to associate completion port with job: {}",
last_err()
);
} }
} }
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { mem::zeroed() }; let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { mem::zeroed() };
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let r = unsafe { let r = unsafe {
SetInformationJobObject(job, SetInformationJobObject(
JobObjectExtendedLimitInformation, job,
&mut info as *mut _ as LPVOID, JobObjectExtendedLimitInformation,
mem::size_of_val(&info) as DWORD) &mut info as *mut _ as LPVOID,
mem::size_of_val(&info) as DWORD,
)
}; };
if r == 0 { if r == 0 {
panic!("failed to set job info: {}", last_err()); panic!("failed to set job info: {}", last_err());
@ -250,19 +277,20 @@ mod imp {
command.env(name, val); command.env(name, val);
} }
command command.spawn().and_then(|p| {
.spawn() let handle = p.into_raw_handle();
.and_then(|p| { let r = unsafe { AssignProcessToJobObject(job, handle) };
let handle = p.into_raw_handle(); if r == 0 {
let r = unsafe { AssignProcessToJobObject(job, handle) }; panic!("failed to add to job object: {}", last_err());
if r == 0 { }
panic!("failed to add to job object: {}", last_err());
}
resume_threads(handle); resume_threads(handle);
Ok(Process { job: job, completion_port: completion_port }) Ok(Process {
}) job: job,
completion_port: completion_port,
})
})
} }
pub fn reap(&self) {} pub fn reap(&self) {}
@ -279,7 +307,13 @@ mod imp {
let mut code: DWORD = 0; let mut code: DWORD = 0;
let mut key: ULONG_PTR = 0; let mut key: ULONG_PTR = 0;
let mut overlapped: LPOVERLAPPED = mem::uninitialized(); let mut overlapped: LPOVERLAPPED = mem::uninitialized();
GetQueuedCompletionStatus(self.completion_port, &mut code, &mut key, &mut overlapped, INFINITE); GetQueuedCompletionStatus(
self.completion_port,
&mut code,
&mut key,
&mut overlapped,
INFINITE,
);
if code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO && (key as HANDLE) == self.job { if code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO && (key as HANDLE) == self.job {
break; break;
@ -307,7 +341,15 @@ mod imp {
let child_id = GetProcessId(child_process); let child_id = GetProcessId(child_process);
let h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); let h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
let mut entry = THREADENTRY32 { dwSize: 28, cntUsage: 0, th32ThreadID: 0, th32OwnerProcessID: 0, tpBasePri: 0, tpDeltaPri: 0, dwFlags: 0}; let mut entry = THREADENTRY32 {
dwSize: 28,
cntUsage: 0,
th32ThreadID: 0,
th32OwnerProcessID: 0,
tpBasePri: 0,
tpDeltaPri: 0,
dwFlags: 0,
};
let mut result = Thread32First(h, &mut entry); let mut result = Thread32First(h, &mut entry);
while result != 0 { while result != 0 {
@ -325,7 +367,6 @@ mod imp {
} }
} }
/// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars /// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars
/// ///
/// WRITTEN -> `notify::ops::WRITE`, `notify::ops::CLOSE_WRITE` /// WRITTEN -> `notify::ops::WRITE`, `notify::ops::CLOSE_WRITE`
@ -339,11 +380,13 @@ fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
#[cfg(not(target_family = "unix"))] #[cfg(not(target_family = "unix"))]
const ENV_SEP: &'static str = ";"; const ENV_SEP: &'static str = ";";
let mut by_op = HashMap::new(); // Paths as `String`s collected by `notify::op` let mut by_op = HashMap::new(); // Paths as `String`s collected by `notify::op`
let mut all_pathbufs = HashSet::new(); // All unique `PathBuf`s let mut all_pathbufs = HashSet::new(); // All unique `PathBuf`s
for pathop in pathops { for pathop in pathops {
if let Some(op) = pathop.op { // ignore pathops that don't have a `notify::op` set if let Some(op) = pathop.op {
if let Some(s) = pathop.path.to_str() { // ignore invalid utf8 paths // ignore pathops that don't have a `notify::op` set
if let Some(s) = pathop.path.to_str() {
// ignore invalid utf8 paths
all_pathbufs.insert(pathop.path.clone()); all_pathbufs.insert(pathop.path.clone());
let e = by_op.entry(op).or_insert_with(Vec::new); let e = by_op.entry(op).or_insert_with(Vec::new);
e.push(s.to_owned()); e.push(s.to_owned());
@ -358,29 +401,35 @@ fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
let common_path = if all_pathbufs.len() > 1 { let common_path = if all_pathbufs.len() > 1 {
let all_pathbufs: Vec<PathBuf> = all_pathbufs.into_iter().collect(); let all_pathbufs: Vec<PathBuf> = all_pathbufs.into_iter().collect();
get_longest_common_path(&all_pathbufs) get_longest_common_path(&all_pathbufs)
} else { None }; } else {
None
};
if let Some(ref common_path) = common_path { if let Some(ref common_path) = common_path {
vars.push(("WATCHEXEC_COMMON_PATH".to_string(), common_path.to_string())); vars.push(("WATCHEXEC_COMMON_PATH".to_string(), common_path.to_string()));
} }
for (op, paths) in by_op { for (op, paths) in by_op {
let key = match op { let key = match op {
op if PathOp::is_create(op) => "WATCHEXEC_CREATED_PATH", op if PathOp::is_create(op) => "WATCHEXEC_CREATED_PATH",
op if PathOp::is_remove(op) => "WATCHEXEC_REMOVED_PATH", op if PathOp::is_remove(op) => "WATCHEXEC_REMOVED_PATH",
op if PathOp::is_rename(op) => "WATCHEXEC_RENAMED_PATH", op if PathOp::is_rename(op) => "WATCHEXEC_RENAMED_PATH",
op if PathOp::is_write(op) => "WATCHEXEC_WRITTEN_PATH", op if PathOp::is_write(op) => "WATCHEXEC_WRITTEN_PATH",
op if PathOp::is_meta(op) => "WATCHEXEC_META_CHANGED_PATH", op if PathOp::is_meta(op) => "WATCHEXEC_META_CHANGED_PATH",
_ => continue, // ignore `notify::op::RESCAN`s _ => continue, // ignore `notify::op::RESCAN`s
}; };
let paths = if let Some(ref common_path) = common_path { let paths = if let Some(ref common_path) = common_path {
paths.iter().map(|path_str| path_str.trim_left_matches(common_path).to_string()).collect::<Vec<_>>() paths
} else { paths }; .iter()
.map(|path_str| path_str.trim_left_matches(common_path).to_string())
.collect::<Vec<_>>()
} else {
paths
};
vars.push((key.to_string(), paths.as_slice().join(ENV_SEP))); vars.push((key.to_string(), paths.as_slice().join(ENV_SEP)));
} }
vars vars
} }
fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> { fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
match paths.len() { match paths.len() {
0 => return None, 0 => return None,
@ -413,18 +462,17 @@ fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
result.to_str().map(|ref_val| ref_val.to_string()) result.to_str().map(|ref_val| ref_val.to_string())
} }
#[cfg(test)] #[cfg(test)]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
mod tests { mod tests {
use std::path::PathBuf;
use std::collections::HashSet;
use notify; use notify;
use pathop::PathOp; use pathop::PathOp;
use std::collections::HashSet;
use std::path::PathBuf;
use super::spawn;
use super::get_longest_common_path;
use super::collect_path_env_vars; use super::collect_path_env_vars;
use super::get_longest_common_path;
use super::spawn;
use super::wrap_commands; use super::wrap_commands;
#[test] #[test]
@ -478,23 +526,26 @@ mod tests {
let single_result = get_longest_common_path(&single_path).unwrap(); let single_result = get_longest_common_path(&single_path).unwrap();
assert_eq!(single_result, "/tmp/random/"); assert_eq!(single_result, "/tmp/random/");
let common_paths = vec![PathBuf::from("/tmp/logs/hi"), let common_paths = vec![
PathBuf::from("/tmp/logs/bye"), PathBuf::from("/tmp/logs/hi"),
PathBuf::from("/tmp/logs/bye"), PathBuf::from("/tmp/logs/bye"),
PathBuf::from("/tmp/logs/fly")]; PathBuf::from("/tmp/logs/bye"),
PathBuf::from("/tmp/logs/fly"),
];
let common_result = get_longest_common_path(&common_paths).unwrap(); let common_result = get_longest_common_path(&common_paths).unwrap();
assert_eq!(common_result, "/tmp/logs"); assert_eq!(common_result, "/tmp/logs");
let diverging_paths = vec![PathBuf::from("/tmp/logs/hi"), PathBuf::from("/var/logs/hi")]; let diverging_paths = vec![PathBuf::from("/tmp/logs/hi"), PathBuf::from("/var/logs/hi")];
let diverging_result = get_longest_common_path(&diverging_paths).unwrap(); let diverging_result = get_longest_common_path(&diverging_paths).unwrap();
assert_eq!(diverging_result, "/"); assert_eq!(diverging_result, "/");
let uneven_paths = vec![PathBuf::from("/tmp/logs/hi"), let uneven_paths = vec![
PathBuf::from("/tmp/logs/"), PathBuf::from("/tmp/logs/hi"),
PathBuf::from("/tmp/logs/bye")]; PathBuf::from("/tmp/logs/"),
PathBuf::from("/tmp/logs/bye"),
];
let uneven_result = get_longest_common_path(&uneven_paths).unwrap(); let uneven_result = get_longest_common_path(&uneven_paths).unwrap();
assert_eq!(uneven_result, "/tmp/logs"); assert_eq!(uneven_result, "/tmp/logs");
@ -503,17 +554,35 @@ mod tests {
#[test] #[test]
fn pathops_collect_to_env_vars() { fn pathops_collect_to_env_vars() {
let pathops = vec![ let pathops = vec![
PathOp::new(&PathBuf::from("/tmp/logs/hi"), Some(notify::op::CREATE), None), PathOp::new(
PathOp::new(&PathBuf::from("/tmp/logs/hey/there"), Some(notify::op::CREATE), None), &PathBuf::from("/tmp/logs/hi"),
PathOp::new(&PathBuf::from("/tmp/logs/bye"), Some(notify::op::REMOVE), None), Some(notify::op::CREATE),
None,
),
PathOp::new(
&PathBuf::from("/tmp/logs/hey/there"),
Some(notify::op::CREATE),
None,
),
PathOp::new(
&PathBuf::from("/tmp/logs/bye"),
Some(notify::op::REMOVE),
None,
),
]; ];
let expected_vars = vec![ let expected_vars = vec![
("WATCHEXEC_COMMON_PATH".to_string(), "/tmp/logs".to_string()), ("WATCHEXEC_COMMON_PATH".to_string(), "/tmp/logs".to_string()),
("WATCHEXEC_REMOVED_PATH".to_string(), "/bye".to_string()), ("WATCHEXEC_REMOVED_PATH".to_string(), "/bye".to_string()),
("WATCHEXEC_CREATED_PATH".to_string(), "/hi:/hey/there".to_string()), (
"WATCHEXEC_CREATED_PATH".to_string(),
"/hi:/hey/there".to_string(),
),
]; ];
let vars = collect_path_env_vars(&pathops); let vars = collect_path_env_vars(&pathops);
assert_eq!(vars.iter().collect::<HashSet<_>>(), expected_vars.iter().collect::<HashSet<_>>()); assert_eq!(
vars.iter().collect::<HashSet<_>>(),
expected_vars.iter().collect::<HashSet<_>>()
);
} }
} }
@ -568,4 +637,3 @@ mod tests {
); );
} }
} }

View File

@ -1,22 +1,22 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::sync::mpsc::{channel, Receiver}; use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use cli; use cli;
use env_logger; use env_logger;
use gitignore; use gitignore;
use log; use log;
use notify::Error;
use notification_filter::NotificationFilter; use notification_filter::NotificationFilter;
use notify::Error;
use pathop::PathOp;
use process::{self, Process}; use process::{self, Process};
use signal::{self, Signal}; use signal::{self, Signal};
use watcher::{Event, Watcher}; use watcher::{Event, Watcher};
use pathop::PathOp;
type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>; pub type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
fn init_logger(debug: bool) { fn init_logger(debug: bool) {
let mut log_builder = env_logger::Builder::new(); let mut log_builder = env_logger::Builder::new();
@ -68,14 +68,15 @@ pub fn run(args: cli::Args) -> Result<()> {
init_logger(args.debug); init_logger(args.debug);
let paths: Result<Vec<PathBuf>> = args.paths let paths: Result<Vec<PathBuf>> = args
.paths
.iter() .iter()
.map(|p| { .map(|p| {
Ok(Path::new(&p) Ok(Path::new(&p)
.canonicalize() .canonicalize()
.map_err(|e| format!("Unable to canonicalize path: \"{}\", {}", p, e))? .map_err(|e| format!("Unable to canonicalize path: \"{}\", {}", p, e))?
.to_owned()) .to_owned())
}) })
.collect(); .collect();
let paths = paths?; let paths = paths?;
@ -92,11 +93,15 @@ pub fn run(args: cli::Args) -> Result<()> {
let watcher = match Watcher::new(tx.clone(), &paths, args.poll, args.poll_interval) { let watcher = match Watcher::new(tx.clone(), &paths, args.poll, args.poll_interval) {
Ok(watcher) => watcher, Ok(watcher) => watcher,
Err(ref e) if !args.poll && should_switch_to_poll(e) => { Err(ref e) if !args.poll && should_switch_to_poll(e) => {
warn!("System notification limit is too small, \ warn!(
falling back to polling mode."); "System notification limit is too small, \
if cfg!(target_os="linux") { falling back to polling mode."
warn!("For better performance increase system limit: \n \ );
sysctl fs.inotify.max_user_watches=524288"); if cfg!(target_os = "linux") {
warn!(
"For better performance increase system limit: \n \
sysctl fs.inotify.max_user_watches=524288"
);
} }
Watcher::new(tx, &paths, true, args.poll_interval) Watcher::new(tx, &paths, true, args.poll_interval)
.expect("polling watcher should always work") .expect("polling watcher should always work")

View File

@ -72,11 +72,12 @@ pub fn new(signal_name: Option<String>) -> Option<Signal> {
#[cfg(unix)] #[cfg(unix)]
pub fn install_handler<F>(handler: F) pub fn install_handler<F>(handler: F)
where F: Fn(self::Signal) + 'static + Send + Sync where
F: Fn(self::Signal) + 'static + Send + Sync,
{ {
use std::thread;
use nix::libc::c_int; use nix::libc::c_int;
use nix::sys::signal::*; use nix::sys::signal::*;
use std::thread;
// Mask all signals interesting to us. The mask propagates // Mask all signals interesting to us. The mask propagates
// to all threads started after this point. // to all threads started after this point.
@ -90,8 +91,7 @@ pub fn install_handler<F>(handler: F)
mask.add(SIGCHLD); mask.add(SIGCHLD);
mask.add(SIGUSR1); mask.add(SIGUSR1);
mask.add(SIGUSR2); mask.add(SIGUSR2);
mask.thread_set_mask() mask.thread_set_mask().expect("unable to set signal mask");
.expect("unable to set signal mask");
set_handler(handler); set_handler(handler);
@ -99,10 +99,14 @@ pub fn install_handler<F>(handler: F)
pub extern "C" fn sigchld_handler(_: c_int) {} pub extern "C" fn sigchld_handler(_: c_int) {}
unsafe { unsafe {
let _ = sigaction(SIGCHLD, let _ = sigaction(
&SigAction::new(SigHandler::Handler(sigchld_handler), SIGCHLD,
SaFlags::empty(), &SigAction::new(
SigSet::empty())); SigHandler::Handler(sigchld_handler),
SaFlags::empty(),
SigSet::empty(),
),
);
} }
// Spawn a thread to catch these signals // Spawn a thread to catch these signals
@ -137,7 +141,8 @@ pub fn install_handler<F>(handler: F)
#[cfg(windows)] #[cfg(windows)]
pub fn install_handler<F>(handler: F) pub fn install_handler<F>(handler: F)
where F: Fn(self::Signal) + 'static + Send + Sync where
F: Fn(self::Signal) + 'static + Send + Sync,
{ {
use kernel32::SetConsoleCtrlHandler; use kernel32::SetConsoleCtrlHandler;
use winapi::{BOOL, DWORD, FALSE, TRUE}; use winapi::{BOOL, DWORD, FALSE, TRUE};
@ -162,7 +167,8 @@ fn invoke(sig: self::Signal) {
} }
fn set_handler<F>(handler: F) fn set_handler<F>(handler: F)
where F: Fn(self::Signal) + 'static + Send + Sync where
F: Fn(self::Signal) + 'static + Send + Sync,
{ {
*CLEANUP.lock().unwrap() = Some(Box::new(handler)); *CLEANUP.lock().unwrap() = Some(Box::new(handler));
} }

View File

@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, raw_watcher}; use notify::{raw_watcher, PollWatcher, RecommendedWatcher, RecursiveMode};
/// Thin wrapper over the notify crate /// Thin wrapper over the notify crate
/// ///
@ -13,8 +13,8 @@ pub struct Watcher {
watcher_impl: WatcherImpl, watcher_impl: WatcherImpl,
} }
pub use notify::RawEvent as Event;
pub use notify::Error; pub use notify::Error;
pub use notify::RawEvent as Event;
enum WatcherImpl { enum WatcherImpl {
Recommended(RecommendedWatcher), Recommended(RecommendedWatcher),
@ -22,11 +22,12 @@ enum WatcherImpl {
} }
impl Watcher { impl Watcher {
pub fn new(tx: Sender<Event>, pub fn new(
paths: &[PathBuf], tx: Sender<Event>,
poll: bool, paths: &[PathBuf],
interval_ms: u32) poll: bool,
-> Result<Watcher, Error> { interval_ms: u32,
) -> Result<Watcher, Error> {
use notify::Watcher; use notify::Watcher;
let imp = if poll { let imp = if poll {