Add --delay-run (#324)

The other side of #79
This commit is contained in:
Félix Saparelli 2022-06-16 22:55:25 +00:00
parent 92d05755c9
commit 5c2c80a6e0
10 changed files with 80 additions and 35 deletions

View File

@ -8,6 +8,7 @@ use std::{
use clap::{crate_version, Arg, ArgMatches, Command};
use miette::{Context, IntoDiagnostic, Result};
use tracing::debug;
const OPTSET_FILTERING: &str = "Filtering options";
const OPTSET_COMMAND: &str = "Command options";
@ -108,6 +109,12 @@ pub fn get_args(tagged_filterer: bool) -> Result<ArgMatches> {
.help("Wait until first change to execute command")
.short('p')
.long("postpone"))
.arg(Arg::new("delay-run")
.help_heading(Some(OPTSET_BEHAVIOUR))
.help("Whenever starting the command, sleep some seconds first")
.long("delay-run")
.takes_value(true)
.value_name("seconds"))
.arg(Arg::new("poll")
.help_heading(Some(OPTSET_BEHAVIOUR))
.help("Force polling mode (interval in milliseconds)")
@ -262,5 +269,6 @@ pub fn get_args(tagged_filterer: bool) -> Result<ArgMatches> {
}
}
debug!(?raw_args, "parsing arguments");
Ok(app.get_matches_from(raw_args))
}

View File

@ -6,7 +6,7 @@ use std::{
use clap::ArgMatches;
use miette::{miette, IntoDiagnostic, Result};
use notify_rust::Notification;
use tracing::debug;
use tracing::{debug, debug_span};
use watchexec::{
action::{Action, Outcome, PostSpawn, PreSpawn},
command::{Command, Shell},
@ -20,6 +20,7 @@ use watchexec::{
};
pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
let _span = debug_span!("args-runtime").entered();
let mut config = RuntimeConfig::default();
config.command(interpret_command_args(args)?);
@ -74,6 +75,12 @@ pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
let print_events = args.is_present("print-events");
let once = args.is_present("once");
let delay_run = args
.value_of("delay-run")
.map(|d| u64::from_str(d))
.transpose()
.into_diagnostic()?
.map(Duration::from_secs);
config.on_action(move |action: Action| {
let fut = async { Ok::<(), Infallible>(()) };
@ -85,7 +92,14 @@ pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
}
if once {
action.outcome(Outcome::both(Outcome::Start, Outcome::wait(Outcome::Exit)));
action.outcome(Outcome::both(
if let Some(delay) = &delay_run {
Outcome::both(Outcome::Sleep(delay.clone()), Outcome::Start)
} else {
Outcome::Start
},
Outcome::wait(Outcome::Exit),
));
return fut;
}
@ -158,24 +172,27 @@ pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
}
}
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 {
let start = if clear {
Outcome::both(Outcome::Clear, Outcome::Start)
} else {
Outcome::Start
};
let start = if let Some(delay) = &delay_run {
Outcome::both(Outcome::Sleep(delay.clone()), start)
} else {
start
};
let when_idle = start.clone();
let when_running = match on_busy.as_str() {
"do-nothing" => Outcome::DoNothing,
"restart" => start,
"signal" => Outcome::Signal(signal),
"queue" => Outcome::wait(start),
_ => Outcome::DoNothing,
};
action.outcome(Outcome::if_running(when_running, when_idle));
fut

View File

@ -1,6 +1,6 @@
#![deny(rust_2018_idioms)]
use std::{fs::File, sync::Mutex, env::var};
use std::{env::var, fs::File, sync::Mutex};
use miette::{IntoDiagnostic, Result};
use tracing::debug;
@ -39,7 +39,9 @@ async fn main() -> Result<()> {
let verbosity = args.occurrences_of("verbose");
let log_file = if let Some(file) = args.value_of("log-file") {
Some(File::create(file).into_diagnostic()?)
} else { None };
} else {
None
};
let mut builder = tracing_subscriber::fmt().with_env_filter(match verbosity {
0 => "watchexec-cli=warn",
@ -54,7 +56,11 @@ async fn main() -> Result<()> {
}
if let Some(writer) = log_file {
builder.json().with_writer(Mutex::new(writer)).try_init().ok();
builder
.json()
.with_writer(Mutex::new(writer))
.try_init()
.ok();
} else if verbosity > 3 {
builder.pretty().try_init().ok();
} else {
@ -62,7 +68,7 @@ async fn main() -> Result<()> {
}
}
debug!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI");
debug!(version=%env!("CARGO_PKG_VERSION"), ?args, "constructing Watchexec from CLI");
let init = config::init(&args)?;
let mut runtime = config::runtime(&args)?;

View File

@ -45,6 +45,9 @@ Behaviour options:
-d, --debounce <milliseconds>
Set the timeout between detected change and command execution, defaults to 50ms
--delay-run <seconds>
Whenever starting the command, sleep some seconds first
--force-poll <interval>
Force polling mode (interval in milliseconds)

View File

@ -46,6 +46,9 @@ Behaviour options:
-d, --debounce <milliseconds>
Set the timeout between detected change and command execution, defaults to 50ms
--delay-run <seconds>
Whenever starting the command, sleep some seconds first
--force-poll <interval>
Force polling mode (interval in milliseconds)

View File

@ -155,7 +155,9 @@ impl OutcomeWorker {
}
(_, Outcome::Sleep(time)) => {
trace!(?time, "sleeping");
notry!(sleep(time));
trace!(?time, "done sleeping");
}
(_, Outcome::Clear) => {

View File

@ -59,26 +59,19 @@ pub enum Command {
pub enum Shell {
/// Use the given string as a unix shell invocation.
///
/// This means two things:
/// - the program is invoked with `-c` followed by the command, and
/// - the string will be split on space, and the resulting vec used as execvp(3) arguments:
/// first is the shell program, rest are additional arguments (which come before the `-c`
/// mentioned above). This is a very simplistic approach deliberately: it will not support
/// quoted arguments, for example. Use [`Shell::None`] with a custom command vec for that.
/// This is invoked with `-c` followed by the command.
Unix(String),
/// Use the Windows CMD.EXE shell.
///
/// This is invoked with `/C` followed by the command.
/// This is `cmd.exe` invoked with `/C` followed by the command.
#[cfg(windows)]
Cmd,
/// Use Powershell, on Windows or elsewhere.
///
/// This is invoked with `-Command` followed by the command.
///
/// This is preferred over `Unix("pwsh")`, though that will also work on unices due to
/// Powershell supporting the `-c` short option.
/// This is `powershell.exe` invoked with `-Command` followed by the command on Windows.
/// On unices, it is equivalent to `Unix("pwsh")`.
Powershell,
}

View File

@ -1,4 +1,3 @@
use super::{Command, Shell};
use command_group::AsyncCommandGroup;

View File

@ -2,6 +2,8 @@
use std::{fmt, path::Path, sync::Arc, time::Duration};
use tracing::debug;
use crate::{
action::{Action, PostSpawn, PreSpawn},
command::Command,
@ -20,6 +22,9 @@ use crate::{
/// marked non-exhaustive such that new options may be added without breaking change. You can make
/// changes through the fields directly, or use the convenience (chainable!) methods instead.
///
/// Another advantage of using the convenience methods is that each one contains a call to the
/// [`debug!`] macro, providing insight into what config your application sets for "free".
///
/// You should see the detailed documentation on [fs::WorkingData][crate::fs::WorkingData] and
/// [action::WorkingData][crate::action::WorkingData] for important information and particulars
/// about each field, especially the handlers.
@ -46,11 +51,13 @@ impl RuntimeConfig {
P: AsRef<Path>,
{
self.fs.pathset = pathset.into_iter().map(|p| p.as_ref().into()).collect();
debug!(pathset=?self.fs.pathset, "RuntimeConfig: pathset");
self
}
/// Set the file watcher type to use.
pub fn file_watcher(&mut self, watcher: Watcher) -> &mut Self {
debug!(?watcher, "RuntimeConfig: watcher");
self.fs.watcher = watcher;
self
}
@ -58,11 +65,13 @@ impl RuntimeConfig {
/// Set the action throttle.
pub fn action_throttle(&mut self, throttle: impl Into<Duration>) -> &mut Self {
self.action.throttle = throttle.into();
debug!(throttle=?self.action.throttle, "RuntimeConfig: throttle");
self
}
/// Toggle whether to use process groups or not.
pub fn command_grouped(&mut self, grouped: bool) -> &mut Self {
debug!(?grouped, "RuntimeConfig: command_grouped");
self.action.grouped = grouped;
self
}
@ -71,6 +80,7 @@ impl RuntimeConfig {
///
/// This is a convenience for `.commands(vec![Command...])`.
pub fn command(&mut self, command: Command) -> &mut Self {
debug!(?command, "RuntimeConfig: command");
self.action.commands = vec![command];
self
}
@ -78,23 +88,27 @@ impl RuntimeConfig {
/// Set the commands to run on action.
pub fn commands(&mut self, commands: impl Into<Vec<Command>>) -> &mut Self {
self.action.commands = commands.into();
debug!(commands=?self.action.commands, "RuntimeConfig: commands");
self
}
/// Set the filterer implementation to use.
pub fn filterer(&mut self, filterer: Arc<dyn Filterer>) -> &mut Self {
debug!(?filterer, "RuntimeConfig: filterer");
self.action.filterer = filterer;
self
}
/// Set the action handler.
pub fn on_action(&mut self, handler: impl Handler<Action> + Send + 'static) -> &mut Self {
debug!("RuntimeConfig: on_action");
self.action.action_handler = HandlerLock::new(Box::new(handler));
self
}
/// Set the pre-spawn handler.
pub fn on_pre_spawn(&mut self, handler: impl Handler<PreSpawn> + Send + 'static) -> &mut Self {
debug!("RuntimeConfig: on_pre_spawn");
self.action.pre_spawn_handler = HandlerLock::new(Box::new(handler));
self
}
@ -104,6 +118,7 @@ impl RuntimeConfig {
&mut self,
handler: impl Handler<PostSpawn> + Send + 'static,
) -> &mut Self {
debug!("RuntimeConfig: on_post_spawn");
self.action.post_spawn_handler = HandlerLock::new(Box::new(handler));
self
}
@ -168,6 +183,7 @@ impl InitConfig {
///
/// See the [documentation on the field](InitConfig#structfield.error_handler) for more details.
pub fn on_error(&mut self, handler: impl Handler<ErrorHook> + Send + 'static) -> &mut Self {
debug!("InitConfig: on_error");
self.error_handler = Box::new(handler) as _;
self
}
@ -176,6 +192,7 @@ impl InitConfig {
///
/// See the [documentation on the field](InitConfig#structfield.error_channel_size) for more details.
pub fn error_channel_size(&mut self, size: usize) -> &mut Self {
debug!(?size, "InitConfig: error_channel_size");
self.error_channel_size = size;
self
}
@ -184,6 +201,7 @@ impl InitConfig {
///
/// See the [documentation on the field](InitConfig#structfield.event_channel_size) for more details.
pub fn event_channel_size(&mut self, size: usize) -> &mut Self {
debug!(?size, "InitConfig: event_channel_size");
self.event_channel_size = size;
self
}

View File

@ -157,10 +157,6 @@ pub enum RuntimeError {
),
/// Error emitted by a [`Filterer`](crate::filter::Filterer).
///
/// With built-in filterers this will probably be a dynbox of
/// [`TaggedFiltererError`](crate::error::TaggedFiltererError), but it is
/// possible to use a custom filterer which emits a different error type.
#[error("{kind} filterer: {err}")]
#[diagnostic(code(watchexec::runtime::filterer))]
Filterer {