Formatting
This commit is contained in:
parent
9c60148b66
commit
a6163cc599
36
src/cli.rs
36
src/cli.rs
|
@ -135,24 +135,26 @@ pub fn get_args() -> Args {
|
|||
|
||||
if let Some(extensions) = args.values_of("extensions") {
|
||||
for exts in extensions {
|
||||
filters.extend(exts.split(',')
|
||||
.filter(|ext| !ext.is_empty())
|
||||
.map(|ext| format!("*.{}", ext.replace(".", ""))));
|
||||
|
||||
filters.extend(
|
||||
exts.split(',')
|
||||
.filter(|ext| !ext.is_empty())
|
||||
.map(|ext| format!("*.{}", ext.replace(".", ""))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut ignores = vec![];
|
||||
let default_ignores = vec![format!("**{}.DS_Store", MAIN_SEPARATOR),
|
||||
String::from("*.py[co]"),
|
||||
String::from("#*#"),
|
||||
String::from(".#*"),
|
||||
String::from(".*.sw?"),
|
||||
String::from(".*.sw?x"),
|
||||
format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
||||
format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
||||
format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR)];
|
||||
|
||||
let default_ignores = vec![
|
||||
format!("**{}.DS_Store", MAIN_SEPARATOR),
|
||||
String::from("*.py[co]"),
|
||||
String::from("#*#"),
|
||||
String::from(".#*"),
|
||||
String::from(".*.sw?"),
|
||||
String::from(".*.sw?x"),
|
||||
format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
||||
format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
||||
format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
||||
];
|
||||
|
||||
if args.occurrences_of("no-default-ignore") == 0 {
|
||||
ignores.extend(default_ignores)
|
||||
|
@ -167,9 +169,9 @@ pub fn get_args() -> Args {
|
|||
|
||||
let debounce = if args.occurrences_of("debounce") > 0 {
|
||||
value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit())
|
||||
} else {
|
||||
500
|
||||
};
|
||||
} else {
|
||||
500
|
||||
};
|
||||
|
||||
if signal.is_some() && args.is_present("postpone") {
|
||||
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
||||
|
|
|
@ -88,7 +88,8 @@ impl Gitignore {
|
|||
}
|
||||
|
||||
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()
|
||||
.filter(|f| path.starts_with(&f.root))
|
||||
.collect();
|
||||
|
@ -144,11 +145,10 @@ impl GitignoreFile {
|
|||
}
|
||||
|
||||
Ok(GitignoreFile {
|
||||
set: try!(builder.build()),
|
||||
patterns: patterns,
|
||||
root: root.to_owned(),
|
||||
})
|
||||
|
||||
set: try!(builder.build()),
|
||||
patterns: patterns,
|
||||
root: root.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -167,9 +167,9 @@ impl GitignoreFile {
|
|||
for &i in matches.iter().rev() {
|
||||
let pattern = &self.patterns[i];
|
||||
return match pattern.pattern_type {
|
||||
PatternType::Whitelist => MatchResult::Whitelist,
|
||||
PatternType::Ignore => MatchResult::Ignore,
|
||||
};
|
||||
PatternType::Whitelist => MatchResult::Whitelist,
|
||||
PatternType::Ignore => MatchResult::Ignore,
|
||||
};
|
||||
}
|
||||
|
||||
MatchResult::None
|
||||
|
@ -223,7 +223,6 @@ impl Pattern {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<globset::Error> for Error {
|
||||
fn from(error: globset::Error) -> Error {
|
||||
Error::GlobSet(error)
|
||||
|
@ -236,7 +235,6 @@ impl From<io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GitignoreFile;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate globset;
|
||||
extern crate env_logger;
|
||||
extern crate globset;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate notify;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate kernel32;
|
||||
#[cfg(unix)]
|
||||
extern crate nix;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
#[cfg(windows)]
|
||||
extern crate kernel32;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate mktemp;
|
||||
|
@ -21,10 +21,10 @@ extern crate mktemp;
|
|||
pub mod cli;
|
||||
mod gitignore;
|
||||
mod notification_filter;
|
||||
mod pathop;
|
||||
mod process;
|
||||
pub mod run;
|
||||
mod signal;
|
||||
mod watcher;
|
||||
mod pathop;
|
||||
|
||||
pub use run::run;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
extern crate watchexec;
|
||||
use watchexec::{cli, run};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<Error>> {
|
||||
fn main() -> run::Result<()> {
|
||||
run(cli::get_args())
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ pub enum Error {
|
|||
}
|
||||
|
||||
impl NotificationFilter {
|
||||
pub fn new(filters: Vec<String>,
|
||||
ignores: Vec<String>,
|
||||
ignore_files: gitignore::Gitignore)
|
||||
-> Result<NotificationFilter, Error> {
|
||||
pub fn new(
|
||||
filters: Vec<String>,
|
||||
ignores: Vec<String>,
|
||||
ignore_files: gitignore::Gitignore,
|
||||
) -> Result<NotificationFilter, Error> {
|
||||
let mut filter_set_builder = GlobSetBuilder::new();
|
||||
for f in &filters {
|
||||
filter_set_builder.add(try!(Glob::new(f)));
|
||||
|
@ -49,11 +50,11 @@ impl NotificationFilter {
|
|||
let ignore_set = try!(ignore_set_builder.build());
|
||||
|
||||
Ok(NotificationFilter {
|
||||
filters: filter_set,
|
||||
filter_count: filters.len(),
|
||||
ignores: ignore_set,
|
||||
ignore_files: ignore_files,
|
||||
})
|
||||
filters: filter_set,
|
||||
filter_count: filters.len(),
|
||||
ignores: ignore_set,
|
||||
ignore_files: ignore_files,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_excluded(&self, path: &Path) -> bool {
|
||||
|
@ -93,8 +94,8 @@ impl From<globset::Error> for Error {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gitignore;
|
||||
use super::NotificationFilter;
|
||||
use gitignore;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
|
@ -106,7 +107,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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("test.json")));
|
||||
|
@ -135,8 +140,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_ignores_take_precedence() {
|
||||
let ignores = vec![String::from("*.rs"), String::from("*.toml")];
|
||||
let filter = NotificationFilter::new(ignores.clone(), ignores, gitignore::load(&vec![]))
|
||||
.unwrap();
|
||||
let filter =
|
||||
NotificationFilter::new(ignores.clone(), ignores, gitignore::load(&vec![])).unwrap();
|
||||
|
||||
assert!(filter.is_excluded(&Path::new("hello.rs")));
|
||||
assert!(filter.is_excluded(&Path::new("Cargo.toml")));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use notify::op;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
/// Info about a path and its corresponding `notify` event
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct PathOp {
|
||||
|
@ -41,4 +40,3 @@ impl PathOp {
|
|||
op_.contains(op::CHMOD)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
268
src/process.rs
268
src/process.rs
|
@ -1,6 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
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 {
|
||||
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 {
|
||||
s.contains(|ch| match ch {
|
||||
' ' | '\t' | '\'' | '"' => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn wrap_in_quotes(s: &String) -> String {
|
||||
format!("'{}'", if s.contains('\'') {
|
||||
s.replace('\'', "'\"'\"'")
|
||||
} else {
|
||||
s.clone()
|
||||
})
|
||||
format!(
|
||||
"'{}'",
|
||||
if s.contains('\'') {
|
||||
s.replace('\'', "'\"'\"'")
|
||||
} else {
|
||||
s.clone()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
fn wrap_in_quotes(s: &String) -> String {
|
||||
format!("\"{}\"", if s.contains('"') {
|
||||
s.replace('"', "\"\"")
|
||||
} else {
|
||||
s.clone()
|
||||
})
|
||||
format!(
|
||||
"\"{}\"",
|
||||
if s.contains('"') {
|
||||
s.replace('"', "\"\"")
|
||||
} else {
|
||||
s.clone()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn wrap_commands(cmd: &Vec<String>) -> Vec<String> {
|
||||
cmd.iter().map(|fragment| {
|
||||
if needs_wrapping(fragment) {
|
||||
wrap_in_quotes(fragment)
|
||||
} else {
|
||||
fragment.clone()
|
||||
}
|
||||
}).collect()
|
||||
cmd.iter()
|
||||
.map(|fragment| {
|
||||
if needs_wrapping(fragment) {
|
||||
wrap_in_quotes(fragment)
|
||||
} else {
|
||||
fragment.clone()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
mod imp {
|
||||
use nix::{self, Error};
|
||||
use super::wrap_commands;
|
||||
use nix::libc::*;
|
||||
use nix::{self, Error};
|
||||
use pathop::PathOp;
|
||||
use signal::Signal;
|
||||
use std::io::{self, Result};
|
||||
use std::process::Command;
|
||||
use std::sync::*;
|
||||
use super::wrap_commands;
|
||||
use signal::Signal;
|
||||
use pathop::PathOp;
|
||||
|
||||
pub struct Process {
|
||||
pgid: pid_t,
|
||||
|
@ -71,7 +79,11 @@ mod imp {
|
|||
#[allow(unknown_lints)]
|
||||
#[allow(mutex_atomic)]
|
||||
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 std::os::unix::process::CommandExt;
|
||||
|
||||
|
@ -99,16 +111,15 @@ mod imp {
|
|||
}
|
||||
|
||||
command
|
||||
.before_exec(|| setpgid(Pid::from_raw(0), Pid::from_raw(0))
|
||||
.map_err(from_nix_error))
|
||||
.before_exec(|| setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(from_nix_error))
|
||||
.spawn()
|
||||
.and_then(|p| {
|
||||
Ok(Process {
|
||||
pgid: p.id() as i32,
|
||||
lock: Mutex::new(false),
|
||||
cvar: Condvar::new(),
|
||||
})
|
||||
})
|
||||
Ok(Process {
|
||||
pgid: p.id() as i32,
|
||||
lock: Mutex::new(false),
|
||||
cvar: Condvar::new(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reap(&self) {
|
||||
|
@ -118,8 +129,9 @@ mod imp {
|
|||
let mut finished = true;
|
||||
loop {
|
||||
match waitpid(Pid::from_raw(-self.pgid), Some(WaitPidFlag::WNOHANG)) {
|
||||
Ok(WaitStatus::Exited(_, _)) |
|
||||
Ok(WaitStatus::Signaled(_, _, _)) => finished = finished && true,
|
||||
Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Signaled(_, _, _)) => {
|
||||
finished = finished && true
|
||||
}
|
||||
Ok(_) => {
|
||||
finished = false;
|
||||
break;
|
||||
|
@ -151,7 +163,6 @@ mod imp {
|
|||
unsafe {
|
||||
killpg(self.pgid, sig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn wait(&self) {
|
||||
|
@ -165,16 +176,16 @@ mod imp {
|
|||
|
||||
#[cfg(target_family = "windows")]
|
||||
mod imp {
|
||||
use super::wrap_commands;
|
||||
use kernel32::*;
|
||||
use pathop::PathOp;
|
||||
use signal::Signal;
|
||||
use std::io;
|
||||
use std::io::Result;
|
||||
use std::mem;
|
||||
use std::process::Command;
|
||||
use std::ptr;
|
||||
use super::wrap_commands;
|
||||
use kernel32::*;
|
||||
use winapi::*;
|
||||
use signal::Signal;
|
||||
use pathop::PathOp;
|
||||
|
||||
pub struct Process {
|
||||
job: HANDLE,
|
||||
|
@ -188,7 +199,11 @@ mod imp {
|
|||
}
|
||||
|
||||
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::process::CommandExt;
|
||||
|
||||
|
@ -201,31 +216,43 @@ mod imp {
|
|||
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() {
|
||||
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_port = completion_port;
|
||||
unsafe {
|
||||
let r = SetInformationJobObject(job,
|
||||
JobObjectAssociateCompletionPortInformation,
|
||||
&mut associate_completion as *mut _ as LPVOID,
|
||||
mem::size_of_val(&associate_completion) as DWORD);
|
||||
let r = SetInformationJobObject(
|
||||
job,
|
||||
JobObjectAssociateCompletionPortInformation,
|
||||
&mut associate_completion as *mut _ as LPVOID,
|
||||
mem::size_of_val(&associate_completion) as DWORD,
|
||||
);
|
||||
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() };
|
||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
let r = unsafe {
|
||||
SetInformationJobObject(job,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut info as *mut _ as LPVOID,
|
||||
mem::size_of_val(&info) as DWORD)
|
||||
SetInformationJobObject(
|
||||
job,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut info as *mut _ as LPVOID,
|
||||
mem::size_of_val(&info) as DWORD,
|
||||
)
|
||||
};
|
||||
if r == 0 {
|
||||
panic!("failed to set job info: {}", last_err());
|
||||
|
@ -250,19 +277,20 @@ mod imp {
|
|||
command.env(name, val);
|
||||
}
|
||||
|
||||
command
|
||||
.spawn()
|
||||
.and_then(|p| {
|
||||
let handle = p.into_raw_handle();
|
||||
let r = unsafe { AssignProcessToJobObject(job, handle) };
|
||||
if r == 0 {
|
||||
panic!("failed to add to job object: {}", last_err());
|
||||
}
|
||||
command.spawn().and_then(|p| {
|
||||
let handle = p.into_raw_handle();
|
||||
let r = unsafe { AssignProcessToJobObject(job, handle) };
|
||||
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) {}
|
||||
|
@ -279,7 +307,13 @@ mod imp {
|
|||
let mut code: DWORD = 0;
|
||||
let mut key: ULONG_PTR = 0;
|
||||
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 {
|
||||
break;
|
||||
|
@ -307,7 +341,15 @@ mod imp {
|
|||
let child_id = GetProcessId(child_process);
|
||||
|
||||
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);
|
||||
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
|
||||
///
|
||||
/// 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"))]
|
||||
const ENV_SEP: &'static str = ";";
|
||||
|
||||
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 by_op = HashMap::new(); // Paths as `String`s collected by `notify::op`
|
||||
let mut all_pathbufs = HashSet::new(); // All unique `PathBuf`s
|
||||
for pathop in pathops {
|
||||
if let Some(op) = pathop.op { // ignore pathops that don't have a `notify::op` set
|
||||
if let Some(s) = pathop.path.to_str() { // ignore invalid utf8 paths
|
||||
if let Some(op) = pathop.op {
|
||||
// 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());
|
||||
let e = by_op.entry(op).or_insert_with(Vec::new);
|
||||
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 all_pathbufs: Vec<PathBuf> = all_pathbufs.into_iter().collect();
|
||||
get_longest_common_path(&all_pathbufs)
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(ref common_path) = common_path {
|
||||
vars.push(("WATCHEXEC_COMMON_PATH".to_string(), common_path.to_string()));
|
||||
}
|
||||
for (op, paths) in by_op {
|
||||
let key = match op {
|
||||
op if PathOp::is_create(op) => "WATCHEXEC_CREATED_PATH",
|
||||
op if PathOp::is_remove(op) => "WATCHEXEC_REMOVED_PATH",
|
||||
op if PathOp::is_rename(op) => "WATCHEXEC_RENAMED_PATH",
|
||||
op if PathOp::is_write(op) => "WATCHEXEC_WRITTEN_PATH",
|
||||
op if PathOp::is_meta(op) => "WATCHEXEC_META_CHANGED_PATH",
|
||||
_ => continue, // ignore `notify::op::RESCAN`s
|
||||
op if PathOp::is_create(op) => "WATCHEXEC_CREATED_PATH",
|
||||
op if PathOp::is_remove(op) => "WATCHEXEC_REMOVED_PATH",
|
||||
op if PathOp::is_rename(op) => "WATCHEXEC_RENAMED_PATH",
|
||||
op if PathOp::is_write(op) => "WATCHEXEC_WRITTEN_PATH",
|
||||
op if PathOp::is_meta(op) => "WATCHEXEC_META_CHANGED_PATH",
|
||||
_ => continue, // ignore `notify::op::RESCAN`s
|
||||
};
|
||||
|
||||
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<_>>()
|
||||
} else { paths };
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
||||
match paths.len() {
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_family = "unix")]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashSet;
|
||||
use notify;
|
||||
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::get_longest_common_path;
|
||||
use super::spawn;
|
||||
use super::wrap_commands;
|
||||
|
||||
#[test]
|
||||
|
@ -478,23 +526,26 @@ mod tests {
|
|||
let single_result = get_longest_common_path(&single_path).unwrap();
|
||||
assert_eq!(single_result, "/tmp/random/");
|
||||
|
||||
let common_paths = vec![PathBuf::from("/tmp/logs/hi"),
|
||||
PathBuf::from("/tmp/logs/bye"),
|
||||
PathBuf::from("/tmp/logs/bye"),
|
||||
PathBuf::from("/tmp/logs/fly")];
|
||||
let common_paths = vec![
|
||||
PathBuf::from("/tmp/logs/hi"),
|
||||
PathBuf::from("/tmp/logs/bye"),
|
||||
PathBuf::from("/tmp/logs/bye"),
|
||||
PathBuf::from("/tmp/logs/fly"),
|
||||
];
|
||||
|
||||
let common_result = get_longest_common_path(&common_paths).unwrap();
|
||||
assert_eq!(common_result, "/tmp/logs");
|
||||
|
||||
|
||||
let diverging_paths = vec![PathBuf::from("/tmp/logs/hi"), PathBuf::from("/var/logs/hi")];
|
||||
|
||||
let diverging_result = get_longest_common_path(&diverging_paths).unwrap();
|
||||
assert_eq!(diverging_result, "/");
|
||||
|
||||
let uneven_paths = vec![PathBuf::from("/tmp/logs/hi"),
|
||||
PathBuf::from("/tmp/logs/"),
|
||||
PathBuf::from("/tmp/logs/bye")];
|
||||
let uneven_paths = vec![
|
||||
PathBuf::from("/tmp/logs/hi"),
|
||||
PathBuf::from("/tmp/logs/"),
|
||||
PathBuf::from("/tmp/logs/bye"),
|
||||
];
|
||||
|
||||
let uneven_result = get_longest_common_path(&uneven_paths).unwrap();
|
||||
assert_eq!(uneven_result, "/tmp/logs");
|
||||
|
@ -503,17 +554,35 @@ mod tests {
|
|||
#[test]
|
||||
fn pathops_collect_to_env_vars() {
|
||||
let pathops = vec![
|
||||
PathOp::new(&PathBuf::from("/tmp/logs/hi"), 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),
|
||||
PathOp::new(
|
||||
&PathBuf::from("/tmp/logs/hi"),
|
||||
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![
|
||||
("WATCHEXEC_COMMON_PATH".to_string(), "/tmp/logs".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);
|
||||
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 {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
35
src/run.rs
35
src/run.rs
|
@ -1,22 +1,22 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use cli;
|
||||
use env_logger;
|
||||
use gitignore;
|
||||
use log;
|
||||
use notify::Error;
|
||||
use notification_filter::NotificationFilter;
|
||||
use notify::Error;
|
||||
use pathop::PathOp;
|
||||
use process::{self, Process};
|
||||
use signal::{self, Signal};
|
||||
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) {
|
||||
let mut log_builder = env_logger::Builder::new();
|
||||
|
@ -68,14 +68,15 @@ pub fn run(args: cli::Args) -> Result<()> {
|
|||
|
||||
init_logger(args.debug);
|
||||
|
||||
let paths: Result<Vec<PathBuf>> = args.paths
|
||||
let paths: Result<Vec<PathBuf>> = args
|
||||
.paths
|
||||
.iter()
|
||||
.map(|p| {
|
||||
Ok(Path::new(&p)
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("Unable to canonicalize path: \"{}\", {}", p, e))?
|
||||
.to_owned())
|
||||
})
|
||||
Ok(Path::new(&p)
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("Unable to canonicalize path: \"{}\", {}", p, e))?
|
||||
.to_owned())
|
||||
})
|
||||
.collect();
|
||||
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) {
|
||||
Ok(watcher) => watcher,
|
||||
Err(ref e) if !args.poll && should_switch_to_poll(e) => {
|
||||
warn!("System notification limit is too small, \
|
||||
falling back to polling mode.");
|
||||
if cfg!(target_os="linux") {
|
||||
warn!("For better performance increase system limit: \n \
|
||||
sysctl fs.inotify.max_user_watches=524288");
|
||||
warn!(
|
||||
"System notification limit is too small, \
|
||||
falling back to polling mode."
|
||||
);
|
||||
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)
|
||||
.expect("polling watcher should always work")
|
||||
|
|
|
@ -72,11 +72,12 @@ pub fn new(signal_name: Option<String>) -> Option<Signal> {
|
|||
|
||||
#[cfg(unix)]
|
||||
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::sys::signal::*;
|
||||
use std::thread;
|
||||
|
||||
// Mask all signals interesting to us. The mask propagates
|
||||
// to all threads started after this point.
|
||||
|
@ -90,8 +91,7 @@ pub fn install_handler<F>(handler: F)
|
|||
mask.add(SIGCHLD);
|
||||
mask.add(SIGUSR1);
|
||||
mask.add(SIGUSR2);
|
||||
mask.thread_set_mask()
|
||||
.expect("unable to set signal mask");
|
||||
mask.thread_set_mask().expect("unable to set signal mask");
|
||||
|
||||
set_handler(handler);
|
||||
|
||||
|
@ -99,10 +99,14 @@ pub fn install_handler<F>(handler: F)
|
|||
pub extern "C" fn sigchld_handler(_: c_int) {}
|
||||
|
||||
unsafe {
|
||||
let _ = sigaction(SIGCHLD,
|
||||
&SigAction::new(SigHandler::Handler(sigchld_handler),
|
||||
SaFlags::empty(),
|
||||
SigSet::empty()));
|
||||
let _ = sigaction(
|
||||
SIGCHLD,
|
||||
&SigAction::new(
|
||||
SigHandler::Handler(sigchld_handler),
|
||||
SaFlags::empty(),
|
||||
SigSet::empty(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Spawn a thread to catch these signals
|
||||
|
@ -137,7 +141,8 @@ pub fn install_handler<F>(handler: F)
|
|||
|
||||
#[cfg(windows)]
|
||||
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 winapi::{BOOL, DWORD, FALSE, TRUE};
|
||||
|
@ -162,7 +167,8 @@ fn invoke(sig: self::Signal) {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
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
|
||||
///
|
||||
|
@ -13,8 +13,8 @@ pub struct Watcher {
|
|||
watcher_impl: WatcherImpl,
|
||||
}
|
||||
|
||||
pub use notify::RawEvent as Event;
|
||||
pub use notify::Error;
|
||||
pub use notify::RawEvent as Event;
|
||||
|
||||
enum WatcherImpl {
|
||||
Recommended(RecommendedWatcher),
|
||||
|
@ -22,11 +22,12 @@ enum WatcherImpl {
|
|||
}
|
||||
|
||||
impl Watcher {
|
||||
pub fn new(tx: Sender<Event>,
|
||||
paths: &[PathBuf],
|
||||
poll: bool,
|
||||
interval_ms: u32)
|
||||
-> Result<Watcher, Error> {
|
||||
pub fn new(
|
||||
tx: Sender<Event>,
|
||||
paths: &[PathBuf],
|
||||
poll: bool,
|
||||
interval_ms: u32,
|
||||
) -> Result<Watcher, Error> {
|
||||
use notify::Watcher;
|
||||
|
||||
let imp = if poll {
|
||||
|
|
Loading…
Reference in New Issue