use std::path::PathBuf;
use std::sync::mpsc;
use std::time::Duration;
use colored::*;
use notify::{RecursiveMode, Watcher};
use notify_debouncer_full::{new_debouncer, DebouncedEvent};
use crate::commands::script::{run_script_with_options, RunOptions, Scripts};
use crate::error::CargoScriptError;
pub struct WatchConfig {
pub watch_paths: Vec<PathBuf>,
pub exclude: Vec<String>,
pub debounce: Duration,
}
impl Default for WatchConfig {
fn default() -> Self {
Self {
watch_paths: vec![PathBuf::from(".")],
exclude: vec!["target".into(), ".git".into(), "node_modules".into()],
debounce: Duration::from_millis(250),
}
}
}
pub fn watch_and_run(
scripts: &Scripts,
script_name: &str,
opts: &RunOptions,
cfg: WatchConfig,
) -> Result<(), CargoScriptError> {
if !opts.quiet {
println!(
"{} watching {} path(s); press Ctrl-C to stop",
"👀".cyan(),
cfg.watch_paths.len()
);
}
let _ = run_script_with_options(scripts, script_name, opts);
let (tx, rx) = mpsc::channel::<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>();
let mut debouncer = new_debouncer(cfg.debounce, None, tx).map_err(|e| {
CargoScriptError::WatchError {
path: cfg.watch_paths.iter().map(|p| p.display().to_string()).collect::<Vec<_>>().join(", "),
message: e.to_string(),
}
})?;
for p in &cfg.watch_paths {
debouncer
.watcher()
.watch(p, RecursiveMode::Recursive)
.map_err(|e| CargoScriptError::WatchError {
path: p.display().to_string(),
message: e.to_string(),
})?;
}
loop {
match rx.recv() {
Ok(Ok(events)) => {
if events.iter().any(|ev| relevant_event(ev, &cfg.exclude)) {
if !opts.quiet {
println!(
"\n{} file change detected, re-running '{}'...",
"🔄".yellow(),
script_name.green()
);
}
if let Err(e) = run_script_with_options(scripts, script_name, opts) {
eprintln!("{}", e);
}
}
}
Ok(Err(errors)) => {
for e in errors {
eprintln!("{} watch error: {}", "⚠️".yellow(), e);
}
}
Err(_) => break, }
}
Ok(())
}
fn relevant_event(ev: &DebouncedEvent, exclude: &[String]) -> bool {
for path in &ev.paths {
let s = path.display().to_string();
if exclude.iter().any(|ex| s.contains(ex)) {
return false;
}
}
true
}