watchexec/crates/cli/src/args.rs

919 lines
32 KiB
Rust

use std::{path::PathBuf, str::FromStr, time::Duration};
use clap::{ArgAction, Parser, ValueEnum, ValueHint};
use watchexec::{paths::PATH_SEPARATOR, signal::process::SubSignal};
const OPTSET_FILTERING: &str = "Filtering";
const OPTSET_COMMAND: &str = "Command";
const OPTSET_DEBUGGING: &str = "Debugging";
#[cfg(target_os = "linux")]
shadow_rs::shadow!(build);
/// Execute commands when watched files change.
///
/// Recursively monitors the current directory for changes, executing the command when a filesystem
/// change is detected (among other event sources). By default, watchexec uses efficient
/// kernel-level mechanisms to watch for changes.
///
/// At startup, the specified <COMMAND> is run once, and watchexec begins monitoring for changes.
///
/// Examples:
///
/// Rebuild a project when source files change:
///
/// $ watchexec make
///
/// Watch all HTML, CSS, and JavaScript files for changes:
///
/// $ watchexec -e html,css,js make
///
/// Run tests when source files change, clearing the screen each time:
///
/// $ watchexec -c make test
///
/// Launch and restart a node.js server:
///
/// $ watchexec -r node app.js
///
/// Watch lib and src directories for changes, rebuilding each time:
///
/// $ watchexec -w lib -w src make
#[derive(Debug, Clone, Parser)]
#[command(
name = "watchexec",
bin_name = "watchexec",
author,
version,
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).",
hide_possible_values = true
)]
#[cfg_attr(target_os = "linux", command(long_version = build::CLAP_LONG_VERSION,))]
#[cfg_attr(debug_assertions, command(before_help = "⚠ DEBUG BUILD ⚠"))]
#[cfg_attr(
feature = "dev-console",
command(before_help = "⚠ DEV CONSOLE ENABLED ⚠")
)]
pub struct Args {
/// Command to run on changes
///
/// It's run when events pass filters and the debounce period (and once at startup unless
/// '--postpone' is given). If you pass flags to the command, you should separate it with --
/// though that is not strictly required.
///
/// Examples:
///
/// $ watchexec -w src npm run build
///
/// $ watchexec -w src -- rsync -a src dest
///
/// Take care when using globs or other shell expansions in the command. Your shell may expand
/// them before ever passing them to Watchexec, and the results may not be what you expect.
/// Compare:
///
/// $ watchexec echo src/*.rs
///
/// $ watchexec echo 'src/*.rs'
///
/// $ watchexec --shell=none echo 'src/*.rs'
///
/// Behaviour depends on the value of '--shell': for all except 'none', every part of the
/// command is joined together into one string with a single ascii space character, and given to
/// the shell as described in the help for '--shell'. For 'none', each distinct element the
/// command is passed as per the execvp(3) convention: first argument is the program, as a path
/// or searched for in the 'PATH' environment variable, rest are arguments.
#[arg(
trailing_var_arg = true,
num_args = 1..,
value_hint = ValueHint::CommandString,
value_name = "COMMAND",
required_unless_present_any = ["completions", "manpage"],
)]
pub command: Vec<String>,
/// Watch a specific file or directory
///
/// By default, Watchexec watches the current directory.
///
/// When watching a single file, it's often better to watch the containing directory instead,
/// and filter on the filename. Some editors may replace the file with a new one when saving,
/// and some platforms may not detect that or further changes.
///
/// Upon starting, Watchexec resolves a "project origin" from the watched paths. See the help
/// for '--project-origin' for more information.
///
/// This option can be specified multiple times to watch multiple files or directories.
#[arg(
short = 'w',
long = "watch",
help_heading = OPTSET_FILTERING,
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub paths: Vec<PathBuf>,
/// Clear screen before running command
///
/// If this doesn't completely clear the screen, try '--clear=reset'.
#[arg(
short = 'c',
long = "clear",
num_args = 0..=1,
default_missing_value = "clear",
value_name = "MODE",
)]
pub screen_clear: Option<ClearMode>,
/// What to do when receiving events while the command is running
///
/// Default is to 'queue' up events and run the command once again when the previous run has
/// finished. You can also use 'do-nothing', which ignores events while the command is running
/// and may be useful to avoid spurious changes made by that command, or 'restart', which
/// terminates the running command and starts a new one. Finally, there's 'signal', which only
/// sends a signal; this can be useful with programs that can reload their configuration without
/// a full restart.
///
/// The signal can be specified with the '--signal' option.
///
/// Note that this option is scheduled to change its default to 'do-nothing' in the next major
/// release. File an issue if you have any concerns.
#[arg(
short,
long,
default_value = "queue",
hide_default_value = true,
value_name = "MODE"
)]
pub on_busy_update: OnBusyUpdate,
/// Deprecated alias for '--on-busy-update=do-nothing'
///
/// This option is deprecated and will be removed in the next major release.
#[arg(
long,
short = 'W',
hide = true,
conflicts_with_all = ["on_busy_update", "restart"],
)]
pub watch_when_idle: bool,
/// Restart the process if it's still running
///
/// This is a shorthand for '--on-busy-update=restart'.
#[arg(
short,
long,
conflicts_with_all = ["on_busy_update", "watch_when_idle"],
)]
pub restart: bool,
/// Send a signal to the process when it's still running
///
/// Specify a signal to send to the process when it's still running. This implies
/// '--on-busy-update=signal'; otherwise the signal used when that mode is 'restart' is
/// controlled by '--stop-signal'.
///
/// See the long documentation for '--stop-signal' for syntax.
#[arg(
short,
long,
conflicts_with_all = ["restart", "watch_when_idle"],
value_name = "SIGNAL"
)]
pub signal: Option<SubSignal>,
/// Hidden legacy shorthand for '--signal=kill'.
#[arg(short, long, hide = true)]
pub kill: bool,
/// Signal to send to stop the command
///
/// This is used by 'restart' and 'signal' modes of '--on-busy-update' (unless '--signal' is
/// provided). The restart behaviour is to send the signal, wait for the command to exit, and if
/// it hasn't exited after some time (see '--timeout-stop'), forcefully terminate it.
///
/// The default on unix is "SIGTERM".
///
/// Input is parsed as a full signal name (like "SIGTERM"), a short signal name (like "TERM"),
/// or a signal number (like "15"). All input is case-insensitive.
///
/// On Windows this option is technically supported but only supports the "KILL" event, as
/// Watchexec cannot yet deliver other events. Windows doesn't have signals as such; instead it
/// has termination (here called "KILL" or "STOP") and "CTRL+C", "CTRL+BREAK", and "CTRL+CLOSE"
/// events. For portability the unix signals "SIGKILL", "SIGINT", "SIGTERM", and "SIGHUP" are
/// respectively mapped to these.
#[arg(long, value_name = "SIGNAL")]
pub stop_signal: Option<SubSignal>,
/// Time to wait for the command to exit gracefully
///
/// This is used by the 'restart' mode of '--on-busy-update'. After the graceful stop signal
/// is sent, Watchexec will wait for the command to exit. If it hasn't exited after this time,
/// it is forcefully terminated.
///
/// Takes a unit-less value in seconds, or a time span value such as "5min 20s".
///
/// The default is 60 seconds. Set to 0 to immediately force-kill the command.
#[arg(
long,
default_value = "60",
hide_default_value = true,
value_name = "TIMEOUT"
)]
pub stop_timeout: TimeSpan,
/// Time to wait for new events before taking action
///
/// When an event is received, Watchexec will wait for up to this amount of time before handling
/// it (such as running the command). This is essential as what you might perceive as a single
/// change may actually emit many events, and without this behaviour, Watchexec would run much
/// too often. Additionally, it's not infrequent that file writes are not atomic, and each write
/// may emit an event, so this is a good way to avoid running a command while a file is
/// partially written.
///
/// An alternative use is to set a high value (like "30min" or longer), to save power or
/// bandwidth on intensive tasks, like an ad-hoc backup script. In those use cases, note that
/// every accumulated event will build up in memory.
///
/// Takes a unit-less value in milliseconds, or a time span value such as "5sec 20ms".
///
/// The default is 50 milliseconds. Setting to 0 is highly discouraged.
#[arg(
long,
default_value = "50",
hide_default_value = true,
value_name = "TIMEOUT"
)]
pub debounce: TimeSpan<1_000_000>,
/// Exit when stdin closes
///
/// This watches the stdin file descriptor for EOF, and exits Watchexec gracefully when it is
/// closed. This is used by some process managers to avoid leaving zombie processes around.
#[arg(long)]
pub stdin_quit: bool,
/// Don't load gitignores
///
/// Among other VCS exclude files, like for Mercurial, Subversion, Bazaar, DARCS, Fossil. Note
/// that Watchexec will detect which of these is in use, if any, and only load the relevant
/// files. Both global (like '~/.gitignore') and local (like '.gitignore') files are considered.
///
/// This option is useful if you want to watch files that are ignored by Git.
#[arg(
long,
help_heading = OPTSET_FILTERING,
)]
pub no_vcs_ignore: bool,
/// Don't load project-local ignores
///
/// This disables loading of project-local ignore files, like '.gitignore' or '.ignore' in the
/// watched project. This is contrasted with '--no-vcs-ignore', which disables loading of Git
/// and other VCS ignore files, and with '--no-global-ignore', which disables loading of global
/// or user ignore files, like '~/.gitignore' or '~/.config/watchexec/ignore'.
///
/// Supported project ignore files:
///
/// - Git: .gitignore at project root and child directories, .git/info/exclude, and the file pointed to by `core.excludesFile` in .git/config.
/// - Mercurial: .hgignore at project root and child directories.
/// - Bazaar: .bzrignore at project root.
/// - Darcs: _darcs/prefs/boring
/// - Fossil: .fossil-settings/ignore-glob
/// - Ripgrep/Watchexec/generic: .ignore at project root and child directories.
///
/// VCS ignore files (Git, Mercurial, Bazaar, Darcs, Fossil) are only used if the corresponding
/// VCS is discovered to be in use for the project/origin. For example, a .bzrignore in a Git
/// repository will be discarded.
///
/// Note that this was previously called '--no-ignore', but that's now deprecated and its use is
/// discouraged, as it may be repurposed in the future.
#[arg(
long,
help_heading = OPTSET_FILTERING,
verbatim_doc_comment,
alias = "no-ignore", // deprecated
)]
pub no_project_ignore: bool,
/// Don't load global ignores
///
/// This disables loading of global or user ignore files, like '~/.gitignore',
/// '~/.config/watchexec/ignore', or '%APPDATA%\Bazzar\2.0\ignore'. Contrast with
/// '--no-vcs-ignore' and '--no-project-ignore'.
///
/// Supported global ignore files
///
/// - Git (if core.excludesFile is set): the file at that path
/// - Git (otherwise): the first found of $XDG_CONFIG_HOME/git/ignore, %APPDATA%/.gitignore, %USERPROFILE%/.gitignore, $HOME/.config/git/ignore, $HOME/.gitignore.
/// - Bazaar: the first found of %APPDATA%/Bazzar/2.0/ignore, $HOME/.bazaar/ignore.
/// - Watchexec: the first found of $XDG_CONFIG_HOME/watchexec/ignore, %APPDATA%/watchexec/ignore, %USERPROFILE%/.watchexec/ignore, $HOME/.watchexec/ignore.
///
/// Like for project files, Git and Bazaar global files will only be used for the corresponding
/// VCS as used in the project.
#[arg(
long,
help_heading = OPTSET_FILTERING,
verbatim_doc_comment,
)]
pub no_global_ignore: bool,
/// Don't use internal default ignores
///
/// Watchexec has a set of default ignore patterns, such as editor swap files, `*.pyc`, `*.pyo`,
/// `.DS_Store`, `.bzr`, `_darcs`, `.fossil-settings`, `.git`, `.hg`, `.pijul`, `.svn`, and
/// Watchexec log files.
#[arg(
long,
help_heading = OPTSET_FILTERING,
)]
pub no_default_ignore: bool,
/// Wait until first change before running command
///
/// By default, Watchexec will run the command once immediately. With this option, it will
/// instead wait until an event is detected before running the command as normal.
#[arg(long, short)]
pub postpone: bool,
/// Sleep before running the command
///
/// This option will cause Watchexec to sleep for the specified amount of time before running
/// the command, after an event is detected. This is like using "sleep 5 && command" in a shell,
/// but portable and slightly more efficient.
///
/// Takes a unit-less value in seconds, or a time span value such as "2min 5s".
#[arg(long, value_name = "DURATION")]
pub delay_run: Option<TimeSpan>,
/// Poll for filesystem changes
///
/// By default, and where available, Watchexec uses the operating system's native file system
/// watching capabilities. This option disables that and instead uses a polling mechanism, which
/// is less efficient but can work around issues with some file systems (like network shares) or
/// edge cases.
///
/// Optionally takes a unit-less value in milliseconds, or a time span value such as "2s 500ms",
/// to use as the polling interval. If not specified, the default is 30 seconds.
///
/// Aliased as '--force-poll'.
#[arg(
long,
alias = "force-poll",
num_args = 0..=1,
default_missing_value = "30s",
value_name = "INTERVAL",
)]
pub poll: Option<TimeSpan<1_000_000>>,
/// Use a different shell
///
/// By default, Watchexec will use 'sh' on unix and 'cmd' (CMD.EXE) on Windows. With this, you
/// can override that and use a different shell, for example one with more features or one which
/// has your custom aliases and functions.
///
/// If the value has spaces, it is parsed as a command line, and the first word used as the
/// shell program, with the rest as arguments to the shell.
///
/// The command is run with the '-c' flag (except for 'cmd' and 'powershell' on Windows, where
/// the '/C' option is used).
///
/// Note that the default shell will change at the next major release: the value of '$SHELL'
/// will be respected, falling back to 'sh' on unix and to PowerShell on Windows.
///
/// The special value 'none' can be used to disable shell use entirely. In that case, the
/// command provided to Watchexec will be parsed, with the first word being the executable and
/// the rest being the arguments, and executed directly. Note that this parsing is rudimentary,
/// and may not work as expected in all cases.
///
/// Using 'none' is a little more efficient and can enable a stricter interpretation of the
/// input, but it also means that you can't use shell features like globbing, redirection, or
/// pipes.
///
/// Examples:
///
/// Use without shell:
///
/// $ watchexec -n -- zsh -x -o shwordsplit scr
///
/// Use with powershell:
///
/// $ watchexec --shell=powershell -- test-connection localhost
///
/// Use with cmd:
///
/// $ watchexec --shell=cmd -- dir
///
/// Use with a different unix shell:
///
/// $ watchexec --shell=bash -- 'echo $BASH_VERSION'
///
/// Use with a unix shell and options:
///
/// $ watchexec --shell='zsh -x -o shwordsplit' -- scr
#[arg(
long,
help_heading = OPTSET_COMMAND,
value_name = "SHELL",
)]
pub shell: Option<String>,
/// Don't use a shell
///
/// This is a shorthand for '--shell=none'.
#[arg(
short = 'n',
help_heading = OPTSET_COMMAND,
)]
pub no_shell: bool,
/// Don't use a shell
///
/// This is a deprecated alias for '--shell=none'.
#[arg(
long,
hide = true,
help_heading = OPTSET_COMMAND,
alias = "no-shell", // deprecated
)]
pub no_shell_long: bool,
/// Shorthand for '--emit-events=none'
///
/// This is the old way to disable event emission into the environment. See '--emit-events' for
/// more.
#[arg(
long,
help_heading = OPTSET_COMMAND,
// TODO: deprecate then remove
)]
pub no_environment: bool,
/// Configure event emission
///
/// Watchexec emits event information when running a command, which can be used by the command
/// to target specific changed files.
///
/// One thing to take care of is assuming inherent behaviour where there is only chance.
/// Notably, it could appear as if the `RENAMED` variable contains both the original and the new
/// path being renamed. In previous versions, it would even appear on some platforms as if the
/// original always came before the new. However, none of this was true. It's impossible to
/// reliably and portably know which changed path is the old or new, "half" renames may appear
/// (only the original, only the new), "unknown" renames may appear (change was a rename, but
/// whether it was the old or new isn't known), rename events might split across two debouncing
/// boundaries, and so on.
///
/// This option controls where that information is emitted. It defaults to 'environment', which
/// sets environment variables with the paths of the affected files, for filesystem events:
///
/// $WATCHEXEC_COMMON_PATH is set to the longest common path of all of the below variables,
/// and so should be prepended to each path to obtain the full/real path. Then:
///
/// - $WATCHEXEC_CREATED_PATH is set when files/folders were created
/// - $WATCHEXEC_REMOVED_PATH is set when files/folders were removed
/// - $WATCHEXEC_RENAMED_PATH is set when files/folders were renamed
/// - $WATCHEXEC_WRITTEN_PATH is set when files/folders were modified
/// - $WATCHEXEC_META_CHANGED_PATH is set when files/folders' metadata were modified
/// - $WATCHEXEC_OTHERWISE_CHANGED_PATH is set for every other kind of pathed event
///
/// Multiple paths are separated by the system path separator, ';' on Windows and ':' on unix.
/// Within each variable, paths are deduplicated and sorted in binary order (i.e. neither
/// Unicode nor locale aware).
///
/// This is the legacy mode and will be deprecated and removed in the future. The environment of
/// a process is a very restricted space, while also limited in what it can usefully represent.
/// Large numbers of files will either cause the environment to be truncated, or may error or
/// crash the process entirely.
///
/// Two new modes are available: 'stdin' writes absolute paths to the stdin of the command,
/// one per line, each prefixed with `create:`, `remove:`, `rename:`, `modify:`, or `other:`,
/// then closes the handle; 'file' writes the same thing to a temporary file, and its path is
/// given with the $WATCHEXEC_EVENTS_FILE environment variable.
///
/// There are also two JSON modes, which are based on JSON objects and can represent the full
/// set of events Watchexec handles. Here's an example of a folder being created on Linux:
///
/// ```json
/// {
/// "tags": [
/// {
/// "kind": "path",
/// "absolute": "/home/user/your/new-folder",
/// "filetype": "dir"
/// },
/// {
/// "kind": "fs",
/// "simple": "create",
/// "full": "Create(Folder)"
/// },
/// {
/// "kind": "source",
/// "source": "filesystem",
/// }
/// ],
/// "metadata": {
/// "notify-backend": "inotify"
/// }
/// }
/// ```
///
/// The fields are as follows:
///
/// - `tags`, structured event data.
/// - `tags[].kind`, which can be:
/// * 'path', along with:
/// + `absolute`, an absolute path.
/// + `filetype`, a file type if known ('dir', 'file', 'symlink', 'other').
/// * 'fs':
/// + `simple`, the "simple" event type ('access', 'create', 'modify', 'remove', or 'other').
/// + `full`, the "full" event type, which is too complex to fully describe here, but looks like 'General(Precise(Specific))'.
/// * 'source', along with:
/// + `source`, the source of the event ('filesystem', 'keyboard', 'mouse', 'os', 'time', 'internal').
/// * 'keyboard', along with:
/// + `keycode`. Currently only the value 'eof' is supported.
/// * 'process', for events caused by processes:
/// + `pid`, the process ID.
/// * 'signal', for signals sent to Watchexec:
/// + `name`, the normalised signal name ('hangup', 'interrupt', 'quit', 'terminate', 'user1', 'user2').
/// * 'completion', for when a command ends:
/// + `disposition`, the exit disposition ('success', 'error', 'signal', 'stop', 'exception', 'continued').
/// + `code`, the exit, stop, or exception code.
/// + `signal`, the signal name or number if the exit was caused by a signal.
///
/// The 'json-stdin' mode will emit JSON events to the standard input of the command, one per
/// line, then close stdin. The 'json-file' mode will create a temporary file, write the
/// events to it, and provide the path to the file with the $WATCHEXEC_EVENTS_FILE
/// environment variable.
///
/// Finally, the special 'none' mode will disable event emission entirely.
#[arg(
hide = true, // until the feature is done
long,
help_heading = OPTSET_COMMAND,
verbatim_doc_comment,
default_value = "environment",
hide_default_value = true,
value_name = "MODE",
)]
pub emit_events_to: EmitEvents,
/// Add env vars to the command
///
/// This is a convenience option for setting environment variables for the command, without
/// setting them for the Watchexec process itself.
///
/// Use key=value syntax. Multiple variables can be set by repeating the option.
#[arg(
long,
short = 'E',
help_heading = OPTSET_COMMAND,
value_name = "KEY=VALUE",
)]
pub env: Vec<String>,
/// Don't use a process group
///
/// By default, Watchexec will run the command in a process group, so that signals and
/// terminations are sent to all processes in the group. Sometimes that's not what you want, and
/// you can disable the behaviour with this option.
#[arg(
long,
help_heading = OPTSET_COMMAND,
)]
pub no_process_group: bool,
/// Testing only: exit Watchexec after the first run
#[arg(short = '1', hide = true)]
pub once: bool,
/// Alert when commands start and end
///
/// With this, Watchexec will emit a desktop notification when a command starts and ends, on
/// supported platforms. On unsupported platforms, it may silently do nothing, or log a warning.
#[arg(long, short = 'N')]
pub notify: bool,
/// Set the project origin
///
/// Watchexec will attempt to discover the project's "origin" (or "root") by searching for a
/// variety of markers, like files or directory patterns. It does its best but sometimes gets it
/// it wrong, and you can override that with this option.
///
/// The project origin is used to determine the path of certain ignore files, which VCS is being
/// used, the meaning of a leading '/' in filtering patterns, and maybe more in the future.
///
/// When set, Watchexec will also not bother searching, which can be significantly faster.
#[arg(
long,
value_hint = ValueHint::DirPath,
value_name = "DIRECTORY",
)]
pub project_origin: Option<PathBuf>,
/// Set the working directory
///
/// By default, the working directory of the command is the working directory of Watchexec. You
/// can change that with this option. Note that paths may be less intuitive to use with this.
#[arg(
long,
value_hint = ValueHint::DirPath,
value_name = "DIRECTORY",
)]
pub workdir: Option<PathBuf>,
/// Filename extensions to filter to
///
/// This is a quick filter to only emit events for files with the given extensions. Extensions
/// can be given with or without the leading dot (e.g. 'js' or '.js'). Multiple extensions can
/// be given by repeating the option or by separating them with commas.
#[arg(
long = "exts",
short = 'e',
help_heading = OPTSET_FILTERING,
value_delimiter = ',',
value_name = "EXTENSIONS",
)]
pub filter_extensions: Vec<String>,
/// Filename patterns to filter to
///
/// Provide a glob-like filter pattern, and only events for files matching the pattern will be
/// emitted. Multiple patterns can be given by repeating the option. Events that are not from
/// files (e.g. signals, keyboard events) will pass through untouched.
#[arg(
long = "filter",
short = 'f',
help_heading = OPTSET_FILTERING,
value_name = "PATTERN",
)]
pub filter_patterns: Vec<String>,
/// Files to load filters from
///
/// Provide a path to a file containing filters, one per line. Empty lines and lines starting
/// with '#' are ignored. Uses the same pattern format as the '--filter' option.
///
/// This can also be used via the $WATCHEXEC_FILTER_FILES environment variable.
#[arg(
long = "filter-file",
help_heading = OPTSET_FILTERING,
value_delimiter = PATH_SEPARATOR.chars().next().unwrap(),
value_hint = ValueHint::FilePath,
value_name = "PATH",
env = "WATCHEXEC_FILTER_FILES",
hide_env = true,
)]
pub filter_files: Vec<PathBuf>,
/// Filename patterns to filter out
///
/// Provide a glob-like filter pattern, and events for files matching the pattern will be
/// excluded. Multiple patterns can be given by repeating the option. Events that are not from
/// files (e.g. signals, keyboard events) will pass through untouched.
#[arg(
long = "ignore",
short = 'i',
help_heading = OPTSET_FILTERING,
value_name = "PATTERN",
)]
pub ignore_patterns: Vec<String>,
/// Files to load ignores from
///
/// Provide a path to a file containing ignores, one per line. Empty lines and lines starting
/// with '#' are ignored. Uses the same pattern format as the '--ignore' option.
///
/// This can also be used via the $WATCHEXEC_IGNORE_FILES environment variable.
#[arg(
long = "ignore-file",
help_heading = OPTSET_FILTERING,
value_delimiter = PATH_SEPARATOR.chars().next().unwrap(),
value_hint = ValueHint::FilePath,
value_name = "PATH",
env = "WATCHEXEC_IGNORE_FILES",
hide_env = true,
)]
pub ignore_files: Vec<PathBuf>,
/// Filesystem events to filter to
///
/// This is a quick filter to only emit events for the given types of filesystem changes. Choose
/// from 'access', 'create', 'remove', 'rename', 'modify', 'metadata'. Multiple types can be
/// given by repeating the option or by separating them with commas. By default, this is all
/// types except for 'access'.
///
/// This may apply filtering at the kernel level when possible, which can be more efficient, but
/// may be more confusing when reading the logs.
#[arg(
long = "fs-events",
help_heading = OPTSET_FILTERING,
default_value = "create,remove,rename,modify,metadata",
value_delimiter = ',',
hide_default_value = true,
value_name = "EVENTS",
)]
pub filter_fs_events: Vec<FsEvent>,
/// Don't emit fs events for metadata changes
///
/// This is a shorthand for '--fs-events create,remove,rename,modify'. Using it alongside the
/// '--fs-events' option is non-sensical and not allowed.
#[arg(
long = "no-meta",
help_heading = OPTSET_FILTERING,
conflicts_with = "filter_fs_events",
)]
pub filter_fs_meta: bool,
/// Print events that trigger actions
///
/// This prints the events that triggered the action when handling it (after debouncing), in a
/// human readable form. This is useful for debugging filters.
///
/// Use '-v' when you need more diagnostic information.
#[arg(
long,
alias = "changes-only", // deprecated
help_heading = OPTSET_DEBUGGING,
)]
pub print_events: bool,
/// Set diagnostic log level
///
/// This enables diagnostic logging, which is useful for investigating bugs or gaining more
/// insight into faulty filters or "missing" events. Use multiple times to increase verbosity.
///
/// Goes up to '-vvvv'. When submitting bug reports, default to a '-vvv' log level.
///
/// You may want to use with '--log-file' to avoid polluting your terminal.
///
/// Setting $RUST_LOG also works, and takes precendence, but is not recommended. However, using
/// $RUST_LOG is the only way to get logs from before these options are parsed.
#[arg(
long,
short,
help_heading = OPTSET_DEBUGGING,
action = ArgAction::Count,
num_args = 0,
)]
pub verbose: Option<u8>,
/// Write diagnostic logs to a file
///
/// This writes diagnostic logs to a file, instead of the terminal, in JSON format. If a log
/// level was not already specified, this will set it to '-vvv'.
///
/// If a path is not provided, the default is the working directory. Note that with
/// '--ignore-nothing', the write events to the log will likely get picked up by Watchexec,
/// causing a loop; prefer setting a path outside of the watched directory.
///
/// If the path provided is a directory, a file will be created in that directory. The file name
/// will be the current date and time, in the format 'watchexec.YYYY-MM-DDTHH-MM-SSZ.log'.
#[arg(
long,
help_heading = OPTSET_DEBUGGING,
num_args = 0..=1,
default_missing_value = ".",
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub log_file: Option<PathBuf>,
/// Show the manual page
///
/// This shows the manual page for Watchexec, if the output is a terminal and the 'man' program
/// is available. If not, the manual page is printed to stdout in ROFF format (suitable for
/// writing to a watchexec.1 file).
#[arg(
long,
help_heading = OPTSET_DEBUGGING,
conflicts_with_all = ["command", "completions"],
)]
pub manpage: bool,
/// Generate a shell completions script
///
/// Provides a completions script or configuration for the given shell. If Watchexec is not
/// distributed with pre-generated completions, you can use this to generate them yourself.
///
/// Supported shells: bash, elvish, fish, nu, powershell, zsh.
#[arg(
long,
help_heading = OPTSET_DEBUGGING,
conflicts_with_all = ["command", "manpage"],
)]
pub completions: Option<ShellCompletion>,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum EmitEvents {
#[default]
Environment,
Stdin,
File,
JsonStdin,
JsonFile,
None,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum OnBusyUpdate {
#[default]
Queue,
DoNothing,
Restart,
Signal,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum ClearMode {
#[default]
Clear,
Reset,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum FsEvent {
Access,
Create,
Remove,
Rename,
Modify,
Metadata,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum ShellCompletion {
Bash,
Elvish,
Fish,
Nu,
Powershell,
Zsh,
}
#[derive(Clone, Copy, Debug)]
pub struct TimeSpan<const UNITLESS_NANOS_MULTIPLIER: u64 = { 1_000_000_000 }>(pub Duration);
impl<const UNITLESS_NANOS_MULTIPLIER: u64> FromStr for TimeSpan<UNITLESS_NANOS_MULTIPLIER> {
type Err = humantime::DurationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<u64>() {
Ok(unitless) => Ok(Duration::from_nanos(unitless * UNITLESS_NANOS_MULTIPLIER)),
Err(_) => humantime::parse_duration(s),
}
.map(TimeSpan)
}
}
#[inline]
pub fn get_args() -> Args {
use tracing::{debug, warn};
if std::env::var("RUST_LOG").is_ok() {
warn!("⚠ RUST_LOG environment variable set, logging options have no effect");
}
if let Ok(filt) = std::env::var("WATCHEXEC_FILTERER") {
warn!("WATCHEXEC_FILTERER is deprecated");
if filt == "tagged" {
eprintln!("Tagged filterer has been removed. Open an issue if you have no workaround.");
}
}
debug!("expanding @argfile arguments if any");
let args = argfile::expand_args(argfile::parse_fromfile, argfile::PREFIX).unwrap();
debug!("parsing arguments");
let mut args = Args::parse_from(args);
if args.kill {
args.signal = Some(SubSignal::ForceStop);
}
if args.signal.is_some() {
args.on_busy_update = OnBusyUpdate::Signal;
} else if args.restart {
args.on_busy_update = OnBusyUpdate::Restart;
} else if args.watch_when_idle {
args.on_busy_update = OnBusyUpdate::DoNothing;
}
if args.no_environment {
args.emit_events_to = EmitEvents::None;
}
if args.filter_fs_meta {
args.filter_fs_events = vec![
FsEvent::Create,
FsEvent::Remove,
FsEvent::Rename,
FsEvent::Modify,
];
}
debug!(?args, "got arguments");
args
}