mirror of
https://github.com/watchexec/watchexec.git
synced 2024-09-29 22:51:33 +02:00
[api] Make watchexec take a Handler rather than a callback
Instead of special-casing the callback, which is the path least-taken, switch the internals to a Handler model, where the default behaviour is an implementation of a Handler, and external callers can implement their own Handlers and pass them in. While doing so, change all unwraps in run::run to returning Errs, and expand the watchexec Error enum to accommodate. That should make it easier to use as a library. Also, differentiate between "manual" and "on update" runs. For now the only manual run is the initial run, but this paves the way for e.g. keyboard- or signal- triggered runs.
This commit is contained in:
parent
ac3a4f0717
commit
aae5a216b0
56
src/cli.rs
56
src/cli.rs
@ -1,12 +1,11 @@
|
|||||||
use std::path::MAIN_SEPARATOR;
|
use clap::{App, Arg, Error};
|
||||||
use std::process::Command;
|
use error;
|
||||||
|
use std::{ffi::OsString, fs::canonicalize, path::{MAIN_SEPARATOR, PathBuf}, process::Command};
|
||||||
|
|
||||||
use clap::{App, Arg, Error, ArgMatches};
|
#[derive(Clone, Debug)]
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub cmd: Vec<String>,
|
pub cmd: Vec<String>,
|
||||||
pub paths: Vec<String>,
|
pub paths: Vec<PathBuf>,
|
||||||
pub filters: Vec<String>,
|
pub filters: Vec<String>,
|
||||||
pub ignores: Vec<String>,
|
pub ignores: Vec<String>,
|
||||||
pub clear_screen: bool,
|
pub clear_screen: bool,
|
||||||
@ -32,7 +31,19 @@ pub fn clear_screen() {
|
|||||||
let _ = Command::new("tput").arg("reset").status();
|
let _ = Command::new("tput").arg("reset").status();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_app<'a, 'b>() -> App<'a, 'b> {
|
pub fn get_args() -> error::Result<Args> {
|
||||||
|
get_args_impl(None::<&[&str]>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_args_from<I, T>(from: I) -> error::Result<Args>
|
||||||
|
where I: IntoIterator<Item=T>, T: Into<OsString> + Clone
|
||||||
|
{
|
||||||
|
get_args_impl(Some(from))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_args_impl<I, T>(from: Option<I>) -> error::Result<Args>
|
||||||
|
where I: IntoIterator<Item=T>, T: Into<OsString> + Clone
|
||||||
|
{
|
||||||
let app = App::new("watchexec")
|
let app = App::new("watchexec")
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about("Execute commands when watched files change")
|
.about("Execute commands when watched files change")
|
||||||
@ -116,12 +127,18 @@ pub fn init_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.short("n")
|
.short("n")
|
||||||
.long("no-shell"))
|
.long("no-shell"))
|
||||||
.arg(Arg::with_name("once").short("1").hidden(true));
|
.arg(Arg::with_name("once").short("1").hidden(true));
|
||||||
app
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_args(args: ArgMatches) -> Args{
|
let args = match from {
|
||||||
let cmd: Vec<String> = values_t!(args.values_of("command"), String).unwrap();
|
None => app.get_matches(),
|
||||||
let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]);
|
Some(i) => app.get_matches_from(i)
|
||||||
|
};
|
||||||
|
|
||||||
|
let cmd: Vec<String> = values_t!(args.values_of("command"), String)?;
|
||||||
|
let str_paths = values_t!(args.values_of("path"), String).unwrap_or(vec![".".into()]);
|
||||||
|
let mut paths = vec![];
|
||||||
|
for path in str_paths {
|
||||||
|
paths.push(canonicalize(&path).map_err(|e| error::Error::Canonicalization(path, e))?);
|
||||||
|
}
|
||||||
|
|
||||||
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)
|
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)
|
||||||
let signal = if args.is_present("kill") {
|
let signal = if args.is_present("kill") {
|
||||||
@ -131,7 +148,7 @@ pub fn process_args(args: ArgMatches) -> Args{
|
|||||||
args.value_of("signal").map(str::to_string)
|
args.value_of("signal").map(str::to_string)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]);
|
let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(Vec::new());
|
||||||
|
|
||||||
if let Some(extensions) = args.values_of("extensions") {
|
if let Some(extensions) = args.values_of("extensions") {
|
||||||
for exts in extensions {
|
for exts in extensions {
|
||||||
@ -159,7 +176,7 @@ pub fn process_args(args: ArgMatches) -> Args{
|
|||||||
if args.occurrences_of("no-default-ignore") == 0 {
|
if args.occurrences_of("no-default-ignore") == 0 {
|
||||||
ignores.extend(default_ignores)
|
ignores.extend(default_ignores)
|
||||||
};
|
};
|
||||||
ignores.extend(values_t!(args.values_of("ignore"), String).unwrap_or(vec![]));
|
ignores.extend(values_t!(args.values_of("ignore"), String).unwrap_or(Vec::new()));
|
||||||
|
|
||||||
let poll_interval = if args.occurrences_of("poll") > 0 {
|
let poll_interval = if args.occurrences_of("poll") > 0 {
|
||||||
value_t!(args.value_of("poll"), u32).unwrap_or_else(|e| e.exit())
|
value_t!(args.value_of("poll"), u32).unwrap_or_else(|e| e.exit())
|
||||||
@ -185,7 +202,7 @@ pub fn process_args(args: ArgMatches) -> Args{
|
|||||||
.exit();
|
.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Args {
|
Ok(Args {
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
paths: paths,
|
paths: paths,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
@ -201,12 +218,5 @@ pub fn process_args(args: ArgMatches) -> Args{
|
|||||||
once: args.is_present("once"),
|
once: args.is_present("once"),
|
||||||
poll: args.occurrences_of("poll") > 0,
|
poll: args.occurrences_of("poll") > 0,
|
||||||
poll_interval: poll_interval,
|
poll_interval: poll_interval,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unknown_lints)]
|
|
||||||
#[allow(or_fun_call)]
|
|
||||||
pub fn get_args() -> Args {
|
|
||||||
let args = init_app().get_matches();
|
|
||||||
process_args(args)
|
|
||||||
}
|
}
|
19
src/error.rs
19
src/error.rs
@ -1,14 +1,17 @@
|
|||||||
|
use clap;
|
||||||
use globset;
|
use globset;
|
||||||
use notify;
|
use notify;
|
||||||
use std::{error::Error as StdError, fmt, io};
|
use std::{error::Error as StdError, fmt, io, sync::PoisonError};
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Canonicalization(String, io::Error),
|
Canonicalization(String, io::Error),
|
||||||
|
Clap(clap::Error),
|
||||||
Glob(globset::Error),
|
Glob(globset::Error),
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
Notify(notify::Error),
|
Notify(notify::Error),
|
||||||
|
PoisonedLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {
|
impl StdError for Error {
|
||||||
@ -19,6 +22,12 @@ impl StdError for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<clap::Error> for Error {
|
||||||
|
fn from(err: clap::Error) -> Self {
|
||||||
|
Error::Clap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<globset::Error> for Error {
|
impl From<globset::Error> for Error {
|
||||||
fn from(err: globset::Error) -> Self {
|
fn from(err: globset::Error) -> Self {
|
||||||
Error::Glob(err)
|
Error::Glob(err)
|
||||||
@ -40,6 +49,12 @@ impl From<notify::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> From<PoisonError<T>> for Error {
|
||||||
|
fn from(_err: PoisonError<T>) -> Self {
|
||||||
|
Error::PoisonedLock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
@ -47,9 +62,11 @@ impl fmt::Display for Error {
|
|||||||
"{} error: {}",
|
"{} error: {}",
|
||||||
match self {
|
match self {
|
||||||
Error::Canonicalization(_, _) => "Path",
|
Error::Canonicalization(_, _) => "Path",
|
||||||
|
Error::Clap(_) => "Argument",
|
||||||
Error::Glob(_) => "Globset",
|
Error::Glob(_) => "Globset",
|
||||||
Error::Io(_) => "I/O",
|
Error::Io(_) => "I/O",
|
||||||
Error::Notify(_) => "Notify",
|
Error::Notify(_) => "Notify",
|
||||||
|
Error::PoisonedLock => "Internal",
|
||||||
},
|
},
|
||||||
match self {
|
match self {
|
||||||
Error::Canonicalization(path, err) => {
|
Error::Canonicalization(path, err) => {
|
||||||
|
@ -2,5 +2,5 @@ extern crate watchexec;
|
|||||||
use watchexec::{cli, error, run};
|
use watchexec::{cli, error, run};
|
||||||
|
|
||||||
fn main() -> error::Result<()> {
|
fn main() -> error::Result<()> {
|
||||||
run(cli::get_args(), None::<fn(_)>)
|
run(cli::get_args()?)
|
||||||
}
|
}
|
||||||
|
@ -14,18 +14,18 @@ pub struct NotificationFilter {
|
|||||||
|
|
||||||
impl NotificationFilter {
|
impl NotificationFilter {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
filters: Vec<String>,
|
filters: &[String],
|
||||||
ignores: Vec<String>,
|
ignores: &[String],
|
||||||
ignore_files: Gitignore,
|
ignore_files: Gitignore,
|
||||||
) -> error::Result<NotificationFilter> {
|
) -> error::Result<NotificationFilter> {
|
||||||
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(Glob::new(f)?);
|
filter_set_builder.add(Glob::new(f)?);
|
||||||
debug!("Adding filter: \"{}\"", f);
|
debug!("Adding filter: \"{}\"", f);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ignore_set_builder = GlobSetBuilder::new();
|
let mut ignore_set_builder = GlobSetBuilder::new();
|
||||||
for i in &ignores {
|
for i in ignores {
|
||||||
let mut ignore_path = Path::new(i).to_path_buf();
|
let mut ignore_path = Path::new(i).to_path_buf();
|
||||||
if ignore_path.is_relative() && !i.starts_with("*") {
|
if ignore_path.is_relative() && !i.starts_with("*") {
|
||||||
ignore_path = Path::new("**").join(&ignore_path);
|
ignore_path = Path::new("**").join(&ignore_path);
|
||||||
@ -74,7 +74,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_allows_everything_by_default() {
|
fn test_allows_everything_by_default() {
|
||||||
let filter = NotificationFilter::new(vec![], vec![], gitignore::load(&vec![])).unwrap();
|
let filter = NotificationFilter::new(&[], &[], gitignore::load(&[])).unwrap();
|
||||||
|
|
||||||
assert!(!filter.is_excluded(&Path::new("foo")));
|
assert!(!filter.is_excluded(&Path::new("foo")));
|
||||||
}
|
}
|
||||||
@ -82,9 +82,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_filename() {
|
fn test_filename() {
|
||||||
let filter = NotificationFilter::new(
|
let filter = NotificationFilter::new(
|
||||||
vec![],
|
&[],
|
||||||
vec![String::from("test.json")],
|
&["test.json".into()],
|
||||||
gitignore::load(&vec![]),
|
gitignore::load(&[]),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert!(filter.is_excluded(&Path::new("/path/to/test.json")));
|
assert!(filter.is_excluded(&Path::new("/path/to/test.json")));
|
||||||
@ -93,8 +93,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_filters() {
|
fn test_multiple_filters() {
|
||||||
let filters = vec![String::from("*.rs"), String::from("*.toml")];
|
let filters = &["*.rs".into(), "*.toml".into()];
|
||||||
let filter = NotificationFilter::new(filters, vec![], gitignore::load(&vec![])).unwrap();
|
let filter = NotificationFilter::new(filters, &[], gitignore::load(&[])).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")));
|
||||||
@ -103,8 +103,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_ignores() {
|
fn test_multiple_ignores() {
|
||||||
let ignores = vec![String::from("*.rs"), String::from("*.toml")];
|
let ignores = &["*.rs".into(), "*.toml".into()];
|
||||||
let filter = NotificationFilter::new(vec![], ignores, gitignore::load(&vec![])).unwrap();
|
let filter = NotificationFilter::new(&[], 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")));
|
||||||
@ -113,9 +113,9 @@ 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 = &["*.rs".into(), "*.toml".into()];
|
||||||
let filter =
|
let filter =
|
||||||
NotificationFilter::new(ignores.clone(), ignores, gitignore::load(&vec![])).unwrap();
|
NotificationFilter::new(ignores, ignores, gitignore::load(&[])).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")));
|
||||||
|
187
src/run.rs
187
src/run.rs
@ -5,7 +5,7 @@ use std::sync::mpsc::{channel, Receiver};
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cli;
|
use cli::{Args, clear_screen};
|
||||||
use env_logger;
|
use env_logger;
|
||||||
use error::{Error, Result};
|
use error::{Error, Result};
|
||||||
use gitignore;
|
use gitignore;
|
||||||
@ -32,40 +32,53 @@ fn init_logger(debug: bool) {
|
|||||||
.init();
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run<F>(args: cli::Args, cb: Option<F>) -> Result<()> where F: Fn(Vec<PathOp>) {
|
pub trait Handler {
|
||||||
let child_process: Arc<RwLock<Option<Process>>> = Arc::new(RwLock::new(None));
|
/// Initialises the `Handler` with a copy of the arguments.
|
||||||
let weak_child = Arc::downgrade(&child_process);
|
fn new(args: Args) -> Result<Self> where Self: Sized;
|
||||||
|
|
||||||
// Convert signal string to the corresponding integer
|
/// Called through a manual request, such as an initial run.
|
||||||
let signal = signal::new(args.signal);
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `Result` which means:
|
||||||
|
///
|
||||||
|
/// - `Err`: an error has occurred while processing, quit.
|
||||||
|
/// - `Ok(false)`: everything is fine and the loop can continue.
|
||||||
|
/// - `Ok(true)`: everything is fine but we should gracefully stop.
|
||||||
|
fn on_manual(&mut self) -> Result<bool>;
|
||||||
|
|
||||||
signal::install_handler(move |sig: Signal| {
|
/// Called through a file-update request.
|
||||||
if let Some(lock) = weak_child.upgrade() {
|
///
|
||||||
let strong = lock.read().unwrap();
|
/// # Parameters
|
||||||
if let Some(ref child) = *strong {
|
///
|
||||||
match sig {
|
/// - `ops`: The list of events that triggered this update.
|
||||||
Signal::SIGCHLD => child.reap(), // SIGCHLD is special, initiate reap()
|
///
|
||||||
_ => child.signal(sig),
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `Result` which means:
|
||||||
|
///
|
||||||
|
/// - `Err`: an error has occurred while processing, quit.
|
||||||
|
/// - `Ok(true)`: everything is fine and the loop can continue.
|
||||||
|
/// - `Ok(false)`: everything is fine but we should gracefully stop.
|
||||||
|
fn on_update(&mut self, ops: Vec<PathOp>) -> Result<bool>;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
/// Starts watching, and calls a handler when something happens.
|
||||||
|
///
|
||||||
|
/// Given an argument structure and a `Handler` type, starts the watcher
|
||||||
|
/// loop (blocking until done).
|
||||||
|
pub fn watch<H>(args: Args) -> Result<()> where H: Handler {
|
||||||
init_logger(args.debug);
|
init_logger(args.debug);
|
||||||
|
let mut handler = H::new(args.clone())?;
|
||||||
|
|
||||||
let mut paths = vec![];
|
let gitignore = gitignore::load(if args.no_vcs_ignore { &[] } else { &args.paths });
|
||||||
for path in args.paths {
|
let filter = NotificationFilter::new(&args.filters, &args.ignores, gitignore)?;
|
||||||
paths.push(canonicalize(&path).map_err(|e| Error::Canonicalization(path, e))?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let gitignore = gitignore::load(if args.no_vcs_ignore { &[] } else { &paths });
|
|
||||||
let filter = NotificationFilter::new(args.filters, args.ignores, gitignore)?;
|
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let poll = args.poll.clone();
|
let poll = args.poll.clone();
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let poll_interval = args.poll_interval.clone();
|
let poll_interval = args.poll_interval.clone();
|
||||||
let watcher = Watcher::new(tx.clone(), &paths, args.poll, args.poll_interval).or_else(|err| {
|
let watcher = Watcher::new(tx.clone(), &args.paths, args.poll, args.poll_interval).or_else(|err| {
|
||||||
if poll {
|
if poll {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
@ -82,7 +95,7 @@ pub fn run<F>(args: cli::Args, cb: Option<F>) -> Result<()> where F: Fn(Vec<Path
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fallback {
|
if fallback {
|
||||||
return Watcher::new(tx, &paths, true, poll_interval);
|
return Watcher::new(tx, &args.paths, true, poll_interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,37 +106,71 @@ pub fn run<F>(args: cli::Args, cb: Option<F>) -> Result<()> where F: Fn(Vec<Path
|
|||||||
warn!("Polling for changes every {} ms", args.poll_interval);
|
warn!("Polling for changes every {} ms", args.poll_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start child process initially, if necessary
|
// Call handler initially, if necessary
|
||||||
if args.run_initially && !args.once {
|
if args.run_initially && !args.once {
|
||||||
if args.clear_screen {
|
if args.clear_screen {
|
||||||
cli::clear_screen();
|
clear_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut guard = child_process.write().unwrap();
|
if !handler.on_manual()? {
|
||||||
*guard = Some(process::spawn(&args.cmd, vec![], args.no_shell));
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Decide if callback cb function or direct execution should be used
|
|
||||||
let has_cb: bool = cb.is_some();
|
|
||||||
if has_cb {
|
|
||||||
let fcb = cb.unwrap();
|
|
||||||
loop {
|
loop {
|
||||||
debug!("Waiting for filesystem activity");
|
debug!("Waiting for filesystem activity");
|
||||||
let paths = wait_fs(&rx, &filter, args.debounce);
|
let paths = wait_fs(&rx, &filter, args.debounce);
|
||||||
if let Some(path) = paths.get(0) {
|
debug!("Paths updated: {:?}", paths);
|
||||||
debug!("Path updated: {:?}", path);
|
|
||||||
}
|
if args.clear_screen {
|
||||||
//Execute callback
|
clear_screen();
|
||||||
fcb(paths);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
loop {
|
|
||||||
debug!("Waiting for filesystem activity");
|
|
||||||
let paths = wait_fs(&rx, &filter, args.debounce);
|
|
||||||
if let Some(path) = paths.get(0) {
|
|
||||||
debug!("Path updated: {:?}", path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !handler.on_update(paths)? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExecHandler {
|
||||||
|
args: Args,
|
||||||
|
signal: Option<Signal>,
|
||||||
|
child_process: Arc<RwLock<Option<Process>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for ExecHandler {
|
||||||
|
fn new(args: Args) -> Result<Self> {
|
||||||
|
let child_process: Arc<RwLock<Option<Process>>> = Arc::new(RwLock::new(None));
|
||||||
|
let weak_child = Arc::downgrade(&child_process);
|
||||||
|
|
||||||
|
// Convert signal string to the corresponding integer
|
||||||
|
let signal = signal::new(args.signal.clone());
|
||||||
|
|
||||||
|
signal::install_handler(move |sig: Signal| {
|
||||||
|
if let Some(lock) = weak_child.upgrade() {
|
||||||
|
let strong = lock.read().unwrap();
|
||||||
|
if let Some(ref child) = *strong {
|
||||||
|
match sig {
|
||||||
|
Signal::SIGCHLD => child.reap(), // SIGCHLD is special, initiate reap()
|
||||||
|
_ => child.signal(sig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { args, signal, child_process })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_manual(&mut self) -> Result<bool> {
|
||||||
|
let mut guard = self.child_process.write()?;
|
||||||
|
*guard = Some(process::spawn(&self.args.cmd, Vec::new(), self.args.no_shell));
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_update(&mut self, ops: Vec<PathOp>) -> Result<bool> {
|
||||||
// We have three scenarios here:
|
// We have three scenarios here:
|
||||||
//
|
//
|
||||||
// 1. Make sure the previous run was ended, then run the command again
|
// 1. Make sure the previous run was ended, then run the command again
|
||||||
@ -131,74 +178,78 @@ pub fn run<F>(args: cli::Args, cb: Option<F>) -> Result<()> where F: Fn(Vec<Path
|
|||||||
// 3. Send SIGTERM to the child, wait for it to exit, then run the command again
|
// 3. Send SIGTERM to the child, wait for it to exit, then run the command again
|
||||||
// 4. Send a specified signal to the child, wait for it to exit, then run the command again
|
// 4. Send a specified signal to the child, wait for it to exit, then run the command again
|
||||||
//
|
//
|
||||||
let scenario = (args.restart, signal.is_some());
|
let scenario = (self.args.restart, self.signal.is_some());
|
||||||
|
|
||||||
match scenario {
|
match scenario {
|
||||||
// Custom restart behaviour (--restart was given, and --signal specified):
|
// Custom restart behaviour (--restart was given, and --signal specified):
|
||||||
// Send specified signal to the child, wait for it to exit, then run the command again
|
// Send specified signal to the child, wait for it to exit, then run the command again
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
signal_process(&child_process, signal, true);
|
signal_process(&self.child_process, self.signal, true);
|
||||||
|
|
||||||
// Launch child process
|
// Launch child process
|
||||||
if args.clear_screen {
|
if self.args.clear_screen {
|
||||||
cli::clear_screen();
|
clear_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Launching child process");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
let mut guard = self.child_process.write()?;
|
||||||
*guard = Some(process::spawn(&args.cmd, paths, args.no_shell));
|
*guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default restart behaviour (--restart was given, but --signal wasn't specified):
|
// Default restart behaviour (--restart was given, but --signal wasn't specified):
|
||||||
// Send SIGTERM to the child, wait for it to exit, then run the command again
|
// Send SIGTERM to the child, wait for it to exit, then run the command again
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
let sigterm = signal::new(Some("SIGTERM".to_owned()));
|
let sigterm = signal::new(Some("SIGTERM".into()));
|
||||||
signal_process(&child_process, sigterm, true);
|
signal_process(&self.child_process, sigterm, true);
|
||||||
|
|
||||||
// Launch child process
|
// Launch child process
|
||||||
if args.clear_screen {
|
if self.args.clear_screen {
|
||||||
cli::clear_screen();
|
clear_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Launching child process");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
let mut guard = self.child_process.write()?;
|
||||||
*guard = Some(process::spawn(&args.cmd, paths, args.no_shell));
|
*guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIGHUP scenario: --signal was given, but --restart was not
|
// SIGHUP scenario: --signal was given, but --restart was not
|
||||||
// Just send a signal (e.g. SIGHUP) to the child, do nothing more
|
// Just send a signal (e.g. SIGHUP) to the child, do nothing more
|
||||||
(false, true) => signal_process(&child_process, signal, false),
|
(false, true) => signal_process(&self.child_process, self.signal, false),
|
||||||
|
|
||||||
// Default behaviour (neither --signal nor --restart specified):
|
// Default behaviour (neither --signal nor --restart specified):
|
||||||
// Make sure the previous run was ended, then run the command again
|
// Make sure the previous run was ended, then run the command again
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
signal_process(&child_process, None, true);
|
signal_process(&self.child_process, None, true);
|
||||||
|
|
||||||
// Launch child process
|
// Launch child process
|
||||||
if args.clear_screen {
|
if self.args.clear_screen {
|
||||||
cli::clear_screen();
|
clear_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Launching child process");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
let mut guard = self.child_process.write()?;
|
||||||
*guard = Some(process::spawn(&args.cmd, paths, args.no_shell));
|
*guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle once option for integration testing
|
// Handle once option for integration testing
|
||||||
if args.once {
|
if self.args.once {
|
||||||
signal_process(&child_process, signal, false);
|
signal_process(&self.child_process, self.signal, false);
|
||||||
break;
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
pub fn run(args: Args) -> Result<()> {
|
||||||
|
watch::<ExecHandler>(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) -> Vec<PathOp> {
|
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) -> Vec<PathOp> {
|
||||||
|
Loading…
Reference in New Issue
Block a user