watchexec/cli/src/config.rs

220 lines
5.2 KiB
Rust
Raw Normal View History

use std::{
2021-09-28 11:24:06 +02:00
convert::Infallible, env::current_dir, io::stderr, path::Path, str::FromStr, sync::Arc,
time::Duration,
};
use clap::ArgMatches;
use miette::{IntoDiagnostic, Result};
use watchexec::{
2021-10-16 02:55:20 +02:00
action::{Action, Outcome},
command::Shell,
config::{InitConfig, RuntimeConfig},
2021-10-21 18:48:37 +02:00
event::ProcessEnd,
2021-09-28 11:24:06 +02:00
filter::tagged::TaggedFilterer,
fs::Watcher,
handler::PrintDisplay,
2021-10-16 02:55:20 +02:00
signal::{process::SubSignal, source::MainSignal},
};
2021-09-28 11:24:06 +02:00
pub fn new(args: &ArgMatches<'static>) -> Result<(InitConfig, RuntimeConfig, Arc<TaggedFilterer>)> {
let r = runtime(args)?;
Ok((init(args)?, r.0, r.1))
}
fn init(_args: &ArgMatches<'static>) -> Result<InitConfig> {
let mut config = InitConfig::default();
config.on_error(PrintDisplay(stderr()));
Ok(config)
}
2021-09-28 11:24:06 +02:00
fn runtime(args: &ArgMatches<'static>) -> Result<(RuntimeConfig, Arc<TaggedFilterer>)> {
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("100")
.parse()
.into_diagnostic()?,
));
if let Some(interval) = args.value_of("poll") {
config.file_watcher(Watcher::Poll(Duration::from_millis(
interval.parse().into_diagnostic()?,
)));
}
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 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")
2021-12-21 06:15:47 +01:00
.map(SubSignal::from_str)
.transpose()
.into_diagnostic()?
2021-10-16 02:55:20 +02:00
.unwrap_or(SubSignal::Terminate);
if args.is_present("kill") {
2021-10-16 02:55:20 +02:00
signal = SubSignal::ForceStop;
}
let print_events = args.is_present("print-events");
let once = args.is_present("once");
2021-09-29 17:03:46 +02:00
let filterer = TaggedFilterer::new(".", ".")?;
2021-09-28 11:24:06 +02:00
config.filterer(filterer.clone());
config.on_action(move |action: Action| {
let fut = async { Ok::<(), Infallible>(()) };
if print_events {
for (n, event) in action.events.iter().enumerate() {
2021-09-02 21:58:20 +02:00
eprintln!("[EVENT {}] {}", n, event);
}
}
2021-08-24 13:15:38 +02:00
if once {
action.outcome(Outcome::both(Outcome::Start, Outcome::wait(Outcome::Exit)));
2021-08-24 13:15:38 +02:00
return fut;
}
let signals: Vec<MainSignal> = action.events.iter().flat_map(|e| e.signals()).collect();
2021-08-24 13:15:38 +02:00
let has_paths = action
.events
.iter()
.flat_map(|e| e.paths())
.next()
.is_some();
if signals.contains(&MainSignal::Terminate) {
2021-08-24 13:15:38 +02:00
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
return fut;
}
if signals.contains(&MainSignal::Interrupt) {
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
2021-08-24 13:15:38 +02:00
return fut;
}
2021-09-02 23:25:06 +02:00
if !has_paths {
if !signals.is_empty() {
let mut out = Outcome::DoNothing;
for sig in signals {
2021-10-16 02:55:20 +02:00
out = Outcome::both(out, Outcome::Signal(sig.into()));
}
2021-08-24 13:15:38 +02:00
action.outcome(out);
return fut;
2021-09-02 23:25:06 +02:00
}
let completion = action.events.iter().flat_map(|e| e.completions()).next();
if let Some(status) = completion {
match status {
2021-10-21 18:48:37 +02:00
Some(ProcessEnd::ExitError(code)) => {
eprintln!("[Command exited with {}]", code);
}
Some(ProcessEnd::ExitSignal(sig)) => {
eprintln!("[Command killed by {:?}]", sig);
}
Some(ProcessEnd::ExitStop(sig)) => {
eprintln!("[Command stopped by {:?}]", sig);
}
Some(ProcessEnd::Continued) => {
eprintln!("[Command continued]");
}
Some(ProcessEnd::Exception(ex)) => {
eprintln!("[Command ended by exception {:#x}]", ex);
2021-09-02 23:25:06 +02:00
}
_ => {}
}
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
});
2021-09-28 11:24:06 +02:00
Ok((config, filterer))
}
2021-09-28 11:24:06 +02:00
// 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)
}