Add option to exit when stdin ends (#449)
Co-authored-by: Félix Saparelli <felix@passcod.name>
This commit is contained in:
parent
db287c49f7
commit
f613ba1a79
|
@ -282,9 +282,19 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -764,11 +774,11 @@ checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
|||
|
||||
[[package]]
|
||||
name = "git-actor"
|
||||
version = "0.11.4"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f71e800c934ad4cb177a1a396a6ea57e4cb493bd5278d350752205570863478"
|
||||
checksum = "ac9fb99c934ed45a62d9ae1e7b21949f2d869d1b82a07dcbf16ed61daa665870"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"bstr 1.0.1",
|
||||
"btoi",
|
||||
"git-date",
|
||||
"itoa",
|
||||
|
@ -778,41 +788,55 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "git-config"
|
||||
version = "0.7.1"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d533a785dd345fb133acde7a88ac264de96a1982cecad7f0e8a644ff4c39dcc5"
|
||||
checksum = "bd1d13179bcf3dd68e83404f91a8d01c618f54eb97ef36c68ee5e6f30183a681"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
"bstr 1.0.1",
|
||||
"git-config-value",
|
||||
"git-features",
|
||||
"git-glob",
|
||||
"git-path",
|
||||
"git-ref",
|
||||
"git-sec",
|
||||
"libc",
|
||||
"memchr",
|
||||
"nom 7.1.1",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"unicode-bom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-date"
|
||||
version = "0.1.0"
|
||||
name = "git-config-value"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d58ccaaf783384a6ad68a6abf84942a3f88e34970ced3b34dc68183be50996d"
|
||||
checksum = "64561e9700f1fc737fa3c1c4ea55293be70dba98e45c54cf3715cb180f37a566"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"bitflags",
|
||||
"bstr 1.0.1",
|
||||
"git-path",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-date"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e33db9f4462b565a33507aee113f3383bf16b988d2c573f07691e34302b7aa0a"
|
||||
dependencies = [
|
||||
"bstr 1.0.1",
|
||||
"itoa",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-features"
|
||||
version = "0.22.4"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50af1af32068e5fb709ee479afc414ae98216869dfb1238ca99894a9073be34a"
|
||||
checksum = "d7bdbe755d2129bc609437b6b18af1116f146128dda6070c15c0aa50201ac17c"
|
||||
dependencies = [
|
||||
"git-hash",
|
||||
"libc",
|
||||
|
@ -822,19 +846,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "git-glob"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1879e27b5cb57bee828ea57a1ce9a004e9ae51fa71a2d4fb031175386df246"
|
||||
checksum = "ef858611602fce54b51e45671ca72f07fe6a3c0e24a0539c66b75dfd4d84bd77"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
"bstr 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-hash"
|
||||
version = "0.9.9"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fafbe250292836c26f0e447f661789771c433ed50e49bc3d55b9fbc7c667213"
|
||||
checksum = "d74d271e8194956dcb6f8bf94b6bc1f403acf34c81d9371c15e4145e6d059795"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"thiserror",
|
||||
|
@ -842,9 +866,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "git-lock"
|
||||
version = "2.1.1"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ff6ad736a93573e219cb9b81c2edb6df0ced812f886e8003df375d96e650e73"
|
||||
checksum = "89e4f05b8a68c3a5dd83a6651c76be384e910fe283072184fdab9d77f87ccec2"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"git-tempfile",
|
||||
|
@ -853,11 +877,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "git-object"
|
||||
version = "0.20.3"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd987a3518738c902bd654f9b6ae7aa24934bf5a80b1614f8afb3a02c9bb16d3"
|
||||
checksum = "ce0f14f9cd8f0782e843898a2fb7b0c2f5a6e37bd4cdff4409bb8ec698597dad"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"bstr 1.0.1",
|
||||
"btoi",
|
||||
"git-actor",
|
||||
"git-features",
|
||||
|
@ -866,25 +890,25 @@ dependencies = [
|
|||
"hex",
|
||||
"itoa",
|
||||
"nom 7.1.1",
|
||||
"quick-error",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-path"
|
||||
version = "0.4.2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05c3657d6f51d4d29b47812a77c0bb4df705d4d5bcd4fa41fd45729265e3420"
|
||||
checksum = "5f60cbc13bc0fdd95df5f4b80437197e2853116792894b1bf38d1a6b4a64f8c9"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"bstr 1.0.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-ref"
|
||||
version = "0.15.4"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5926938f4732a200a5897f512234bf6d23e4bc5f23a6d371aae4a66c51020"
|
||||
checksum = "22484043921e699edc170415789f1b882c8f3546e1fbbc447a0043ef07e088c4"
|
||||
dependencies = [
|
||||
"git-actor",
|
||||
"git-features",
|
||||
|
@ -896,27 +920,27 @@ dependencies = [
|
|||
"git-validate",
|
||||
"memmap2",
|
||||
"nom 7.1.1",
|
||||
"quick-error",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-sec"
|
||||
version = "0.3.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0073a138d171b64d5251726620c2232f695f7fbcfa7e5678dc62c1c19846f0e5"
|
||||
checksum = "1ecb370efde58da72827909292284b5c5b885e0621a342515a36976b0b3bf660"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dirs 4.0.0",
|
||||
"git-path",
|
||||
"libc",
|
||||
"windows 0.37.0",
|
||||
"windows 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-tempfile"
|
||||
version = "2.0.4"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baed392d47397d32d29be06bc09824a259a44df85614fd301ef98e69770a15b9"
|
||||
checksum = "a6bb4dee86c8cae5a078cfaac3b004ef99c31548ed86218f23a7ff9b4b74f3be"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"libc",
|
||||
|
@ -928,12 +952,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "git-validate"
|
||||
version = "0.5.5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7af1453adfe6011f0ef71824591b7cdd85850c27bbf3dc8fa855574bed2fe107"
|
||||
checksum = "cdf83bae632fc064ca938ebfb987364d9083b7f98b1476805f0a2d5eebb48686"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"quick-error",
|
||||
"bstr 1.0.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -943,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"bstr 0.2.17",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
|
@ -2786,15 +2810,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
|
||||
checksum = "e30acc718a52fb130fec72b1cb5f55ffeeec9253e1b785e94db222178a6acaa1"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc 0.37.0",
|
||||
"windows_i686_gnu 0.37.0",
|
||||
"windows_i686_msvc 0.37.0",
|
||||
"windows_x86_64_gnu 0.37.0",
|
||||
"windows_x86_64_msvc 0.37.0",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc 0.40.0",
|
||||
"windows_i686_gnu 0.40.0",
|
||||
"windows_i686_msvc 0.40.0",
|
||||
"windows_x86_64_gnu 0.40.0",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2810,6 +2836,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3caa4a1a16561b714323ca6b0817403738583033a6a92e04c5d10d4ba37ca10"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
|
@ -2818,9 +2850,9 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
|
||||
checksum = "328973c62dfcc50fb1aaa8e7100676e0b642fe56bac6bafff3327902db843ab4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
@ -2836,9 +2868,9 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
|
||||
checksum = "aa5b09fad70f0df85dea2ac2a525537e415e2bf63ee31cf9b8e263645ee9f3c1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
@ -2854,9 +2886,9 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
|
||||
checksum = "2a1ad4031c1a98491fa195d8d43d7489cb749f135f2e5c4eed58da094bd0d876"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
@ -2872,9 +2904,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
|
||||
checksum = "520ff37edd72da8064b49d2281182898e17f0688ae9f4070bca27e4b5c162ac7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046e5b82215102c44fd75f488f1b9158973d02aa34d06ed85c23d6f5520a2853"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
@ -2890,9 +2928,9 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
|
||||
checksum = "2a0c9c6df55dd1bfa76e131cef44bdd8ec9c819ef3611f04dfe453fd5bfeda28"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
|
|
|
@ -71,6 +71,10 @@ pub fn get_args(tagged_filterer: bool) -> Result<ArgMatches> {
|
|||
.value_name("milliseconds")
|
||||
.short('d')
|
||||
.long("debounce"))
|
||||
.arg(Arg::new("stdin-quit")
|
||||
.help_heading(Some(OPTSET_BEHAVIOUR))
|
||||
.help("Stop watching when stdin closes")
|
||||
.long("stdin-quit"))
|
||||
.arg(Arg::new("verbose")
|
||||
.help_heading(Some(OPTSET_DEBUGGING))
|
||||
.help("Print debugging messages (-v, -vv, -vvv, -vvvv; use -vvv for bug reports)")
|
||||
|
|
|
@ -12,9 +12,10 @@ use watchexec::{
|
|||
command::{Command, Shell},
|
||||
config::RuntimeConfig,
|
||||
error::RuntimeError,
|
||||
event::ProcessEnd,
|
||||
event::{ProcessEnd, Tag},
|
||||
fs::Watcher,
|
||||
handler::SyncFnHandler,
|
||||
keyboard::Keyboard,
|
||||
paths::summarise_events_to_env,
|
||||
signal::{process::SubSignal, source::MainSignal},
|
||||
};
|
||||
|
@ -37,6 +38,8 @@ pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
|
|||
.into_diagnostic()?,
|
||||
));
|
||||
|
||||
config.keyboard_emit_eof(args.is_present("stdin-quit"));
|
||||
|
||||
if let Some(interval) = args.value_of("poll") {
|
||||
config.file_watcher(Watcher::Poll(Duration::from_millis(
|
||||
interval.parse().into_diagnostic()?,
|
||||
|
@ -120,6 +123,16 @@ pub fn runtime(args: &ArgMatches) -> Result<RuntimeConfig> {
|
|||
return fut;
|
||||
}
|
||||
|
||||
let is_keyboard_eof = action
|
||||
.events
|
||||
.iter()
|
||||
.any(|e| e.tags.contains(&Tag::Keyboard(Keyboard::Eof)));
|
||||
|
||||
if is_keyboard_eof {
|
||||
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
|
||||
return fut;
|
||||
}
|
||||
|
||||
if !has_paths {
|
||||
if !signals.is_empty() {
|
||||
let mut out = Outcome::DoNothing;
|
||||
|
|
|
@ -36,6 +36,9 @@ pub struct RuntimeConfig {
|
|||
/// This notably includes the path set to be watched.
|
||||
pub fs: crate::fs::WorkingData,
|
||||
|
||||
/// Working data for keyboard event sources.
|
||||
pub keyboard: crate::keyboard::WorkingData,
|
||||
|
||||
/// Working data for the action processing.
|
||||
///
|
||||
/// This is the task responsible for scheduling the actions in response to events, applying the
|
||||
|
@ -62,6 +65,12 @@ impl RuntimeConfig {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enable monitoring of 'end of file' from stdin
|
||||
pub fn keyboard_emit_eof(&mut self, enable: bool) -> &mut Self {
|
||||
self.keyboard.eof = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the action throttle.
|
||||
pub fn action_throttle(&mut self, throttle: impl Into<Duration>) -> &mut Self {
|
||||
self.action.throttle = throttle.into();
|
||||
|
|
|
@ -50,6 +50,15 @@ pub enum RuntimeError {
|
|||
err: super::FsWatcherError,
|
||||
},
|
||||
|
||||
/// Events from the keyboard event source
|
||||
#[error("keyboard watcher error")]
|
||||
#[diagnostic(code(watchexec::runtime::keyboard_watcher))]
|
||||
KeyboardWatcher {
|
||||
/// The underlying error.
|
||||
#[source]
|
||||
err: super::KeyboardWatcherError,
|
||||
},
|
||||
|
||||
/// Opaque internal error from a command supervisor.
|
||||
#[error("internal: command supervisor: {0}")]
|
||||
#[diagnostic(code(watchexec::runtime::internal_supervisor))]
|
||||
|
|
|
@ -4,7 +4,7 @@ use miette::Diagnostic;
|
|||
use thiserror::Error;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::{action, fs};
|
||||
use crate::{action, fs, keyboard};
|
||||
|
||||
/// Errors occurring from reconfigs.
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
@ -20,6 +20,11 @@ pub enum ReconfigError {
|
|||
#[error("reconfig: fs watch: {0}")]
|
||||
#[diagnostic(code(watchexec::reconfig::fs_watch))]
|
||||
FsWatch(#[from] watch::error::SendError<fs::WorkingData>),
|
||||
|
||||
/// Error received when the keyboard event source cannot be updated.
|
||||
#[error("reconfig: keyboard watch: {0}")]
|
||||
#[diagnostic(code(watchexec::reconfig::keyboard_watch))]
|
||||
KeyboardWatch(#[from] watch::error::SendError<keyboard::WorkingData>),
|
||||
}
|
||||
|
||||
/// Error when parsing a signal from string.
|
||||
|
@ -117,3 +122,14 @@ pub enum FsWatcherError {
|
|||
err: notify::Error,
|
||||
},
|
||||
}
|
||||
|
||||
/// Errors emitted by the keyboard watcher.
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[non_exhaustive]
|
||||
#[diagnostic(url(docsrs))]
|
||||
pub enum KeyboardWatcherError {
|
||||
/// Error received when shutting down stdin watcher fails.
|
||||
#[error("failed to shut down stdin watcher")]
|
||||
#[diagnostic(code(watchexec::keyboard_watcher))]
|
||||
StdinShutdown,
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use std::{
|
|||
|
||||
use filekind::FileEventKind;
|
||||
|
||||
use crate::keyboard::Keyboard;
|
||||
use crate::signal::{process::SubSignal, source::MainSignal};
|
||||
|
||||
/// Re-export of the Notify file event types.
|
||||
|
@ -55,6 +56,9 @@ pub enum Tag {
|
|||
/// The general source of the event.
|
||||
Source(Source),
|
||||
|
||||
/// The event was caused by specific keyboard input
|
||||
Keyboard(Keyboard),
|
||||
|
||||
/// The event was caused by a particular process.
|
||||
Process(u32),
|
||||
|
||||
|
@ -72,6 +76,7 @@ impl Tag {
|
|||
Tag::Path { .. } => "Path",
|
||||
Tag::FileEventKind(_) => "FileEventKind",
|
||||
Tag::Source(_) => "Source",
|
||||
Tag::Keyboard(_) => "Keyboard",
|
||||
Tag::Process(_) => "Process",
|
||||
Tag::Signal(_) => "Signal",
|
||||
Tag::ProcessCompletion(_) => "ProcessCompletion",
|
||||
|
@ -346,6 +351,7 @@ impl fmt::Display for Event {
|
|||
}
|
||||
Tag::FileEventKind(kind) => write!(f, " kind={:?}", kind)?,
|
||||
Tag::Source(s) => write!(f, " source={:?}", s)?,
|
||||
Tag::Keyboard(k) => write!(f, " keyboard={:?}", k)?,
|
||||
Tag::Process(p) => write!(f, " process={}", p)?,
|
||||
Tag::Signal(s) => write!(f, " signal={:?}", s)?,
|
||||
Tag::ProcessCompletion(None) => write!(f, " command-completed")?,
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
//! Event source for keyboard input and related events
|
||||
use async_priority_channel as priority;
|
||||
use tokio::{
|
||||
io::AsyncReadExt,
|
||||
sync::{mpsc, oneshot, watch},
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
error::{CriticalError, KeyboardWatcherError, RuntimeError},
|
||||
event::{Event, Priority, Source, Tag},
|
||||
};
|
||||
|
||||
/// The configuration of the [keyboard][self] worker.
|
||||
///
|
||||
/// This is marked non-exhaustive so new configuration can be added without breaking.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct WorkingData {
|
||||
/// Whether or not to watch for 'end of file' on stdin
|
||||
pub eof: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
/// Enumeration of different keyboard events
|
||||
pub enum Keyboard {
|
||||
/// Event representing an 'end of file' on stdin
|
||||
Eof,
|
||||
}
|
||||
|
||||
/// Launch the filesystem event worker.
|
||||
///
|
||||
/// While you can run several, you should only have one.
|
||||
///
|
||||
/// Sends keyboard events via to the provided 'events' channel
|
||||
pub async fn worker(
|
||||
mut working: watch::Receiver<WorkingData>,
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
let mut send_close = None;
|
||||
while working.changed().await.is_ok() {
|
||||
let watch_for_eof = { working.borrow().eof };
|
||||
match (watch_for_eof, &send_close) {
|
||||
// If we want to watch stdin and we're not already watching it then spawn a task to watch it
|
||||
(true, None) => {
|
||||
let (close_s, close_r) = tokio::sync::oneshot::channel::<()>();
|
||||
|
||||
send_close = Some(close_s);
|
||||
tokio::spawn(watch_stdin(errors.clone(), events.clone(), close_r));
|
||||
}
|
||||
// If we don't want to watch stdin but we are already watching it then send a close signal to end the
|
||||
// watching
|
||||
(false, Some(_)) => {
|
||||
// Repeat match using 'take'
|
||||
if let Some(close_s) = send_close.take() {
|
||||
if close_s.send(()).is_err() {
|
||||
errors
|
||||
.send(RuntimeError::KeyboardWatcher {
|
||||
err: KeyboardWatcherError::StdinShutdown,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise no action is required
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn watch_stdin(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
mut close_r: oneshot::Receiver<()>,
|
||||
) -> Result<(), CriticalError> {
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut buffer = [0; 10];
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = stdin.read(&mut buffer[..]) => {
|
||||
// Read from stdin and if we've read 0 bytes then we assume stdin has received an 'eof' so
|
||||
// we send that event into the system and break out of the loop as 'eof' means that there will
|
||||
// be no more information on stdin.
|
||||
match result {
|
||||
Ok(0) => {
|
||||
send_event(errors, events, Keyboard::Eof).await?;
|
||||
break;
|
||||
}
|
||||
Err(_) => break,
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = &mut close_r => {
|
||||
// If we receive a close signal then break out of the loop and end which drops
|
||||
// our handle on stdin
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_event(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
msg: Keyboard,
|
||||
) -> Result<(), CriticalError> {
|
||||
let tags = vec![Tag::Source(Source::Keyboard), Tag::Keyboard(msg)];
|
||||
|
||||
let event = Event {
|
||||
tags,
|
||||
metadata: Default::default(),
|
||||
};
|
||||
|
||||
trace!(?event, "processed keyboard input into event");
|
||||
if let Err(err) = events.send(event, Priority::Normal).await {
|
||||
errors
|
||||
.send(RuntimeError::EventChannelSend {
|
||||
ctx: "keyboard",
|
||||
err,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -106,6 +106,7 @@ pub mod error;
|
|||
pub mod event;
|
||||
pub mod filter;
|
||||
pub mod fs;
|
||||
pub mod keyboard;
|
||||
pub mod paths;
|
||||
pub mod signal;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
|||
event::{Event, Priority},
|
||||
fs,
|
||||
handler::{rte, Handler},
|
||||
signal,
|
||||
keyboard, signal,
|
||||
};
|
||||
|
||||
/// The main watchexec runtime.
|
||||
|
@ -43,6 +43,7 @@ pub struct Watchexec {
|
|||
|
||||
action_watch: watch::Sender<action::WorkingData>,
|
||||
fs_watch: watch::Sender<fs::WorkingData>,
|
||||
keyboard_watch: watch::Sender<keyboard::WorkingData>,
|
||||
|
||||
event_input: priority::Sender<Event, Priority>,
|
||||
}
|
||||
|
@ -78,6 +79,7 @@ impl Watchexec {
|
|||
let (ev_s, ev_r) = priority::bounded(init.event_channel_size);
|
||||
let (ac_s, ac_r) = watch::channel(take(&mut runtime.action));
|
||||
let (fs_s, fs_r) = watch::channel(fs::WorkingData::default());
|
||||
let (keyboard_s, keyboard_r) = watch::channel(keyboard::WorkingData::default());
|
||||
|
||||
let event_input = ev_s.clone();
|
||||
|
||||
|
@ -86,6 +88,11 @@ impl Watchexec {
|
|||
fs_s.send(take(&mut runtime.fs))
|
||||
.expect("cannot send to just-created fs watch (bug)");
|
||||
|
||||
trace!("sending initial config to keyboard worker");
|
||||
keyboard_s
|
||||
.send(take(&mut runtime.keyboard))
|
||||
.expect("cannot send to just-created keyboard watch (bug)");
|
||||
|
||||
trace!("creating main task");
|
||||
let notify = Arc::new(Notify::new());
|
||||
let start_lock = notify.clone();
|
||||
|
@ -105,11 +112,15 @@ impl Watchexec {
|
|||
let fs = SubTask::spawn("fs", fs::worker(fs_r, er_s.clone(), ev_s.clone()));
|
||||
let signal =
|
||||
SubTask::spawn("signal", signal::source::worker(er_s.clone(), ev_s.clone()));
|
||||
let keyboard = SubTask::spawn(
|
||||
"keyboard",
|
||||
keyboard::worker(keyboard_r, er_s.clone(), ev_s.clone()),
|
||||
);
|
||||
|
||||
let error_hook = SubTask::spawn("error_hook", error_hook(er_r, eh));
|
||||
|
||||
// Use Tokio TaskSet when that lands
|
||||
try_join!(action, error_hook, fs, signal)
|
||||
try_join!(action, error_hook, fs, signal, keyboard)
|
||||
.map(drop)
|
||||
.or_else(|e| {
|
||||
// Close event channel to signal worker task to stop
|
||||
|
@ -134,6 +145,7 @@ impl Watchexec {
|
|||
|
||||
action_watch: ac_s,
|
||||
fs_watch: fs_s,
|
||||
keyboard_watch: keyboard_s,
|
||||
|
||||
event_input,
|
||||
}))
|
||||
|
@ -144,6 +156,7 @@ impl Watchexec {
|
|||
debug!(?config, "reconfiguring");
|
||||
self.action_watch.send(config.action)?;
|
||||
self.fs_watch.send(config.fs)?;
|
||||
self.keyboard_watch.send(config.keyboard)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,9 @@ Terminates the command if it is still running when subsequent file modifications
|
|||
* `-s`, `--signal`:
|
||||
Sends the specified signal (e.g. `SIGKILL`) to the command. Defaults to `SIGTERM`.
|
||||
|
||||
* `--stdin-quit`:
|
||||
Exit when watchexec's stdin is closed. This is useful when watchexec is run as a subprocess and should shut itself down when the parent process stops.
|
||||
|
||||
* `-W`, `--watch-when-idle`:
|
||||
Ignore events while the process is still running. This is distinct from `--restart` in that with this option, events received while the command is running will not trigger a new run immediately after the current command is done.
|
||||
|
||||
|
|
Loading…
Reference in New Issue