2021-04-10 15:33:40 +02:00
use std ::{
2021-08-24 10:23:37 +02:00
env ,
ffi ::OsString ,
fs ::File ,
io ::{ BufRead , BufReader } ,
path ::Path ,
2021-04-10 15:33:40 +02:00
} ;
2022-06-11 10:05:11 +02:00
use clap ::{ crate_version , Arg , ArgMatches , Command } ;
2021-10-09 07:43:51 +02:00
use miette ::{ Context , IntoDiagnostic , Result } ;
2022-06-17 00:55:25 +02:00
use tracing ::debug ;
2021-07-30 18:24:05 +02:00
2022-06-11 10:05:11 +02:00
const OPTSET_FILTERING : & str = " Filtering options " ;
const OPTSET_COMMAND : & str = " Command options " ;
const OPTSET_DEBUGGING : & str = " Debugging options " ;
const OPTSET_OUTPUT : & str = " Output options " ;
const OPTSET_BEHAVIOUR : & str = " Behaviour options " ;
2021-11-22 09:23:29 +01:00
2022-06-11 10:05:11 +02:00
pub fn get_args ( tagged_filterer : bool ) -> Result < ArgMatches > {
2022-06-28 14:21:19 +02:00
let mut app = Command ::new ( " watchexec " )
2021-08-24 12:45:31 +02:00
. version ( crate_version! ( ) )
. about ( " Execute commands when watched files change " )
. 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). " )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " command " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_COMMAND ) )
2021-08-24 12:45:31 +02:00
. help ( " Command to execute " )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2021-08-24 12:45:31 +02:00
. required ( true ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " paths " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
2021-08-24 12:45:31 +02:00
. help ( " Watch a specific file or directory " )
2022-06-11 10:05:11 +02:00
. short ( 'w' )
2021-08-24 12:45:31 +02:00
. long ( " watch " )
2021-08-24 12:53:44 +02:00
. value_name ( " path " )
2021-08-24 12:45:31 +02:00
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2022-06-28 03:17:17 +02:00
. takes_value ( true )
. allow_invalid_utf8 ( true ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " clear " )
2022-04-15 05:30:00 +02:00
. help_heading ( Some ( OPTSET_OUTPUT ) )
2021-08-24 12:45:31 +02:00
. help ( " Clear screen before executing command " )
2022-06-11 10:05:11 +02:00
. short ( 'c' )
2021-08-24 12:45:31 +02:00
. long ( " clear " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " on-busy-update " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Select the behaviour to use when receiving events while the command is running. Current default is queue, will change to do-nothing in 2.0. " )
. takes_value ( true )
2022-09-07 04:15:38 +02:00
. possible_values ( [ " do-nothing " , " queue " , " restart " , " signal " ] )
2021-08-24 12:45:31 +02:00
. long ( " on-busy-update " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " restart " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Restart the process if it's still running. Shorthand for --on-busy-update=restart " )
2022-06-11 10:05:11 +02:00
. short ( 'r' )
2021-08-24 12:45:31 +02:00
. long ( " restart " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " signal " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Specify the signal to send when using --on-busy-update=signal " )
2022-06-11 10:05:11 +02:00
. short ( 's' )
2021-08-24 12:45:31 +02:00
. long ( " signal " )
. takes_value ( true )
2022-01-24 08:50:27 +01:00
. value_name ( " signal " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " kill " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2022-06-11 10:05:11 +02:00
. hide ( true )
. short ( 'k' )
2021-08-24 12:45:31 +02:00
. long ( " kill " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " debounce " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2022-01-22 02:45:43 +01:00
. help ( " Set the timeout between detected change and command execution, defaults to 50ms " )
2021-08-24 12:45:31 +02:00
. takes_value ( true )
. value_name ( " milliseconds " )
2022-06-11 10:05:11 +02:00
. short ( 'd' )
2021-08-24 12:45:31 +02:00
. long ( " debounce " ) )
2022-12-02 00:19:04 +01:00
. arg ( Arg ::new ( " stdin-quit " )
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
. help ( " Stop watching when stdin closes " )
. long ( " stdin-quit " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " verbose " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_DEBUGGING ) )
2022-01-15 03:14:25 +01:00
. help ( " Print debugging messages (-v, -vv, -vvv, -vvvv; use -vvv for bug reports) " )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
. short ( 'v' )
2021-08-24 12:45:31 +02:00
. long ( " verbose " ) )
2022-06-16 17:56:59 +02:00
. arg ( Arg ::new ( " log-file " )
. help_heading ( Some ( OPTSET_DEBUGGING ) )
. help ( " Write debugging messages to file in JSON format (use for bug reports) " )
. long ( " log-file " )
. takes_value ( true )
2022-06-28 03:17:17 +02:00
. value_name ( " path " )
. allow_invalid_utf8 ( true ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " print-events " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_DEBUGGING ) )
2021-08-24 12:45:31 +02:00
. help ( " Print events that trigger actions " )
. long ( " print-events " )
. alias ( " changes-only " ) ) // --changes-only is deprecated (remove at v2)
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-vcs-ignore " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
2021-12-29 07:10:08 +01:00
. help ( " Skip auto-loading of VCS (Git, etc) ignore files " )
2021-08-24 12:45:31 +02:00
. long ( " no-vcs-ignore " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-project-ignore " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
2021-12-29 07:10:08 +01:00
. help ( " Skip auto-loading of project ignore files (.gitignore, .ignore, etc) " )
2022-01-16 05:34:06 +01:00
. long ( " no-project-ignore " )
. alias ( " no-ignore " ) ) // --no-ignore is deprecated (remove at v2)
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-default-ignore " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
2021-08-24 12:45:31 +02:00
. help ( " Skip auto-ignoring of commonly ignored globs " )
. long ( " no-default-ignore " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-global-ignore " )
2021-12-29 06:39:31 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Skip auto-loading of global or environment-wide ignore files " )
2021-12-29 06:53:47 +01:00
. long ( " no-global-ignore " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " postpone " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Wait until first change to execute command " )
2022-06-11 10:05:11 +02:00
. short ( 'p' )
2021-08-24 12:45:31 +02:00
. long ( " postpone " ) )
2022-06-17 00:55:25 +02:00
. 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 " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " poll " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Force polling mode (interval in milliseconds) " )
. long ( " force-poll " )
. value_name ( " interval " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " shell " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_COMMAND ) )
2021-08-24 12:45:31 +02:00
. help ( if cfg! ( windows ) {
" Use a different shell, or `none`. Try --shell=powershell, which will become the default in 2.0. "
} else {
2022-01-22 02:44:03 +01:00
" Use a different shell, or `none`. Defaults to `sh` (until 2.0, where that will change to `$SHELL`). E.g. --shell=bash "
2021-08-24 12:45:31 +02:00
} )
. takes_value ( true )
. long ( " shell " ) )
// -n short form will not be removed, and instead become a shorthand for --shell=none
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-shell " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_COMMAND ) )
2021-08-24 12:45:31 +02:00
. help ( " Do not wrap command in a shell. Deprecated: use --shell=none instead. " )
2022-06-11 10:05:11 +02:00
. short ( 'n' )
2021-08-24 12:45:31 +02:00
. long ( " no-shell " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-environment " )
2022-04-15 05:30:00 +02:00
. help_heading ( Some ( OPTSET_COMMAND ) )
2021-12-29 09:36:42 +01:00
. help ( " Do not set WATCHEXEC_*_PATH environment variables for the command " )
2021-08-24 12:45:31 +02:00
. long ( " no-environment " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " no-process-group " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_COMMAND ) )
2021-08-24 12:45:31 +02:00
. help ( " Do not use a process group when running the command " )
. long ( " no-process-group " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " once " ) . short ( '1' ) . hide ( true ) )
. arg ( Arg ::new ( " watch-when-idle " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_BEHAVIOUR ) )
2021-08-24 12:45:31 +02:00
. help ( " Deprecated alias for --on-busy-update=do-nothing, which will become the default in 2.0. " )
2022-06-11 10:05:11 +02:00
. short ( 'W' )
. hide ( true )
2021-08-24 12:45:31 +02:00
. long ( " watch-when-idle " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " notif " )
2021-11-22 09:23:29 +01:00
. help_heading ( Some ( OPTSET_OUTPUT ) )
2021-12-24 07:06:01 +01:00
. help ( " Send a desktop notification when the command ends " )
2022-06-11 10:05:11 +02:00
. short ( 'N' )
2022-01-25 16:19:07 +01:00
. long ( " notify " ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " project-origin " )
2022-01-25 16:19:07 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Override the project origin: the directory from which ignore files are detected " )
. value_name ( " path " )
2022-06-28 03:17:17 +02:00
. long ( " project-origin " )
. allow_invalid_utf8 ( true ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " command-workdir " )
2022-04-15 05:30:00 +02:00
. help_heading ( Some ( OPTSET_COMMAND ) )
. help ( " Change the working directory of the command " )
. value_name ( " path " )
2022-06-28 03:17:17 +02:00
. long ( " workdir " )
. allow_invalid_utf8 ( true ) )
2022-06-11 10:05:11 +02:00
. arg ( Arg ::new ( " command-env " )
2022-04-15 05:30:00 +02:00
. help_heading ( Some ( OPTSET_COMMAND ) )
. help ( " Add an environment variable to the command " )
. value_name ( " name=value " )
. long ( " env " )
2022-06-11 10:05:11 +02:00
. short ( 'E' )
2022-04-15 05:30:00 +02:00
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true ) ) ;
2021-04-10 15:33:40 +02:00
2022-06-28 14:21:19 +02:00
// none of these are mutually exclusive, but it should be rare to unintentionally have two on
if cfg! ( debug_assertions ) {
app = app . before_help ( " ⚠ DEBUG BUILD ⚠ " ) ;
} else if cfg! ( feature = " dev-console " ) {
app = app . before_help ( " ⚠ DEV CONSOLE ENABLED ⚠ " ) ;
} else if std ::env ::var ( " RUST_LOG " ) . is_ok ( ) {
app =
app . before_help ( " ⚠ RUST_LOG environment variable set, logging options have no effect " ) ;
}
2021-12-23 15:56:03 +01:00
let app = if tagged_filterer {
app . arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " filter " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Add tagged filter (e.g. 'path=foo*', 'type=dir', 'kind=Create(*)') " )
2022-06-11 10:05:11 +02:00
. short ( 'f' )
2021-12-23 15:56:03 +01:00
. long ( " filter " )
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2021-12-23 15:56:03 +01:00
. takes_value ( true )
. value_name ( " tagged filter " ) ,
)
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " filter-files " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Load tagged filters from a file " )
2022-06-11 10:05:11 +02:00
. short ( 'F' )
2021-12-23 15:56:03 +01:00
. long ( " filter-file " )
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2021-12-23 15:56:03 +01:00
. takes_value ( true )
2022-06-28 03:17:17 +02:00
. value_name ( " path " )
. allow_invalid_utf8 ( true ) ,
2021-12-23 15:56:03 +01:00
)
2021-12-29 06:39:31 +01:00
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " no-global-filters " )
2021-12-29 06:39:31 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Skip auto-loading of global or environment-wide ignore files " )
. long ( " no-default-filters " ) ,
)
2021-12-23 15:56:03 +01:00
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " no-meta " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Ignore metadata changes (equivalent of `-f 'kind*!Modify(Metadata(*))'`) " )
. long ( " no-meta " ) ,
)
} else {
app . arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " extensions " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Comma-separated list of file extensions to watch (e.g. js,css,html) " )
2022-06-11 10:05:11 +02:00
. short ( 'e' )
2021-12-23 15:56:03 +01:00
. long ( " exts " )
2022-06-28 03:17:17 +02:00
. takes_value ( true )
. allow_invalid_utf8 ( true ) ,
2021-12-23 15:56:03 +01:00
)
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " filter " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Ignore all modifications except those matching the pattern " )
2022-06-11 10:05:11 +02:00
. short ( 'f' )
2021-12-23 15:56:03 +01:00
. long ( " filter " )
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2021-12-23 15:56:03 +01:00
. takes_value ( true )
. value_name ( " pattern " ) ,
)
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " ignore " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Ignore modifications to paths matching the pattern " )
2022-06-11 10:05:11 +02:00
. short ( 'i' )
2021-12-23 15:56:03 +01:00
. long ( " ignore " )
. number_of_values ( 1 )
2022-06-11 10:05:11 +02:00
. multiple_occurrences ( true )
2021-12-23 15:56:03 +01:00
. takes_value ( true )
. value_name ( " pattern " ) ,
)
. arg (
2022-06-11 10:05:11 +02:00
Arg ::new ( " no-meta " )
2021-12-23 15:56:03 +01:00
. help_heading ( Some ( OPTSET_FILTERING ) )
. help ( " Ignore metadata changes " )
. long ( " no-meta " ) ,
)
} ;
2021-08-24 10:23:37 +02:00
let mut raw_args : Vec < OsString > = env ::args_os ( ) . collect ( ) ;
2021-04-10 15:33:40 +02:00
2021-08-24 10:23:37 +02:00
if let Some ( first ) = raw_args . get ( 1 ) . and_then ( | s | s . to_str ( ) ) {
if let Some ( arg_path ) = first . strip_prefix ( '@' ) . map ( Path ::new ) {
let arg_file = BufReader ::new (
File ::open ( arg_path )
2021-10-09 07:43:51 +02:00
. into_diagnostic ( )
2023-01-06 14:53:49 +01:00
. wrap_err_with ( | | format! ( " Failed to open argument file {arg_path:?} " ) ) ? ,
2021-08-24 10:23:37 +02:00
) ;
2021-04-10 15:33:40 +02:00
2021-08-24 10:23:37 +02:00
let mut more_args : Vec < OsString > = arg_file
. lines ( )
2021-10-09 07:43:51 +02:00
. map ( | l | l . map ( OsString ::from ) . into_diagnostic ( ) )
2021-08-24 10:23:37 +02:00
. collect ::< Result < _ > > ( ) ? ;
2021-04-10 15:33:40 +02:00
2021-08-24 10:23:37 +02:00
more_args . insert ( 0 , raw_args . remove ( 0 ) ) ;
more_args . extend ( raw_args . into_iter ( ) . skip ( 1 ) ) ;
raw_args = more_args ;
}
}
2021-04-10 16:36:10 +02:00
2022-06-17 00:55:25 +02:00
debug! ( ? raw_args , " parsing arguments " ) ;
2021-08-24 10:23:37 +02:00
Ok ( app . get_matches_from ( raw_args ) )
2021-04-10 16:36:10 +02:00
}