diff --git a/completions/bash b/completions/bash index 770118b9..7445ce0a 100644 --- a/completions/bash +++ b/completions/bash @@ -19,7 +19,7 @@ _watchexec() { case "${cmd}" in watchexec) - opts="-w -c -o -W -r -s -k -d -p -n -E -1 -N -e -f -i -v -h -V --watch --clear --on-busy-update --watch-when-idle --restart --signal --kill --stop-signal --stop-timeout --debounce --stdin-quit --no-vcs-ignore --no-project-ignore --no-global-ignore --no-default-ignore --no-discover-ignore --postpone --delay-run --poll --shell --no-shell-long --no-environment --emit-events-to --env --no-process-group --notify --project-origin --workdir --exts --filter --filter-file --ignore --ignore-file --fs-events --no-meta --print-events --verbose --log-file --manual --completions --help --version [COMMAND]..." + opts="-w -c -o -W -r -s -k -d -p -n -E -1 -N -e -f -i -v -h -V --watch --clear --on-busy-update --watch-when-idle --restart --signal --kill --stop-signal --stop-timeout --debounce --stdin-quit --no-vcs-ignore --no-project-ignore --no-global-ignore --no-default-ignore --no-discover-ignore --postpone --delay-run --poll --shell --no-shell-long --no-environment --emit-events-to --only-emit-events --env --no-process-group --notify --project-origin --workdir --exts --filter --filter-file --ignore --ignore-file --fs-events --no-meta --print-events --verbose --log-file --manual --completions --help --version [COMMAND]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/elvish b/completions/elvish index 46c0ecbf..d5df7373 100644 --- a/completions/elvish +++ b/completions/elvish @@ -66,6 +66,7 @@ set edit:completion:arg-completer[watchexec] = {|@words| cand -n 'Don''t use a shell' cand --no-shell-long 'Don''t use a shell' cand --no-environment 'Shorthand for ''--emit-events=none''' + cand --only-emit-events 'Only emit events to stdout, run no commands' cand --no-process-group 'Don''t use a process group' cand -1 'Testing only: exit Watchexec after the first run' cand -N 'Alert when commands start and end' diff --git a/completions/fish b/completions/fish index a8579cbc..a3159179 100644 --- a/completions/fish +++ b/completions/fish @@ -33,6 +33,7 @@ complete -c watchexec -s p -l postpone -d 'Wait until first change before runnin complete -c watchexec -s n -d 'Don\'t use a shell' complete -c watchexec -l no-shell-long -d 'Don\'t use a shell' complete -c watchexec -l no-environment -d 'Shorthand for \'--emit-events=none\'' +complete -c watchexec -l only-emit-events -d 'Only emit events to stdout, run no commands' complete -c watchexec -l no-process-group -d 'Don\'t use a process group' complete -c watchexec -s 1 -d 'Testing only: exit Watchexec after the first run' complete -c watchexec -s N -l notify -d 'Alert when commands start and end' diff --git a/completions/nu b/completions/nu index 72b92a1c..34b6a0dc 100644 --- a/completions/nu +++ b/completions/nu @@ -47,6 +47,7 @@ module completions { --no-shell-long # Don't use a shell --no-environment # Shorthand for '--emit-events=none' --emit-events-to: string@"nu-complete watchexec emit_events_to" # Configure event emission + --only-emit-events # Only emit events to stdout, run no commands --env(-E): string # Add env vars to the command --no-process-group # Don't use a process group -1 # Testing only: exit Watchexec after the first run diff --git a/completions/powershell b/completions/powershell index 67a49a92..b3ef9e6a 100644 --- a/completions/powershell +++ b/completions/powershell @@ -69,6 +69,7 @@ Register-ArgumentCompleter -Native -CommandName 'watchexec' -ScriptBlock { [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Don''t use a shell') [CompletionResult]::new('--no-shell-long', 'no-shell-long', [CompletionResultType]::ParameterName, 'Don''t use a shell') [CompletionResult]::new('--no-environment', 'no-environment', [CompletionResultType]::ParameterName, 'Shorthand for ''--emit-events=none''') + [CompletionResult]::new('--only-emit-events', 'only-emit-events', [CompletionResultType]::ParameterName, 'Only emit events to stdout, run no commands') [CompletionResult]::new('--no-process-group', 'no-process-group', [CompletionResultType]::ParameterName, 'Don''t use a process group') [CompletionResult]::new('-1', '1', [CompletionResultType]::ParameterName, 'Testing only: exit Watchexec after the first run') [CompletionResult]::new('-N', 'N ', [CompletionResultType]::ParameterName, 'Alert when commands start and end') diff --git a/completions/zsh b/completions/zsh index e345be34..466d8811 100644 --- a/completions/zsh +++ b/completions/zsh @@ -63,6 +63,7 @@ _watchexec() { '-n[Don'\''t use a shell]' \ '--no-shell-long[Don'\''t use a shell]' \ '--no-environment[Shorthand for '\''--emit-events=none'\'']' \ +'(--completions --manual)--only-emit-events[Only emit events to stdout, run no commands]' \ '--no-process-group[Don'\''t use a process group]' \ '-1[Testing only\: exit Watchexec after the first run]' \ '-N[Alert when commands start and end]' \ diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 2e2c5a93..87b85142 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, str::FromStr, time::Duration}; -use clap::{ArgAction, Parser, ValueEnum, ValueHint}; +use clap::{error::ErrorKind, ArgAction, CommandFactory, Parser, ValueEnum, ValueHint}; use watchexec::paths::PATH_SEPARATOR; use watchexec_signals::Signal; @@ -88,7 +88,7 @@ pub struct Args { num_args = 1.., value_hint = ValueHint::CommandString, value_name = "COMMAND", - required_unless_present_any = ["completions", "manual"], + required_unless_present_any = ["completions", "manual", "only_emit_events"], )] pub command: Vec, @@ -576,9 +576,26 @@ pub struct Args { default_value = "environment", hide_default_value = true, value_name = "MODE", + required_if_eq("only_emit_events", "true"), )] pub emit_events_to: EmitEvents, + /// Only emit events to stdout, run no commands. + /// + /// This is a convenience option for using Watchexec as a file watcher, without running any + /// commands. It is almost equivalent to using `cat` as the command, except that it will not + /// spawn a new process for each event. + /// + /// This option requires `--emit-events-to` to be set, and restricts the available modes to + /// `stdin` and `json-stdin`, modifying their behaviour to write to stdout instead of the stdin + /// of the command. + #[arg( + long, + help_heading = OPTSET_COMMAND, + conflicts_with_all = ["command", "completions", "manual"], + )] + pub only_emit_events: bool, + /// Add env vars to the command /// /// This is a convenience option for setting environment variables for the command, without @@ -936,6 +953,19 @@ pub fn get_args() -> Args { ]; } + if args.only_emit_events + && !matches!( + args.emit_events_to, + EmitEvents::JsonStdin | EmitEvents::Stdin + ) { + Args::command() + .error( + ErrorKind::InvalidValue, + "only-emit-events requires --emit-events-to=stdin or --emit-events-to=json-stdin", + ) + .exit(); + } + debug!(?args, "got arguments"); args } diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs index 9f59ca88..72a134c6 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -24,7 +24,7 @@ use watchexec::{ use watchexec_events::{Event, Keyboard, ProcessEnd, Tag}; use watchexec_signals::Signal; -use crate::state::State; +use crate::{state::State, emits::events_to_simple_format}; use crate::{ args::{Args, ClearMode, EmitEvents, OnBusyUpdate}, state::RotatingTempFile, @@ -73,7 +73,58 @@ pub fn make_config(args: &Args, state: &State) -> Result { config.file_watcher(Watcher::Poll(interval.0)); } + let once = args.once; let clear = args.screen_clear; + + let emit_events_to = args.emit_events_to; + let emit_file = state.emit_file.clone(); + + if args.only_emit_events { + config.on_action(move |mut action| { + // if we got a terminate or interrupt signal, quit + if action.signals().any(|sig| sig == Signal::Terminate || sig == Signal::Interrupt) { + action.quit(); + return action; + } + + // clear the screen before printing events + if let Some(mode) = clear { + match mode { + ClearMode::Clear => { + clearscreen::clear().ok(); + } + ClearMode::Reset => { + for cs in [ + ClearScreen::WindowsCooked, + ClearScreen::WindowsVt, + ClearScreen::VtLeaveAlt, + ClearScreen::VtWellDone, + ClearScreen::default(), + ] { + cs.clear().ok(); + } + } + } + } + + match emit_events_to { + EmitEvents::Stdin => { + println!("{}", events_to_simple_format(action.events.as_ref()).unwrap_or_default()); + } + EmitEvents::JsonStdin => { + for event in action.events.iter().filter(|e| !e.is_empty()) { + println!("{}", serde_json::to_string(event).unwrap_or_default()); + } + } + other => unreachable!("emit_events_to should have been validated earlier: {:?}", other), + } + + action + }); + + return Ok(config); + } + let delay_run = args.delay_run.map(|ts| ts.0); let on_busy = args.on_busy_update; @@ -81,12 +132,9 @@ pub fn make_config(args: &Args, state: &State) -> Result { let stop_signal = args.stop_signal; let stop_timeout = args.stop_timeout.0; - let once = args.once; let notif = args.notify; let print_events = args.print_events; - let emit_events_to = args.emit_events_to; - let emit_file = state.emit_file.clone(); let workdir = Arc::new(args.workdir.clone()); let mut add_envs = HashMap::new(); diff --git a/crates/cli/src/emits.rs b/crates/cli/src/emits.rs index d0778bfd..e82e8bb6 100644 --- a/crates/cli/src/emits.rs +++ b/crates/cli/src/emits.rs @@ -12,7 +12,7 @@ pub fn emits_to_environment(events: &[Event]) -> impl Iterator Result { +pub fn events_to_simple_format(events: &[Event]) -> Result { let mut buf = String::new(); for event in events { let feks = event diff --git a/doc/watchexec.1 b/doc/watchexec.1 index 29e931bd..d24b00ec 100644 --- a/doc/watchexec.1 +++ b/doc/watchexec.1 @@ -4,7 +4,7 @@ .SH NAME watchexec \- Execute commands when watched files change .SH SYNOPSIS -\fBwatchexec\fR [\fB\-w\fR|\fB\-\-watch\fR] [\fB\-c\fR|\fB\-\-clear\fR] [\fB\-o\fR|\fB\-\-on\-busy\-update\fR] [\fB\-r\fR|\fB\-\-restart\fR] [\fB\-s\fR|\fB\-\-signal\fR] [\fB\-\-stop\-signal\fR] [\fB\-\-stop\-timeout\fR] [\fB\-d\fR|\fB\-\-debounce\fR] [\fB\-\-stdin\-quit\fR] [\fB\-\-no\-vcs\-ignore\fR] [\fB\-\-no\-project\-ignore\fR] [\fB\-\-no\-global\-ignore\fR] [\fB\-\-no\-default\-ignore\fR] [\fB\-\-no\-discover\-ignore\fR] [\fB\-p\fR|\fB\-\-postpone\fR] [\fB\-\-delay\-run\fR] [\fB\-\-poll\fR] [\fB\-\-shell\fR] [\fB\-n \fR] [\fB\-\-no\-environment\fR] [\fB\-\-emit\-events\-to\fR] [\fB\-E\fR|\fB\-\-env\fR] [\fB\-\-no\-process\-group\fR] [\fB\-N\fR|\fB\-\-notify\fR] [\fB\-\-project\-origin\fR] [\fB\-\-workdir\fR] [\fB\-e\fR|\fB\-\-exts\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-filter\-file\fR] [\fB\-i\fR|\fB\-\-ignore\fR] [\fB\-\-ignore\-file\fR] [\fB\-\-fs\-events\fR] [\fB\-\-no\-meta\fR] [\fB\-\-print\-events\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-\-log\-file\fR] [\fB\-\-manual\fR] [\fB\-\-completions\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICOMMAND\fR] +\fBwatchexec\fR [\fB\-w\fR|\fB\-\-watch\fR] [\fB\-c\fR|\fB\-\-clear\fR] [\fB\-o\fR|\fB\-\-on\-busy\-update\fR] [\fB\-r\fR|\fB\-\-restart\fR] [\fB\-s\fR|\fB\-\-signal\fR] [\fB\-\-stop\-signal\fR] [\fB\-\-stop\-timeout\fR] [\fB\-d\fR|\fB\-\-debounce\fR] [\fB\-\-stdin\-quit\fR] [\fB\-\-no\-vcs\-ignore\fR] [\fB\-\-no\-project\-ignore\fR] [\fB\-\-no\-global\-ignore\fR] [\fB\-\-no\-default\-ignore\fR] [\fB\-\-no\-discover\-ignore\fR] [\fB\-p\fR|\fB\-\-postpone\fR] [\fB\-\-delay\-run\fR] [\fB\-\-poll\fR] [\fB\-\-shell\fR] [\fB\-n \fR] [\fB\-\-no\-environment\fR] [\fB\-\-emit\-events\-to\fR] [\fB\-\-only\-emit\-events\fR] [\fB\-E\fR|\fB\-\-env\fR] [\fB\-\-no\-process\-group\fR] [\fB\-N\fR|\fB\-\-notify\fR] [\fB\-\-project\-origin\fR] [\fB\-\-workdir\fR] [\fB\-e\fR|\fB\-\-exts\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-filter\-file\fR] [\fB\-i\fR|\fB\-\-ignore\fR] [\fB\-\-ignore\-file\fR] [\fB\-\-fs\-events\fR] [\fB\-\-no\-meta\fR] [\fB\-\-print\-events\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-\-log\-file\fR] [\fB\-\-manual\fR] [\fB\-\-completions\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICOMMAND\fR] .SH DESCRIPTION Execute commands when watched files change. .PP @@ -341,6 +341,13 @@ environment variable. Finally, the special \*(Aqnone\*(Aq mode will disable event emission entirely. .TP +\fB\-\-only\-emit\-events\fR +Only emit events to stdout, run no commands. + +This is a convenience option for using Watchexec as a file watcher, without running any commands. It is almost equivalent to using `cat` as the command, except that it will not spawn a new process for each event. + +This option requires `\-\-emit\-events\-to` to be set, and restricts the available modes to `stdin` and `json\-stdin`, modifying their behaviour to write to stdout instead of the stdin of the command. +.TP \fB\-E\fR, \fB\-\-env\fR=\fIKEY=VALUE\fR Add env vars to the command diff --git a/doc/watchexec.1.md b/doc/watchexec.1.md index 1d4cf254..c018b450 100644 --- a/doc/watchexec.1.md +++ b/doc/watchexec.1.md @@ -13,9 +13,10 @@ watchexec - Execute commands when watched files change \[**\--no-discover-ignore**\] \[**-p**\|**\--postpone**\] \[**\--delay-run**\] \[**\--poll**\] \[**\--shell**\] \[**-n **\] \[**\--no-environment**\] \[**\--emit-events-to**\] -\[**-E**\|**\--env**\] \[**\--no-process-group**\] -\[**-N**\|**\--notify**\] \[**\--project-origin**\] \[**\--workdir**\] -\[**-e**\|**\--exts**\] \[**-f**\|**\--filter**\] \[**\--filter-file**\] +\[**\--only-emit-events**\] \[**-E**\|**\--env**\] +\[**\--no-process-group**\] \[**-N**\|**\--notify**\] +\[**\--project-origin**\] \[**\--workdir**\] \[**-e**\|**\--exts**\] +\[**-f**\|**\--filter**\] \[**\--filter-file**\] \[**-i**\|**\--ignore**\] \[**\--ignore-file**\] \[**\--fs-events**\] \[**\--no-meta**\] \[**\--print-events**\] \[**-v**\|**\--verbose**\]\... \[**\--log-file**\] \[**\--manual**\] @@ -458,6 +459,19 @@ file with the \$WATCHEXEC_EVENTS_FILE environment variable. Finally, the special none mode will disable event emission entirely. +**\--only-emit-events** + +: Only emit events to stdout, run no commands. + +This is a convenience option for using Watchexec as a file watcher, +without running any commands. It is almost equivalent to using \`cat\` +as the command, except that it will not spawn a new process for each +event. + +This option requires \`\--emit-events-to\` to be set, and restricts the +available modes to \`stdin\` and \`json-stdin\`, modifying their +behaviour to write to stdout instead of the stdin of the command. + **-E**, **\--env**=*KEY=VALUE* : Add env vars to the command diff --git a/doc/watchexec.1.pdf b/doc/watchexec.1.pdf index 6f37dc6e..5a696b1c 100644 Binary files a/doc/watchexec.1.pdf and b/doc/watchexec.1.pdf differ