mirror of
https://github.com/watchexec/watchexec.git
synced 2024-09-28 22:21:33 +02:00
detailed update information
issue #59 - Keep track of `notify::op::Op`s associated with each updated path - Collect paths into `notify::op::Op` categories and pass them on as environment vars - Set a COMMON_PATH and use relative paths if more than one unique path was touched
This commit is contained in:
parent
d6ea55cbd4
commit
8bd9bb3c25
@ -25,5 +25,6 @@ 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;
|
||||||
|
@ -25,6 +25,7 @@ mod process;
|
|||||||
mod run;
|
mod run;
|
||||||
mod signal;
|
mod signal;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
mod pathop;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = cli::get_args();
|
let args = cli::get_args();
|
||||||
|
44
src/pathop.rs
Normal file
44
src/pathop.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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 {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub op: Option<op::Op>,
|
||||||
|
pub cookie: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathOp {
|
||||||
|
pub fn new(path: &Path, op: Option<op::Op>, cookie: Option<u32>) -> PathOp {
|
||||||
|
PathOp {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
op: op,
|
||||||
|
cookie: cookie,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_create(op_: op::Op) -> bool {
|
||||||
|
op_.contains(op::CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_remove(op_: op::Op) -> bool {
|
||||||
|
op_.contains(op::REMOVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_rename(op_: op::Op) -> bool {
|
||||||
|
op_.contains(op::RENAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_write(op_: op::Op) -> bool {
|
||||||
|
let mut write_or_close_write = op::WRITE;
|
||||||
|
write_or_close_write.toggle(op::CLOSE_WRITE);
|
||||||
|
op_.intersects(write_or_close_write)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_meta(op_: op::Op) -> bool {
|
||||||
|
op_.contains(op::CHMOD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
106
src/process.rs
106
src/process.rs
@ -1,6 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use pathop::PathOp;
|
||||||
|
|
||||||
pub fn spawn(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Process {
|
pub fn spawn(cmd: &str, 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,10 +13,10 @@ mod imp {
|
|||||||
use nix::{self, Error};
|
use nix::{self, Error};
|
||||||
use nix::libc::*;
|
use nix::libc::*;
|
||||||
use std::io::{self, Result};
|
use std::io::{self, Result};
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::*;
|
use std::sync::*;
|
||||||
use signal::Signal;
|
use signal::Signal;
|
||||||
|
use pathop::PathOp;
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pgid: pid_t,
|
pgid: pid_t,
|
||||||
@ -33,7 +35,7 @@ mod imp {
|
|||||||
#[allow(unknown_lints)]
|
#[allow(unknown_lints)]
|
||||||
#[allow(mutex_atomic)]
|
#[allow(mutex_atomic)]
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
pub fn new(cmd: &str, 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;
|
||||||
|
|
||||||
@ -55,12 +57,9 @@ mod imp {
|
|||||||
|
|
||||||
debug!("Assembled command {:?}", command);
|
debug!("Assembled command {:?}", command);
|
||||||
|
|
||||||
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
let command_envs = super::collect_path_env_vars(&updated_paths);
|
||||||
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
for &(ref name, ref val) in &command_envs {
|
||||||
}
|
command.env(name, val);
|
||||||
|
|
||||||
if let Some(common_path) = super::get_longest_common_path(&updated_paths) {
|
|
||||||
command.env("WATCHEXEC_COMMON_PATH", common_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command
|
command
|
||||||
@ -133,12 +132,12 @@ mod imp {
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use kernel32::*;
|
use kernel32::*;
|
||||||
use winapi::*;
|
use winapi::*;
|
||||||
use signal::Signal;
|
use signal::Signal;
|
||||||
|
use pathop::PathOp;
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
job: HANDLE,
|
job: HANDLE,
|
||||||
@ -152,7 +151,7 @@ mod imp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
pub fn new(cmd: &str, 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;
|
||||||
|
|
||||||
@ -215,12 +214,9 @@ mod imp {
|
|||||||
command.creation_flags(CREATE_SUSPENDED);
|
command.creation_flags(CREATE_SUSPENDED);
|
||||||
debug!("Assembled command {:?}", command);
|
debug!("Assembled command {:?}", command);
|
||||||
|
|
||||||
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
let command_envs = super::collect_path_env_vars(&updated_paths);
|
||||||
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
for &(ref name, ref val) in &command_envs {
|
||||||
}
|
command.env(name, val);
|
||||||
|
|
||||||
if let Some(common_path) = super::get_longest_common_path(&updated_paths) {
|
|
||||||
command.env("WATCHEXEC_COMMON_PATH", common_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command
|
command
|
||||||
@ -298,10 +294,62 @@ mod imp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_single_updated_path(paths: &[PathBuf]) -> Option<&str> {
|
|
||||||
paths.get(0).and_then(|p| p.to_str())
|
/// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars
|
||||||
|
///
|
||||||
|
/// WRITTEN -> notify::ops::WRITE, notify::ops::CLOSE_WRITE
|
||||||
|
/// META_CHANGED -> notify::ops::CHMOD
|
||||||
|
/// REMOVED -> notify::ops::REMOVE
|
||||||
|
/// CREATED -> notify::ops::CREATE
|
||||||
|
/// RENAMED -> notify::ops::RENAME
|
||||||
|
fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
const ENV_SEP: &'static str = ":";
|
||||||
|
#[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
|
||||||
|
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
|
||||||
|
all_pathbufs.insert(pathop.path.clone());
|
||||||
|
let e = by_op.entry(op).or_insert(vec![]);
|
||||||
|
e.push(s.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vars = vec![];
|
||||||
|
// Only break off a common path if we have more than one unique path,
|
||||||
|
// otherwise we end up with a `COMMON_PATH` being set and other vars
|
||||||
|
// being present but empty.
|
||||||
|
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 };
|
||||||
|
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.into_iter() {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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 };
|
||||||
|
vars.push((key.to_string(), paths.as_slice().join(ENV_SEP)));
|
||||||
|
}
|
||||||
|
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,
|
||||||
@ -339,9 +387,13 @@ fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use notify;
|
||||||
|
use pathop::PathOp;
|
||||||
|
|
||||||
use super::spawn;
|
use super::spawn;
|
||||||
use super::get_longest_common_path;
|
use super::get_longest_common_path;
|
||||||
|
use super::collect_path_env_vars;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
@ -375,5 +427,21 @@ mod tests {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
];
|
||||||
|
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()),
|
||||||
|
];
|
||||||
|
let vars = collect_path_env_vars(&pathops);
|
||||||
|
assert_eq!(vars.iter().collect::<HashSet<_>>(), expected_vars.iter().collect::<HashSet<_>>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/run.rs
18
src/run.rs
@ -12,6 +12,7 @@ use notification_filter::NotificationFilter;
|
|||||||
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;
|
||||||
|
|
||||||
fn init_logger(debug: bool) {
|
fn init_logger(debug: bool) {
|
||||||
let mut log_builder = env_logger::LogBuilder::new();
|
let mut log_builder = env_logger::LogBuilder::new();
|
||||||
@ -167,7 +168,7 @@ pub fn run(args: cli::Args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) -> Vec<PathBuf> {
|
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) -> Vec<PathOp> {
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
let mut cache = HashMap::new();
|
let mut cache = HashMap::new();
|
||||||
|
|
||||||
@ -175,15 +176,16 @@ fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) ->
|
|||||||
let e = rx.recv().expect("error when reading event");
|
let e = rx.recv().expect("error when reading event");
|
||||||
|
|
||||||
if let Some(ref path) = e.path {
|
if let Some(ref path) = e.path {
|
||||||
|
let pathop = PathOp::new(&path, e.op.ok(), e.cookie);
|
||||||
// Ignore cache for the initial file. Otherwise, in
|
// Ignore cache for the initial file. Otherwise, in
|
||||||
// debug mode it's hard to track what's going on
|
// debug mode it's hard to track what's going on
|
||||||
let excluded = filter.is_excluded(path);
|
let excluded = filter.is_excluded(path);
|
||||||
if !cache.contains_key(path) {
|
if !cache.contains_key(&pathop) {
|
||||||
cache.insert(path.to_owned(), excluded);
|
cache.insert(pathop.clone(), excluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !excluded {
|
if !excluded {
|
||||||
paths.push(path.to_owned());
|
paths.push(pathop);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,17 +195,17 @@ fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) ->
|
|||||||
let timeout = Duration::from_millis(debounce);
|
let timeout = Duration::from_millis(debounce);
|
||||||
while let Ok(e) = rx.recv_timeout(timeout) {
|
while let Ok(e) = rx.recv_timeout(timeout) {
|
||||||
if let Some(ref path) = e.path {
|
if let Some(ref path) = e.path {
|
||||||
if cache.contains_key(path) {
|
let pathop = PathOp::new(&path, e.op.ok(), e.cookie);
|
||||||
|
if cache.contains_key(&pathop) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let excluded = filter.is_excluded(path);
|
let excluded = filter.is_excluded(path);
|
||||||
|
|
||||||
let p = path.to_owned();
|
cache.insert(pathop.clone(), excluded);
|
||||||
cache.insert(p.clone(), excluded);
|
|
||||||
|
|
||||||
if !excluded {
|
if !excluded {
|
||||||
paths.push(p);
|
paths.push(pathop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user