mirror of https://github.com/passcod/cargo-watch
Start over for watchexec 2.0
This commit is contained in:
parent
093b443dbc
commit
2ce78572d1
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
|
@ -13,33 +13,71 @@ homepage = "https://watchexec.github.io/#cargo-watch"
|
|||
repository = "https://github.com/watchexec/cargo-watch"
|
||||
readme = "README.md"
|
||||
|
||||
edition = "2018"
|
||||
rust-version = "1.51.1"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
rust-version = "1.58.0"
|
||||
|
||||
exclude = ["/.github"]
|
||||
|
||||
[[bin]]
|
||||
name = "cargo-watch"
|
||||
|
||||
[dependencies]
|
||||
camino = "1.0.4"
|
||||
clap = "2.33.1"
|
||||
log = "0.4.14"
|
||||
shell-escape = "0.1.5"
|
||||
stderrlog = "0.5.1"
|
||||
watchexec = "1.16.1"
|
||||
# camino = "1.0.4"
|
||||
console-subscriber = { version = "0.1.0", optional = true }
|
||||
dunce = "1.0.2"
|
||||
futures = "0.3.17"
|
||||
miette = { version = "3.2.0", features = ["fancy"] }
|
||||
# shell-escape = "0.1.5"
|
||||
tracing = "0.1.26"
|
||||
watchexec = "2.0.0-pre.6"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.33.3"
|
||||
default-features = false
|
||||
features = ["wrap_help"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.15.0"
|
||||
features = [
|
||||
"fs",
|
||||
"io-std",
|
||||
"parking_lot",
|
||||
"process",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
"sync",
|
||||
]
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3.6"
|
||||
features = [
|
||||
"env-filter",
|
||||
"fmt",
|
||||
]
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1.26"
|
||||
|
||||
[target.'cfg(not(target_os="freebsd"))'.dependencies]
|
||||
notify-rust = "4.5.2"
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "1.6.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
insta = "1.7.1"
|
||||
predicates = "2.0.0"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
[features]
|
||||
dev-console = ["console-subscriber"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
debug = 1 # for stack traces
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
[package.metadata.binstall]
|
||||
|
|
457
src/args.rs
457
src/args.rs
|
@ -1,238 +1,225 @@
|
|||
use clap::{App, AppSettings, Arg, ArgMatches, ErrorKind, SubCommand};
|
||||
use std::{env, process};
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub fn parse() -> ArgMatches<'static> {
|
||||
let footnote = "You can use the `-- command` style instead, note you'll need to use full commands, it won't prefix `cargo` for you.\n\nBy default, your entire project is watched, except for the target/ and .git/ folders, and your .ignore and .gitignore files are used to filter paths.".to_owned();
|
||||
use clap::{crate_version, App, Arg, ArgMatches};
|
||||
use miette::{Context, IntoDiagnostic, Result};
|
||||
|
||||
#[cfg(windows)] let footnote = format!("{}\n\nOn Windows, patterns given to -i have forward slashes (/) automatically converted to backward ones (\\) to ease command portability.", footnote);
|
||||
|
||||
let mut app = App::new(env!("CARGO_PKG_NAME"))
|
||||
.bin_name("cargo")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.help_message("")
|
||||
.version_message("")
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.setting(AppSettings::DisableHelpSubcommand)
|
||||
.setting(AppSettings::DontCollapseArgsInUsage)
|
||||
.setting(AppSettings::GlobalVersion)
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("watch")
|
||||
.author(env!("CARGO_PKG_HOMEPAGE"))
|
||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.usage("cargo watch [FLAGS] [OPTIONS]")
|
||||
.help_message("Display this message")
|
||||
.version_message("Display version information")
|
||||
.arg(
|
||||
Arg::with_name("once")
|
||||
.long("testing-only--once")
|
||||
.hidden(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clear")
|
||||
.short("c")
|
||||
.long("clear")
|
||||
.help("Clear the screen before each run"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log:debug")
|
||||
.long("debug")
|
||||
.help("Show debug output"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log:info")
|
||||
.long("why")
|
||||
.help("Show paths that changed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore-nothing")
|
||||
.long("ignore-nothing")
|
||||
.help("Ignore nothing, not even target/ and .git/"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-gitignore")
|
||||
.long("no-gitignore")
|
||||
.help("Don’t use .gitignore files"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore")
|
||||
.long("no-ignore")
|
||||
.help("Don’t use .ignore files"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-restart")
|
||||
.long("no-restart")
|
||||
.help("Don’t restart command while it’s still running"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("packages:all")
|
||||
.long("all")
|
||||
.conflicts_with("packages:one")
|
||||
.hidden(true)
|
||||
.help("Reserved for workspace support"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("poll")
|
||||
.long("poll")
|
||||
.help("Force use of polling for file changes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("postpone")
|
||||
.long("postpone")
|
||||
.help("Postpone first run until a file changes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("watch-when-idle")
|
||||
.long("watch-when-idle")
|
||||
.help("Ignore events emitted while the commands run. Will become default behaviour in 8.0."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("features")
|
||||
.long("features")
|
||||
.takes_value(true)
|
||||
.help("List of features passed to cargo invocations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log:quiet")
|
||||
.short("q")
|
||||
.long("quiet")
|
||||
.help("Suppress output from cargo-watch itself"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("cmd:cargo")
|
||||
.short("x")
|
||||
.long("exec")
|
||||
.takes_value(true)
|
||||
.value_name("cmd")
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.min_values(1)
|
||||
.number_of_values(1)
|
||||
.help("Cargo command(s) to execute on changes [default: check]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("cmd:shell")
|
||||
.short("s")
|
||||
.long("shell")
|
||||
.takes_value(true)
|
||||
.value_name("cmd")
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.min_values(1)
|
||||
.number_of_values(1)
|
||||
.help("Shell command(s) to execute on changes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("delay")
|
||||
.short("d")
|
||||
.long("delay")
|
||||
.takes_value(true)
|
||||
.empty_values(false)
|
||||
.default_value("0.5")
|
||||
.help("File updates debounce delay in seconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore")
|
||||
.short("i")
|
||||
.long("ignore")
|
||||
.takes_value(true)
|
||||
.value_name("pattern")
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.min_values(1)
|
||||
.number_of_values(1)
|
||||
.help("Ignore a glob/gitignore-style pattern"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("packages:one")
|
||||
.short("p")
|
||||
.long("package")
|
||||
.takes_value(true)
|
||||
.value_name("spec")
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.min_values(1)
|
||||
.hidden(true)
|
||||
.help("Reserved for workspace support"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("watch")
|
||||
.short("w")
|
||||
.long("watch")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.min_values(1)
|
||||
.number_of_values(1)
|
||||
.default_value(".")
|
||||
.help("Watch specific file(s) or folder(s)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("use-shell")
|
||||
.long("use-shell")
|
||||
.takes_value(true)
|
||||
.help(if cfg!(windows) {
|
||||
"Use a different shell. Try --use-shell=powershell, which will become the default in 8.0."
|
||||
} else {
|
||||
"Use a different shell. E.g. --use-shell=bash"
|
||||
}),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("workdir")
|
||||
.short("C")
|
||||
.long("workdir")
|
||||
.takes_value(true)
|
||||
.help("Change working directory before running command [default: crate root]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("notif")
|
||||
.help("Send a desktop notification when watchexec notices a change (experimental, behaviour may change)")
|
||||
.short("N")
|
||||
.long("notify")
|
||||
.hidden(cfg!(target_os="freebsd"))
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rust-backtrace")
|
||||
.help("Inject RUST_BACKTRACE=VALUE (generally you want to set it to 1) into the environment")
|
||||
.short("B")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("cmd:trail")
|
||||
.raw(true)
|
||||
.help("Full command to run. -x and -s will be ignored!"),
|
||||
)
|
||||
.after_help(footnote.as_str()),
|
||||
);
|
||||
|
||||
// Allow invocation of cargo-watch with both `cargo-watch watch ARGS`
|
||||
// (as invoked by cargo) and `cargo-watch ARGS`.
|
||||
let mut args: Vec<String> = env::args().collect();
|
||||
args.insert(1, "watch".into());
|
||||
|
||||
let matches = match app.get_matches_from_safe_borrow(args) {
|
||||
Ok(matches) => matches,
|
||||
Err(err) => {
|
||||
match err.kind {
|
||||
ErrorKind::HelpDisplayed => {
|
||||
println!("{}", err);
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
ErrorKind::VersionDisplayed => {
|
||||
// Unlike HelpDisplayed, VersionDisplayed emits the output
|
||||
// by itself (clap-rs/clap#1390). It also does so without a
|
||||
// trailing newline, so we print one ourselves.
|
||||
println!();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
_ => app.get_matches(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
matches.subcommand.unwrap().matches
|
||||
trait Clap3Compat {
|
||||
/// Does nothing for clap2, but remove this trait for clap3, and get cool new option groups!
|
||||
fn help_heading(self, _heading: impl Into<Option<&'static str>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clap3Compat for Arg<'_, '_> {}
|
||||
|
||||
const OPTSET_FILTERING: &str = "Filtering options:";
|
||||
const OPTSET_COMMAND: &str = "Command options:";
|
||||
const OPTSET_CONFIG: &str = "Config file options:";
|
||||
const OPTSET_DEBUGGING: &str = "Debugging options:";
|
||||
const OPTSET_OUTPUT: &str = "Output options:";
|
||||
const OPTSET_BEHAVIOUR: &str = "Behaviour options:";
|
||||
|
||||
pub fn get_args() -> Result<ArgMatches<'static>> {
|
||||
let app = App::new("watchexec")
|
||||
.version(crate_version!())
|
||||
.about("Execute commands when watched files change")
|
||||
.after_help("Use @argfile as first argument to load arguments from the file `argfile` (one argument per line) which will be inserted in place of the @argfile (further arguments on the CLI will override or add onto those in the file).")
|
||||
.arg(Arg::with_name("config-file")
|
||||
.help_heading(Some(OPTSET_CONFIG))
|
||||
.help("Config file(s) to use")
|
||||
.multiple(true)
|
||||
.short("C")
|
||||
.long("config"))
|
||||
.arg(Arg::with_name("command")
|
||||
.help_heading(Some(OPTSET_COMMAND))
|
||||
.help("Command to execute")
|
||||
.multiple(true)
|
||||
.required(true))
|
||||
.arg(Arg::with_name("paths")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Watch a specific file or directory")
|
||||
.short("w")
|
||||
.long("watch")
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("clear")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Clear screen before executing command")
|
||||
.short("c")
|
||||
.long("clear"))
|
||||
.arg(Arg::with_name("on-busy-update")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Select the behaviour to use when receiving events while the command is running. Current default is queue, will change to do-nothing in 2.0.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["do-nothing", "queue", "restart", "signal"])
|
||||
.long("on-busy-update"))
|
||||
.arg(Arg::with_name("restart")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Restart the process if it's still running. Shorthand for --on-busy-update=restart")
|
||||
.short("r")
|
||||
.long("restart"))
|
||||
.arg(Arg::with_name("signal")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Specify the signal to send when using --on-busy-update=signal")
|
||||
.short("s")
|
||||
.long("signal")
|
||||
.takes_value(true)
|
||||
.value_name("signal")
|
||||
.default_value("SIGTERM")
|
||||
.hidden(cfg!(windows)))
|
||||
.arg(Arg::with_name("kill")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.hidden(true)
|
||||
.short("k")
|
||||
.long("kill"))
|
||||
.arg(Arg::with_name("debounce")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Set the timeout between detected change and command execution, defaults to 50ms")
|
||||
.takes_value(true)
|
||||
.value_name("milliseconds")
|
||||
.short("d")
|
||||
.long("debounce"))
|
||||
.arg(Arg::with_name("verbose")
|
||||
.help_heading(Some(OPTSET_DEBUGGING))
|
||||
.help("Print debugging messages (-v, -vv, -vvv, -vvvv; use -vvv for bug reports)")
|
||||
.multiple(true)
|
||||
.short("v")
|
||||
.long("verbose"))
|
||||
.arg(Arg::with_name("print-events")
|
||||
.help_heading(Some(OPTSET_DEBUGGING))
|
||||
.help("Print events that trigger actions")
|
||||
.long("print-events")
|
||||
.alias("changes-only")) // --changes-only is deprecated (remove at v2)
|
||||
.arg(Arg::with_name("no-vcs-ignore")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Skip auto-loading of VCS (Git, etc) ignore files")
|
||||
.long("no-vcs-ignore"))
|
||||
.arg(Arg::with_name("no-project-ignore")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Skip auto-loading of project ignore files (.gitignore, .ignore, etc)")
|
||||
.long("no-project-ignore")
|
||||
.alias("no-ignore")) // --no-ignore is deprecated (remove at v2)
|
||||
.arg(Arg::with_name("no-default-ignore")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Skip auto-ignoring of commonly ignored globs")
|
||||
.long("no-default-ignore"))
|
||||
.arg(Arg::with_name("no-global-ignore")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Skip auto-loading of global or environment-wide ignore files")
|
||||
.long("no-global-ignore"))
|
||||
.arg(Arg::with_name("postpone")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Wait until first change to execute command")
|
||||
.short("p")
|
||||
.long("postpone"))
|
||||
.arg(Arg::with_name("poll")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Force polling mode (interval in milliseconds)")
|
||||
.long("force-poll")
|
||||
.value_name("interval"))
|
||||
.arg(Arg::with_name("shell")
|
||||
.help_heading(Some(OPTSET_COMMAND))
|
||||
.help(if cfg!(windows) {
|
||||
"Use a different shell, or `none`. Try --shell=powershell, which will become the default in 2.0."
|
||||
} else {
|
||||
"Use a different shell, or `none`. Defaults to `sh` (until 2.0, where that will change to `$SHELL`). E.g. --shell=bash"
|
||||
})
|
||||
.takes_value(true)
|
||||
.long("shell"))
|
||||
// -n short form will not be removed, and instead become a shorthand for --shell=none
|
||||
.arg(Arg::with_name("no-shell")
|
||||
.help_heading(Some(OPTSET_COMMAND))
|
||||
.help("Do not wrap command in a shell. Deprecated: use --shell=none instead.")
|
||||
.short("n")
|
||||
.long("no-shell"))
|
||||
.arg(Arg::with_name("no-environment")
|
||||
.help_heading(Some(OPTSET_OUTPUT))
|
||||
.help("Do not set WATCHEXEC_*_PATH environment variables for the command")
|
||||
.long("no-environment"))
|
||||
.arg(Arg::with_name("no-process-group")
|
||||
.help_heading(Some(OPTSET_COMMAND))
|
||||
.help("Do not use a process group when running the command")
|
||||
.long("no-process-group"))
|
||||
.arg(Arg::with_name("once").short("1").hidden(true))
|
||||
.arg(Arg::with_name("watch-when-idle")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Deprecated alias for --on-busy-update=do-nothing, which will become the default in 2.0.")
|
||||
.short("W")
|
||||
.hidden(true)
|
||||
.long("watch-when-idle"))
|
||||
.arg(Arg::with_name("notif")
|
||||
.help_heading(Some(OPTSET_OUTPUT))
|
||||
.help("Send a desktop notification when the command ends")
|
||||
.short("N")
|
||||
.long("notify"))
|
||||
.arg(
|
||||
Arg::with_name("extensions")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Comma-separated list of file extensions to watch (e.g. js,css,html)")
|
||||
.short("e")
|
||||
.long("exts")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("filter")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.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_heading(Some(OPTSET_FILTERING))
|
||||
.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-meta")
|
||||
.help_heading(Some(OPTSET_FILTERING))
|
||||
.help("Ignore metadata changes")
|
||||
.long("no-meta"),
|
||||
);
|
||||
|
||||
let mut raw_args: Vec<OsString> = env::args_os().collect();
|
||||
|
||||
if let Some(first) = raw_args.get(1).and_then(|s| s.to_str()) {
|
||||
if let Some(arg_path) = first.strip_prefix('@').map(Path::new) {
|
||||
let arg_file = BufReader::new(
|
||||
File::open(arg_path)
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| format!("Failed to open argument file {:?}", arg_path))?,
|
||||
);
|
||||
|
||||
let mut more_args: Vec<OsString> = arg_file
|
||||
.lines()
|
||||
.map(|l| l.map(OsString::from).into_diagnostic())
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
more_args.insert(0, raw_args.remove(0));
|
||||
more_args.extend(raw_args.into_iter().skip(1));
|
||||
raw_args = more_args;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(app.get_matches_from(raw_args))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
mod init;
|
||||
mod runtime;
|
||||
|
||||
pub use init::init;
|
||||
pub use runtime::runtime;
|
|
@ -0,0 +1,22 @@
|
|||
use std::convert::Infallible;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use miette::Result;
|
||||
use watchexec::{config::InitConfig, handler::SyncFnHandler};
|
||||
|
||||
pub fn init(_args: &ArgMatches<'static>) -> Result<InitConfig> {
|
||||
let mut config = InitConfig::default();
|
||||
config.on_error(SyncFnHandler::from(
|
||||
|data| -> std::result::Result<(), Infallible> {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[[{:?}]]", data);
|
||||
} else {
|
||||
eprintln!("[[{}]]", data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
));
|
||||
|
||||
Ok(config)
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
use std::{convert::Infallible, env::current_dir, path::Path, str::FromStr, time::Duration};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use notify_rust::Notification;
|
||||
use watchexec::{
|
||||
action::{Action, Outcome, PostSpawn, PreSpawn},
|
||||
command::Shell,
|
||||
config::RuntimeConfig,
|
||||
event::ProcessEnd,
|
||||
fs::Watcher,
|
||||
handler::SyncFnHandler,
|
||||
paths::summarise_events_to_env,
|
||||
signal::{process::SubSignal, source::MainSignal},
|
||||
};
|
||||
|
||||
pub fn runtime(args: &ArgMatches<'static>) -> Result<RuntimeConfig> {
|
||||
let mut config = RuntimeConfig::default();
|
||||
|
||||
config.command(
|
||||
args.values_of_lossy("command")
|
||||
.expect("(clap) Bug: command is not present")
|
||||
.iter(),
|
||||
);
|
||||
|
||||
config.pathset(match args.values_of_os("paths") {
|
||||
Some(paths) => paths.map(|os| Path::new(os).to_owned()).collect(),
|
||||
None => vec![current_dir().into_diagnostic()?],
|
||||
});
|
||||
|
||||
config.action_throttle(Duration::from_millis(
|
||||
args.value_of("debounce")
|
||||
.unwrap_or("50")
|
||||
.parse()
|
||||
.into_diagnostic()?,
|
||||
));
|
||||
|
||||
if let Some(interval) = args.value_of("poll") {
|
||||
config.file_watcher(Watcher::Poll(Duration::from_millis(
|
||||
interval.parse().into_diagnostic()?,
|
||||
)));
|
||||
}
|
||||
|
||||
if args.is_present("no-process-group") {
|
||||
config.command_grouped(false);
|
||||
}
|
||||
|
||||
config.command_shell(if args.is_present("no-shell") {
|
||||
Shell::None
|
||||
} else if let Some(s) = args.value_of("shell") {
|
||||
if s.eq_ignore_ascii_case("powershell") {
|
||||
Shell::Powershell
|
||||
} else if s.eq_ignore_ascii_case("none") {
|
||||
Shell::None
|
||||
} else if s.eq_ignore_ascii_case("cmd") {
|
||||
cmd_shell(s.into())
|
||||
} else {
|
||||
Shell::Unix(s.into())
|
||||
}
|
||||
} else {
|
||||
default_shell()
|
||||
});
|
||||
|
||||
let clear = args.is_present("clear");
|
||||
let notif = args.is_present("notif");
|
||||
let mut on_busy = args
|
||||
.value_of("on-busy-update")
|
||||
.unwrap_or("queue")
|
||||
.to_owned();
|
||||
|
||||
if args.is_present("restart") {
|
||||
on_busy = "restart".into();
|
||||
}
|
||||
|
||||
if args.is_present("watch-when-idle") {
|
||||
on_busy = "do-nothing".into();
|
||||
}
|
||||
|
||||
let mut signal = args
|
||||
.value_of("signal")
|
||||
.map(SubSignal::from_str)
|
||||
.transpose()
|
||||
.into_diagnostic()?
|
||||
.unwrap_or(SubSignal::Terminate);
|
||||
|
||||
if args.is_present("kill") {
|
||||
signal = SubSignal::ForceStop;
|
||||
}
|
||||
|
||||
let print_events = args.is_present("print-events");
|
||||
let once = args.is_present("once");
|
||||
|
||||
config.on_action(move |action: Action| {
|
||||
let fut = async { Ok::<(), Infallible>(()) };
|
||||
|
||||
if print_events {
|
||||
for (n, event) in action.events.iter().enumerate() {
|
||||
eprintln!("[EVENT {}] {}", n, event);
|
||||
}
|
||||
}
|
||||
|
||||
if once {
|
||||
action.outcome(Outcome::both(Outcome::Start, Outcome::wait(Outcome::Exit)));
|
||||
return fut;
|
||||
}
|
||||
|
||||
let signals: Vec<MainSignal> = action.events.iter().flat_map(|e| e.signals()).collect();
|
||||
let has_paths = action
|
||||
.events
|
||||
.iter()
|
||||
.flat_map(|e| e.paths())
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if signals.contains(&MainSignal::Terminate) {
|
||||
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
|
||||
return fut;
|
||||
}
|
||||
|
||||
if signals.contains(&MainSignal::Interrupt) {
|
||||
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
|
||||
return fut;
|
||||
}
|
||||
|
||||
if !has_paths {
|
||||
if !signals.is_empty() {
|
||||
let mut out = Outcome::DoNothing;
|
||||
for sig in signals {
|
||||
out = Outcome::both(out, Outcome::Signal(sig.into()));
|
||||
}
|
||||
|
||||
action.outcome(out);
|
||||
return fut;
|
||||
}
|
||||
|
||||
let completion = action.events.iter().flat_map(|e| e.completions()).next();
|
||||
if let Some(status) = completion {
|
||||
let (msg, printit) = match status {
|
||||
Some(ProcessEnd::ExitError(code)) => {
|
||||
(format!("Command exited with {}", code), true)
|
||||
}
|
||||
Some(ProcessEnd::ExitSignal(sig)) => {
|
||||
(format!("Command killed by {:?}", sig), true)
|
||||
}
|
||||
Some(ProcessEnd::ExitStop(sig)) => {
|
||||
(format!("Command stopped by {:?}", sig), true)
|
||||
}
|
||||
Some(ProcessEnd::Continued) => ("Command continued".to_string(), true),
|
||||
Some(ProcessEnd::Exception(ex)) => {
|
||||
(format!("Command ended by exception {:#x}", ex), true)
|
||||
}
|
||||
Some(ProcessEnd::Success) => ("Command was successful".to_string(), false),
|
||||
None => ("Command completed".to_string(), false),
|
||||
};
|
||||
|
||||
if printit {
|
||||
eprintln!("[[{}]]", msg);
|
||||
}
|
||||
|
||||
if notif {
|
||||
Notification::new()
|
||||
.summary("Watchexec: command ended")
|
||||
.body(&msg)
|
||||
.show()
|
||||
.map(drop)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("[[Failed to send desktop notification: {}]]", err);
|
||||
});
|
||||
}
|
||||
|
||||
action.outcome(Outcome::DoNothing);
|
||||
return fut;
|
||||
}
|
||||
}
|
||||
|
||||
let when_running = match (clear, on_busy.as_str()) {
|
||||
(_, "do-nothing") => Outcome::DoNothing,
|
||||
(true, "restart") => {
|
||||
Outcome::both(Outcome::Stop, Outcome::both(Outcome::Clear, Outcome::Start))
|
||||
}
|
||||
(false, "restart") => Outcome::both(Outcome::Stop, Outcome::Start),
|
||||
(_, "signal") => Outcome::Signal(signal),
|
||||
(true, "queue") => Outcome::wait(Outcome::both(Outcome::Clear, Outcome::Start)),
|
||||
(false, "queue") => Outcome::wait(Outcome::Start),
|
||||
_ => Outcome::DoNothing,
|
||||
};
|
||||
|
||||
let when_idle = if clear {
|
||||
Outcome::both(Outcome::Clear, Outcome::Start)
|
||||
} else {
|
||||
Outcome::Start
|
||||
};
|
||||
|
||||
action.outcome(Outcome::if_running(when_running, when_idle));
|
||||
|
||||
fut
|
||||
});
|
||||
|
||||
let no_env = args.is_present("no-environment");
|
||||
config.on_pre_spawn(move |prespawn: PreSpawn| async move {
|
||||
if !no_env {
|
||||
let envs = summarise_events_to_env(prespawn.events.iter());
|
||||
if let Some(mut command) = prespawn.command().await {
|
||||
for (k, v) in envs {
|
||||
command.env(format!("WATCHEXEC_{}_PATH", k), v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), Infallible>(())
|
||||
});
|
||||
|
||||
config.on_post_spawn(SyncFnHandler::from(move |postspawn: PostSpawn| {
|
||||
if notif {
|
||||
Notification::new()
|
||||
.summary("Watchexec: change detected")
|
||||
.body(&format!("Running `{}`", postspawn.command.join(" ")))
|
||||
.show()
|
||||
.map(drop)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("[[Failed to send desktop notification: {}]]", err);
|
||||
});
|
||||
}
|
||||
|
||||
Ok::<(), Infallible>(())
|
||||
}));
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
// until 2.0, then Powershell
|
||||
#[cfg(windows)]
|
||||
fn default_shell() -> Shell {
|
||||
Shell::Cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn default_shell() -> Shell {
|
||||
Shell::default()
|
||||
}
|
||||
|
||||
// because Shell::Cmd is only on windows
|
||||
#[cfg(windows)]
|
||||
fn cmd_shell(_: String) -> Shell {
|
||||
Shell::Cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn cmd_shell(s: String) -> Shell {
|
||||
Shell::Unix(s)
|
||||
}
|
93
src/main.rs
93
src/main.rs
|
@ -1,50 +1,59 @@
|
|||
use camino::Utf8PathBuf;
|
||||
use stderrlog::Timestamp;
|
||||
use watchexec::{error::Result, run::watch};
|
||||
use std::env::var;
|
||||
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use watchexec::{event::Event, Watchexec};
|
||||
|
||||
mod args;
|
||||
mod options;
|
||||
mod root;
|
||||
mod watch;
|
||||
mod config;
|
||||
// mod filterer;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let matches = args::parse();
|
||||
#[cfg(target_env = "musl")]
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
let debug = matches.is_present("log:debug");
|
||||
let info = matches.is_present("log:info");
|
||||
let quiet = matches.is_present("log:quiet");
|
||||
let testing = matches.is_present("once");
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
#[cfg(feature = "dev-console")]
|
||||
console_subscriber::init();
|
||||
|
||||
stderrlog::new()
|
||||
.quiet(quiet)
|
||||
.show_module_names(debug)
|
||||
.verbosity(if debug {
|
||||
3
|
||||
} else if info {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
})
|
||||
.timestamp(if testing {
|
||||
Timestamp::Off
|
||||
} else {
|
||||
Timestamp::Millisecond
|
||||
})
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
root::change_dir(
|
||||
matches
|
||||
.value_of("workdir")
|
||||
.map(Utf8PathBuf::from)
|
||||
.unwrap_or_else(root::project_root),
|
||||
);
|
||||
|
||||
if let Some(b) = matches.value_of("rust-backtrace") {
|
||||
std::env::set_var("RUST_BACKTRACE", b);
|
||||
if var("RUST_LOG").is_ok() && cfg!(not(feature = "dev-console")) {
|
||||
tracing_subscriber::fmt::init();
|
||||
}
|
||||
|
||||
let opts = options::get_options(&matches);
|
||||
let handler = watch::CwHandler::new(opts, quiet, matches.is_present("notif"))?;
|
||||
watch(&handler)
|
||||
let args = args::get_args()?;
|
||||
|
||||
{
|
||||
let verbosity = args.occurrences_of("verbose");
|
||||
let mut builder = tracing_subscriber::fmt().with_env_filter(match verbosity {
|
||||
0 => "cargo-watch=warn",
|
||||
1 => "watchexec=debug,cargo-watch=debug",
|
||||
2 => "watchexec=trace,cargo-watch=trace",
|
||||
_ => "trace",
|
||||
});
|
||||
|
||||
if verbosity > 2 {
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
builder = builder.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE);
|
||||
}
|
||||
|
||||
if verbosity > 3 {
|
||||
builder.pretty().try_init().ok();
|
||||
} else {
|
||||
builder.try_init().ok();
|
||||
}
|
||||
}
|
||||
|
||||
let init = config::init(&args)?;
|
||||
let runtime = config::runtime(&args)?;
|
||||
// runtime.filterer(filterer::new(&args).await?);
|
||||
|
||||
let wx = Watchexec::new(init, runtime)?;
|
||||
|
||||
if !args.is_present("postpone") {
|
||||
wx.send_event(Event::default()).await?;
|
||||
}
|
||||
|
||||
wx.main().await.into_diagnostic()??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
262
src/options.rs
262
src/options.rs
|
@ -1,262 +0,0 @@
|
|||
use std::{env, path::MAIN_SEPARATOR, time::Duration};
|
||||
|
||||
use clap::{value_t, values_t, ArgMatches};
|
||||
use log::{debug, warn};
|
||||
use watchexec::{
|
||||
config::{Config, ConfigBuilder},
|
||||
run::OnBusyUpdate,
|
||||
Shell,
|
||||
};
|
||||
|
||||
pub fn set_commands(builder: &mut ConfigBuilder, matches: &ArgMatches) {
|
||||
let mut commands: Vec<String> = Vec::new();
|
||||
|
||||
// --features are injected just after applicable cargo subcommands
|
||||
// and before the remaining arguments
|
||||
let features = value_t!(matches, "features", String).ok();
|
||||
|
||||
if matches.is_present("cmd:trail") {
|
||||
debug!("trailing command is present, ignore all other command options");
|
||||
commands = vec![values_t!(matches, "cmd:trail", String)
|
||||
.unwrap_or_else(|e| e.exit())
|
||||
.into_iter()
|
||||
.map(|arg| shell_escape::escape(arg.into()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")];
|
||||
} else {
|
||||
let command_order = env::args().filter_map(|arg| match arg.as_str() {
|
||||
"-x" | "--exec" => Some("cargo"),
|
||||
"-s" | "--shell" => Some("shell"),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut cargos = if matches.is_present("cmd:cargo") {
|
||||
values_t!(matches, "cmd:cargo", String).unwrap_or_else(|e| e.exit())
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
.into_iter();
|
||||
let mut shells = if matches.is_present("cmd:shell") {
|
||||
values_t!(matches, "cmd:shell", String).unwrap_or_else(|e| e.exit())
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
.into_iter();
|
||||
|
||||
for c in command_order {
|
||||
match c {
|
||||
"cargo" => {
|
||||
commands.push(cargo_command(
|
||||
cargos
|
||||
.next()
|
||||
.expect("Argument-order mismatch, this is a bug"),
|
||||
&features,
|
||||
));
|
||||
}
|
||||
"shell" => {
|
||||
commands.push(
|
||||
shells
|
||||
.next()
|
||||
.expect("Argument-order mismatch, this is a bug"),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to `cargo check`
|
||||
if commands.is_empty() {
|
||||
let mut cmd: String = "cargo check".into();
|
||||
if let Some(features) = features.as_ref() {
|
||||
cmd.push_str(" --features ");
|
||||
cmd.push_str(features);
|
||||
}
|
||||
commands.push(cmd);
|
||||
}
|
||||
|
||||
debug!("Commands: {:?}", commands);
|
||||
builder.cmd(commands);
|
||||
}
|
||||
|
||||
fn cargo_command(cargo: String, features: &Option<String>) -> String {
|
||||
let mut cmd = String::from("cargo ");
|
||||
|
||||
let cargo = cargo.trim_start();
|
||||
if let Some(features) = features.as_ref() {
|
||||
if cargo.starts_with('b')
|
||||
|| cargo.starts_with("check")
|
||||
|| cargo.starts_with("doc")
|
||||
|| cargo.starts_with('r')
|
||||
|| cargo.starts_with("test")
|
||||
|| cargo.starts_with("install")
|
||||
{
|
||||
// Split command into first word and the arguments
|
||||
let word_boundary = cargo
|
||||
.find(|c: char| c.is_whitespace())
|
||||
.unwrap_or_else(|| cargo.len());
|
||||
|
||||
// Find returns the byte index, and split_at takes a byte offset.
|
||||
// This means the splitting is unicode-safe.
|
||||
let (subcommand, args) = cargo.split_at(word_boundary);
|
||||
cmd.push_str(subcommand);
|
||||
cmd.push_str(" --features ");
|
||||
cmd.push_str(features);
|
||||
cmd.push(' ');
|
||||
cmd.push_str(args);
|
||||
} else {
|
||||
cmd.push_str(&cargo);
|
||||
}
|
||||
} else {
|
||||
cmd.push_str(&cargo);
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn set_ignores(builder: &mut ConfigBuilder, matches: &ArgMatches) {
|
||||
if matches.is_present("ignore-nothing") {
|
||||
debug!("Ignoring nothing");
|
||||
|
||||
builder.no_vcs_ignore(true);
|
||||
builder.no_ignore(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let novcs = matches.is_present("no-gitignore");
|
||||
builder.no_vcs_ignore(novcs);
|
||||
debug!("Load Git/VCS ignores: {:?}", !novcs);
|
||||
|
||||
let noignore = matches.is_present("no-ignore");
|
||||
builder.no_ignore(noignore);
|
||||
debug!("Load .ignore ignores: {:?}", !noignore);
|
||||
|
||||
let mut list = vec![
|
||||
// Mac
|
||||
format!("*{}.DS_Store", MAIN_SEPARATOR),
|
||||
// Vim
|
||||
"*.sw?".into(),
|
||||
"*.sw?x".into(),
|
||||
// Emacs
|
||||
"#*#".into(),
|
||||
".#*".into(),
|
||||
// Kate
|
||||
".*.kate-swp".into(),
|
||||
// VCS
|
||||
format!("*{s}.hg{s}**", s = MAIN_SEPARATOR),
|
||||
format!("*{s}.git{s}**", s = MAIN_SEPARATOR),
|
||||
format!("*{s}.svn{s}**", s = MAIN_SEPARATOR),
|
||||
// SQLite
|
||||
"*.db".into(),
|
||||
"*.db-*".into(),
|
||||
format!("*{s}*.db-journal{s}**", s = MAIN_SEPARATOR),
|
||||
// Rust
|
||||
format!("*{s}target{s}**", s = MAIN_SEPARATOR),
|
||||
];
|
||||
|
||||
debug!("Default ignores: {:?}", list);
|
||||
|
||||
if matches.is_present("ignore") {
|
||||
for ignore in values_t!(matches, "ignore", String).unwrap_or_else(|e| e.exit()) {
|
||||
#[cfg(windows)]
|
||||
let ignore = ignore.replace("/", &MAIN_SEPARATOR.to_string());
|
||||
list.push(ignore);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("All ignores: {:?}", list);
|
||||
builder.ignores(list);
|
||||
}
|
||||
|
||||
pub fn set_debounce(builder: &mut ConfigBuilder, matches: &ArgMatches) {
|
||||
if matches.is_present("delay") {
|
||||
let debounce = value_t!(matches, "delay", f32).unwrap_or_else(|e| e.exit());
|
||||
debug!("File updates debounce: {} seconds", debounce);
|
||||
|
||||
let d = Duration::from_millis((debounce * 1000.0) as u64);
|
||||
builder.poll_interval(d).debounce(d);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_watches(builder: &mut ConfigBuilder, matches: &ArgMatches) {
|
||||
let mut opts = Vec::new();
|
||||
if matches.is_present("watch") {
|
||||
for watch in values_t!(matches, "watch", String).unwrap_or_else(|e| e.exit()) {
|
||||
opts.push(watch.into());
|
||||
}
|
||||
}
|
||||
|
||||
if opts.is_empty() {
|
||||
opts.push(".".into());
|
||||
}
|
||||
|
||||
debug!("Watches: {:?}", opts);
|
||||
builder.paths(opts);
|
||||
}
|
||||
|
||||
pub fn get_options(matches: &ArgMatches) -> Config {
|
||||
let mut builder = ConfigBuilder::default();
|
||||
builder
|
||||
.poll(matches.is_present("poll"))
|
||||
.clear_screen(matches.is_present("clear"))
|
||||
.run_initially(!matches.is_present("postpone"))
|
||||
.no_environment(true);
|
||||
|
||||
// TODO in 8.0: remove --watch-when-idle and switch --no-restart behaviour to DoNothing
|
||||
builder.on_busy_update(if matches.is_present("no-restart") {
|
||||
OnBusyUpdate::Queue
|
||||
} else if matches.is_present("watch-when-idle") {
|
||||
OnBusyUpdate::DoNothing
|
||||
} else {
|
||||
OnBusyUpdate::Restart
|
||||
});
|
||||
|
||||
builder.shell(if let Some(s) = matches.value_of("use-shell") {
|
||||
if s.eq_ignore_ascii_case("powershell") {
|
||||
Shell::Powershell
|
||||
} else if s.eq_ignore_ascii_case("none") {
|
||||
warn!("--use-shell=none is non-sensical for cargo-watch, ignoring");
|
||||
default_shell()
|
||||
} else if s.eq_ignore_ascii_case("cmd") {
|
||||
cmd_shell(s.into())
|
||||
} else {
|
||||
Shell::Unix(s.into())
|
||||
}
|
||||
} else {
|
||||
// in 8.0, just rely on default watchexec behaviour
|
||||
default_shell()
|
||||
});
|
||||
|
||||
set_ignores(&mut builder, matches);
|
||||
set_debounce(&mut builder, matches);
|
||||
set_watches(&mut builder, matches);
|
||||
set_commands(&mut builder, matches);
|
||||
|
||||
let mut args = builder.build().unwrap();
|
||||
args.once = matches.is_present("once");
|
||||
|
||||
debug!("Watchexec arguments: {:?}", args);
|
||||
args
|
||||
}
|
||||
|
||||
// until 8.0
|
||||
#[cfg(windows)]
|
||||
fn default_shell() -> Shell {
|
||||
Shell::Cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn default_shell() -> Shell {
|
||||
Shell::default()
|
||||
}
|
||||
|
||||
// because Shell::Cmd is only on windows
|
||||
#[cfg(windows)]
|
||||
fn cmd_shell(_: String) -> Shell {
|
||||
Shell::Cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn cmd_shell(s: String) -> Shell {
|
||||
Shell::Unix(s)
|
||||
}
|
27
src/root.rs
27
src/root.rs
|
@ -1,27 +0,0 @@
|
|||
use camino::Utf8PathBuf;
|
||||
use clap::{Error, ErrorKind};
|
||||
use log::debug;
|
||||
use std::{env::set_current_dir, process::Command};
|
||||
|
||||
pub fn project_root() -> Utf8PathBuf {
|
||||
Command::new("cargo")
|
||||
.arg("locate-project")
|
||||
.arg("--message-format")
|
||||
.arg("plain")
|
||||
.output()
|
||||
.map_err(|err| err.to_string())
|
||||
.and_then(|out| String::from_utf8(out.stdout).map_err(|err| err.to_string()))
|
||||
.map(Utf8PathBuf::from)
|
||||
.and_then(|path| {
|
||||
path.parent()
|
||||
.ok_or_else(|| String::from("project root does not exist"))
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
.unwrap_or_else(|err| Error::with_description(&err, ErrorKind::Io).exit())
|
||||
}
|
||||
|
||||
pub fn change_dir(dir: Utf8PathBuf) {
|
||||
debug!("change directory to: {}", dir);
|
||||
set_current_dir(dir)
|
||||
.unwrap_or_else(|err| Error::with_description(&err.to_string(), ErrorKind::Io).exit())
|
||||
}
|
80
src/watch.rs
80
src/watch.rs
|
@ -1,80 +0,0 @@
|
|||
use watchexec::{
|
||||
config::Config,
|
||||
error::Result,
|
||||
pathop::PathOp,
|
||||
run::{ExecHandler, Handler},
|
||||
};
|
||||
|
||||
pub struct CwHandler {
|
||||
cmd: String,
|
||||
once: bool,
|
||||
quiet: bool,
|
||||
notify: bool,
|
||||
inner: ExecHandler,
|
||||
}
|
||||
|
||||
impl Handler for CwHandler {
|
||||
fn args(&self) -> Config {
|
||||
self.inner.args()
|
||||
}
|
||||
|
||||
fn on_manual(&self) -> Result<bool> {
|
||||
if self.once {
|
||||
Ok(true)
|
||||
} else {
|
||||
self.start();
|
||||
self.inner.on_manual()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_update(&self, ops: &[PathOp]) -> Result<bool> {
|
||||
self.start();
|
||||
self.inner.on_update(ops).map(|o| {
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
if self.notify {
|
||||
notify_rust::Notification::new()
|
||||
.summary("Cargo Watch observed a change")
|
||||
.body("Cargo Watch has seen a change, the command may have restarted.")
|
||||
.show()
|
||||
.map(drop)
|
||||
.unwrap_or_else(|err| {
|
||||
log::warn!("Failed to send desktop notification: {}", err);
|
||||
});
|
||||
}
|
||||
|
||||
o
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CwHandler {
|
||||
pub fn new(mut args: Config, quiet: bool, notify: bool) -> Result<Self> {
|
||||
let cmd = args.cmd.join(" && ");
|
||||
let mut final_cmd = cmd.clone();
|
||||
if !quiet {
|
||||
#[cfg(unix)]
|
||||
final_cmd.push_str(r#"; echo "[Finished running. Exit status: $?]""#);
|
||||
#[cfg(windows)]
|
||||
final_cmd.push_str(r#" & echo "[Finished running. Exit status: %ERRORLEVEL%]""#);
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
final_cmd.push_str(r#" ; echo "[Finished running]""#);
|
||||
// ^ could be wrong depending on the platform, to be fixed on demand
|
||||
}
|
||||
|
||||
args.cmd = vec![final_cmd];
|
||||
|
||||
Ok(Self {
|
||||
once: args.once,
|
||||
cmd,
|
||||
inner: ExecHandler::new(args)?,
|
||||
quiet,
|
||||
notify,
|
||||
})
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
if !self.quiet {
|
||||
println!("[Running '{}']", self.cmd);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue