2016-10-29 16:21:04 +02:00
|
|
|
use std::path::MAIN_SEPARATOR;
|
2016-10-26 17:01:55 +02:00
|
|
|
use std::process::Command;
|
2016-10-24 02:07:20 +02:00
|
|
|
|
2017-03-26 18:17:58 +02:00
|
|
|
use clap::{App, Arg, Error};
|
2016-10-24 02:07:20 +02:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Args {
|
|
|
|
pub cmd: String,
|
2017-02-04 22:18:02 +01:00
|
|
|
pub paths: Vec<String>,
|
2016-10-24 02:07:20 +02:00
|
|
|
pub filters: Vec<String>,
|
|
|
|
pub ignores: Vec<String>,
|
|
|
|
pub clear_screen: bool,
|
2017-03-23 23:50:39 +01:00
|
|
|
pub signal: Option<String>,
|
2016-10-24 02:07:20 +02:00
|
|
|
pub restart: bool,
|
2017-09-07 01:55:58 +02:00
|
|
|
pub debounce: u64,
|
2016-10-24 02:07:20 +02:00
|
|
|
pub debug: bool,
|
|
|
|
pub run_initially: bool,
|
2017-04-06 22:31:52 +02:00
|
|
|
pub no_shell: bool,
|
2016-10-24 02:07:20 +02:00
|
|
|
pub no_vcs_ignore: bool,
|
2017-01-27 21:27:44 +01:00
|
|
|
pub once: bool,
|
2016-10-24 02:07:20 +02:00
|
|
|
pub poll: bool,
|
|
|
|
pub poll_interval: u32,
|
|
|
|
}
|
|
|
|
|
2016-10-26 17:01:55 +02:00
|
|
|
#[cfg(target_family = "windows")]
|
|
|
|
pub fn clear_screen() {
|
2017-12-06 18:40:04 +01:00
|
|
|
let _ = Command::new("cmd").arg("/c").arg("cls").status();
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_family = "unix")]
|
|
|
|
pub fn clear_screen() {
|
2017-11-21 06:55:58 +01:00
|
|
|
let _ = Command::new("tput").arg("reset").status();
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-30 17:37:34 +01:00
|
|
|
#[allow(unknown_lints)]
|
|
|
|
#[allow(or_fun_call)]
|
2016-10-24 02:07:20 +02:00
|
|
|
pub fn get_args() -> Args {
|
2017-04-02 21:13:44 +02:00
|
|
|
let args = App::new("watchexec")
|
|
|
|
.version(crate_version!())
|
|
|
|
.about("Execute commands when watched files change")
|
|
|
|
.arg(Arg::with_name("command")
|
|
|
|
.help("Command to execute")
|
|
|
|
.multiple(true)
|
|
|
|
.required(true))
|
|
|
|
.arg(Arg::with_name("extensions")
|
|
|
|
.help("Comma-separated list of file extensions to watch (js,css,html)")
|
|
|
|
.short("e")
|
|
|
|
.long("exts")
|
|
|
|
.takes_value(true))
|
|
|
|
.arg(Arg::with_name("path")
|
|
|
|
.help("Watch a specific directory")
|
|
|
|
.short("w")
|
|
|
|
.long("watch")
|
|
|
|
.number_of_values(1)
|
|
|
|
.multiple(true)
|
|
|
|
.takes_value(true))
|
|
|
|
.arg(Arg::with_name("clear")
|
|
|
|
.help("Clear screen before executing command")
|
|
|
|
.short("c")
|
|
|
|
.long("clear"))
|
|
|
|
.arg(Arg::with_name("restart")
|
|
|
|
.help("Restart the process if it's still running")
|
|
|
|
.short("r")
|
|
|
|
.long("restart"))
|
|
|
|
.arg(Arg::with_name("signal")
|
|
|
|
.help("Send signal to process upon changes, e.g. SIGHUP")
|
|
|
|
.short("s")
|
|
|
|
.long("signal")
|
|
|
|
.takes_value(true)
|
|
|
|
.number_of_values(1)
|
|
|
|
.value_name("signal"))
|
|
|
|
.arg(Arg::with_name("kill")
|
|
|
|
.help("Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)")
|
|
|
|
.short("k")
|
|
|
|
.long("kill"))
|
2017-09-07 01:55:58 +02:00
|
|
|
.arg(Arg::with_name("debounce")
|
|
|
|
.help("Set the timeout between detected change and command execution, defaults to 500ms")
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("milliseconds")
|
2017-04-02 21:13:44 +02:00
|
|
|
.short("d")
|
2017-09-07 01:55:58 +02:00
|
|
|
.long("debounce"))
|
|
|
|
.arg(Arg::with_name("verbose")
|
|
|
|
.help("Print debugging messages to stderr")
|
|
|
|
.short("v")
|
|
|
|
.long("verbose"))
|
2017-04-02 21:13:44 +02:00
|
|
|
.arg(Arg::with_name("filter")
|
|
|
|
.help("Ignore all modifications except those matching the pattern")
|
|
|
|
.short("f")
|
|
|
|
.long("filter")
|
|
|
|
.number_of_values(1)
|
|
|
|
.multiple(true)
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("pattern"))
|
|
|
|
.arg(Arg::with_name("ignore")
|
|
|
|
.help("Ignore modifications to paths matching the pattern")
|
|
|
|
.short("i")
|
|
|
|
.long("ignore")
|
|
|
|
.number_of_values(1)
|
|
|
|
.multiple(true)
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("pattern"))
|
|
|
|
.arg(Arg::with_name("no-vcs-ignore")
|
|
|
|
.help("Skip auto-loading of .gitignore files for filtering")
|
|
|
|
.long("no-vcs-ignore"))
|
2017-04-25 21:56:21 +02:00
|
|
|
.arg(Arg::with_name("no-default-ignore")
|
|
|
|
.help("Skip auto-ignoring of commonly ignored globs")
|
|
|
|
.long("no-default-ignore"))
|
2017-04-02 21:13:44 +02:00
|
|
|
.arg(Arg::with_name("postpone")
|
|
|
|
.help("Wait until first change to execute command")
|
|
|
|
.short("p")
|
|
|
|
.long("postpone"))
|
|
|
|
.arg(Arg::with_name("poll")
|
|
|
|
.help("Forces polling mode")
|
|
|
|
.long("force-poll")
|
|
|
|
.value_name("interval"))
|
2017-04-06 22:31:52 +02:00
|
|
|
.arg(Arg::with_name("no-shell")
|
2017-04-06 23:04:57 +02:00
|
|
|
.help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'")
|
2017-04-06 22:31:52 +02:00
|
|
|
.short("n")
|
|
|
|
.long("no-shell"))
|
2017-04-02 21:13:44 +02:00
|
|
|
.arg(Arg::with_name("once").short("1").hidden(true))
|
|
|
|
.get_matches();
|
|
|
|
|
|
|
|
let cmd = values_t!(args.values_of("command"), String)
|
|
|
|
.unwrap()
|
|
|
|
.join(" ");
|
2017-02-04 22:18:02 +01:00
|
|
|
let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]);
|
2017-04-02 21:13:44 +02:00
|
|
|
|
|
|
|
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)
|
2017-04-10 00:17:03 +02:00
|
|
|
let signal = if args.is_present("kill") {
|
|
|
|
Some("SIGKILL".to_string())
|
|
|
|
} else {
|
|
|
|
// Convert Option<&str> to Option<String>
|
|
|
|
args.value_of("signal").map(str::to_string)
|
2017-04-02 21:13:44 +02:00
|
|
|
};
|
2017-03-13 18:55:12 +01:00
|
|
|
|
2016-10-24 02:07:20 +02:00
|
|
|
let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]);
|
|
|
|
|
|
|
|
if let Some(extensions) = args.values_of("extensions") {
|
|
|
|
for exts in extensions {
|
2016-10-26 17:01:55 +02:00
|
|
|
filters.extend(exts.split(',')
|
2017-04-02 21:13:44 +02:00
|
|
|
.filter(|ext| !ext.is_empty())
|
|
|
|
.map(|ext| format!("*.{}", ext.replace(".", ""))));
|
2016-10-24 02:07:20 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut ignores = vec![];
|
2017-10-07 21:50:47 +02:00
|
|
|
let default_ignores = vec![format!("**{}.DS_Store", MAIN_SEPARATOR),
|
2016-10-30 17:28:54 +01:00
|
|
|
String::from("*.pyc"),
|
2018-02-08 19:21:06 +01:00
|
|
|
String::from("#*#"),
|
|
|
|
String::from(".#*"),
|
2018-02-08 19:16:01 +01:00
|
|
|
String::from(".*.sw?"),
|
|
|
|
String::from(".*.sw?x"),
|
2017-10-07 21:50:47 +02:00
|
|
|
format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
|
|
|
format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR),
|
|
|
|
format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR)];
|
2016-10-24 02:07:20 +02:00
|
|
|
|
2017-04-25 21:56:21 +02:00
|
|
|
|
|
|
|
if args.occurrences_of("no-default-ignore") == 0 {
|
|
|
|
ignores.extend(default_ignores)
|
|
|
|
};
|
2016-10-24 02:07:20 +02:00
|
|
|
ignores.extend(values_t!(args.values_of("ignore"), String).unwrap_or(vec![]));
|
2016-10-29 16:21:04 +02:00
|
|
|
|
2016-10-24 02:07:20 +02:00
|
|
|
let poll_interval = if args.occurrences_of("poll") > 0 {
|
|
|
|
value_t!(args.value_of("poll"), u32).unwrap_or_else(|e| e.exit())
|
2016-10-24 02:12:48 +02:00
|
|
|
} else {
|
|
|
|
1000
|
|
|
|
};
|
2016-10-24 02:07:20 +02:00
|
|
|
|
2017-09-07 01:55:58 +02:00
|
|
|
let debounce = if args.occurrences_of("debounce") > 0 {
|
|
|
|
value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit())
|
|
|
|
} else {
|
|
|
|
500
|
|
|
|
};
|
|
|
|
|
2017-03-26 18:17:58 +02:00
|
|
|
if signal.is_some() && args.is_present("postpone") {
|
|
|
|
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
2017-04-10 00:17:29 +02:00
|
|
|
Error::value_validation_auto("--postpone and --signal are mutually exclusive".to_string())
|
2017-03-26 18:17:58 +02:00
|
|
|
.exit();
|
|
|
|
}
|
|
|
|
|
2017-04-02 21:13:44 +02:00
|
|
|
if signal.is_some() && args.is_present("kill") {
|
|
|
|
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
2017-04-10 00:17:29 +02:00
|
|
|
Error::value_validation_auto("--kill and --signal is ambiguous.\n Hint: Use only '--signal SIGKILL' without --kill".to_string())
|
2017-04-02 21:13:44 +02:00
|
|
|
.exit();
|
|
|
|
}
|
|
|
|
|
2016-10-24 02:07:20 +02:00
|
|
|
Args {
|
|
|
|
cmd: cmd,
|
2017-02-04 22:18:02 +01:00
|
|
|
paths: paths,
|
2016-10-24 02:07:20 +02:00
|
|
|
filters: filters,
|
|
|
|
ignores: ignores,
|
2017-03-13 18:55:12 +01:00
|
|
|
signal: signal,
|
2016-10-24 02:07:20 +02:00
|
|
|
clear_screen: args.is_present("clear"),
|
|
|
|
restart: args.is_present("restart"),
|
2017-09-07 01:55:58 +02:00
|
|
|
debounce: debounce,
|
|
|
|
debug: args.is_present("verbose"),
|
2016-10-29 16:43:45 +02:00
|
|
|
run_initially: !args.is_present("postpone"),
|
2017-04-06 22:31:52 +02:00
|
|
|
no_shell: args.is_present("no-shell"),
|
2016-10-24 02:07:20 +02:00
|
|
|
no_vcs_ignore: args.is_present("no-vcs-ignore"),
|
2017-01-27 21:27:44 +01:00
|
|
|
once: args.is_present("once"),
|
2016-10-24 02:07:20 +02:00
|
|
|
poll: args.occurrences_of("poll") > 0,
|
2016-10-24 02:12:48 +02:00
|
|
|
poll_interval: poll_interval,
|
2016-10-24 02:07:20 +02:00
|
|
|
}
|
|
|
|
}
|