From bbaaff8a1ee4da8ae1950b0125c2fa26b50a5c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sun, 11 Apr 2021 01:33:40 +1200 Subject: [PATCH] Split args from cli in preparation to remove it from lib entirely --- src/args.rs | 226 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cli.rs | 240 +--------------------------------------------------- src/lib.rs | 3 +- src/main.rs | 9 +- 4 files changed, 236 insertions(+), 242 deletions(-) create mode 100644 src/args.rs diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..4e91255 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,226 @@ +use clap::{App, Arg, value_t, values_t, crate_version}; +use log::LevelFilter; +use std::{ + ffi::OsString, + path::{PathBuf, MAIN_SEPARATOR}, +}; + +use crate::error; +use crate::config::{Config, ConfigBuilder}; + +pub fn get_args() -> error::Result<(Config, LevelFilter)> { + get_args_impl(None::<&[&str]>) +} + +#[allow(dead_code)] +pub fn get_args_from(from: I) -> error::Result<(Config, LevelFilter)> +where + I: IntoIterator, + T: Into + Clone, +{ + get_args_impl(Some(from)) +} + +fn get_args_impl(from: Option) -> error::Result<(Config, LevelFilter)> +where + I: IntoIterator, + T: Into + Clone, +{ + let app = App::new("watchexec") + .version(crate_version!()) + .about("Execute commands when watched files change") + .arg(Arg::with_name("command") + .help("Command to execute") + .multiple(true) + .required(true)) + .arg(Arg::with_name("extensions") + .help("Comma-separated list of file extensions to watch (js,css,html)") + .short("e") + .long("exts") + .takes_value(true)) + .arg(Arg::with_name("path") + .help("Watch a specific file or directory") + .short("w") + .long("watch") + .number_of_values(1) + .multiple(true) + .takes_value(true)) + .arg(Arg::with_name("clear") + .help("Clear screen before executing command") + .short("c") + .long("clear")) + .arg(Arg::with_name("restart") + .help("Restart the process if it's still running") + .short("r") + .long("restart")) + .arg(Arg::with_name("signal") + .help("Send signal to process upon changes, e.g. SIGHUP") + .short("s") + .long("signal") + .takes_value(true) + .number_of_values(1) + .value_name("signal")) + .arg(Arg::with_name("kill") + .hidden(true) + .short("k") + .long("kill")) + .arg(Arg::with_name("debounce") + .help("Set the timeout between detected change and command execution, defaults to 500ms") + .takes_value(true) + .value_name("milliseconds") + .short("d") + .long("debounce")) + .arg(Arg::with_name("verbose") + .help("Print debugging messages to stderr") + .short("v") + .long("verbose")) + .arg(Arg::with_name("changes") + .help("Only print path change information. Overridden by --verbose") + .long("changes-only")) + .arg(Arg::with_name("filter") + .help("Ignore all modifications except those matching the pattern") + .short("f") + .long("filter") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("ignore") + .help("Ignore modifications to paths matching the pattern") + .short("i") + .long("ignore") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("no-vcs-ignore") + .help("Skip auto-loading of .gitignore files for filtering") + .long("no-vcs-ignore")) + .arg(Arg::with_name("no-ignore") + .help("Skip auto-loading of ignore files (.gitignore, .ignore, etc.) for filtering") + .long("no-ignore")) + .arg(Arg::with_name("no-default-ignore") + .help("Skip auto-ignoring of commonly ignored globs") + .long("no-default-ignore")) + .arg(Arg::with_name("postpone") + .help("Wait until first change to execute command") + .short("p") + .long("postpone")) + .arg(Arg::with_name("poll") + .help("Force polling mode (interval in milliseconds)") + .long("force-poll") + .value_name("interval")) + .arg(Arg::with_name("no-shell") + .help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'") + .short("n") + .long("no-shell")) + .arg(Arg::with_name("no-meta") + .help("Ignore metadata changes") + .long("no-meta")) + .arg(Arg::with_name("no-environment") + .help("Do not set WATCHEXEC_*_PATH environment variables for child process") + .long("no-environment")) + .arg(Arg::with_name("once").short("1").hidden(true)) + .arg(Arg::with_name("watch-when-idle") + .help("Ignore events while the process is still running") + .short("W") + .long("watch-when-idle")); + + let args = match from { + None => app.get_matches(), + Some(i) => app.get_matches_from(i), + }; + + let mut builder = ConfigBuilder::default(); + + let cmd: Vec = values_t!(args.values_of("command"), String).map_err(|err| err.to_string())?; + builder.cmd(cmd); + + let paths: Vec = values_t!(args.values_of("path"), String) + .unwrap_or_else(|_| vec![".".into()]) + .iter() + .map(|string_path| string_path.into()) + .collect(); + builder.paths(paths); + + // Treat --kill as --signal SIGKILL (for compatibility with deprecated syntax) + if args.is_present("kill") { + builder.signal("SIGKILL"); + } + + if let Some(signal) = args.value_of("signal") { + builder.signal(signal); + } + + let mut filters = values_t!(args.values_of("filter"), String).unwrap_or_else(|_| Vec::new()); + if let Some(extensions) = args.values_of("extensions") { + for exts in extensions { // TODO: refactor with flatten() + filters.extend(exts.split(',').filter_map(|ext| { + if ext.is_empty() { + None + } else { + Some(format!("*.{}", ext.replace(".", ""))) + } + })); + } + } + + builder.filters(filters); + + let mut ignores = vec![]; + let default_ignores = vec![ + format!("**{}.DS_Store", MAIN_SEPARATOR), + String::from("*.py[co]"), + String::from("#*#"), + String::from(".#*"), + String::from(".*.kate-swp"), + String::from(".*.sw?"), + String::from(".*.sw?x"), + format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), + format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), + format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), + ]; + + if args.occurrences_of("no-default-ignore") == 0 { + ignores.extend(default_ignores) + }; + ignores.extend(values_t!(args.values_of("ignore"), String).unwrap_or_else(|_| Vec::new())); + + builder.ignores(ignores); + + if args.occurrences_of("poll") > 0 { + builder.poll_interval(value_t!(args.value_of("poll"), u32).unwrap_or_else(|e| e.exit())); + } + + if args.occurrences_of("debounce") > 0 { + builder.debounce(value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit())); + } + + // TODO: check how postpone + signal behaves + + builder.clear_screen(args.is_present("clear")); + builder.restart(args.is_present("restart")); + builder.run_initially(!args.is_present("postpone")); + builder.no_shell(args.is_present("no-shell")); + builder.no_meta(args.is_present("no-meta")); + builder.no_environment(args.is_present("no-environment")); + builder.no_vcs_ignore(args.is_present("no-vcs-ignore")); + builder.no_ignore(args.is_present("no-ignore")); + builder.poll(args.occurrences_of("poll") > 0); + builder.watch_when_idle(args.is_present("watch-when-idle")); + + let mut config = builder.build()?; + if args.is_present("once") { + config.once = true; + } + + let loglevel = if args.is_present("verbose") { + LevelFilter::Debug + } else if args.is_present("changes") { + LevelFilter::Info + } else { + LevelFilter::Warn + }; + + Ok((config, loglevel)) +} diff --git a/src/cli.rs b/src/cli.rs index 9e73abd..7d5ee69 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,26 +1,6 @@ -//! CLI arguments and library Config struct -//! -//! The [`Config`] struct is not constructable, use [`ConfigBuilder`]. -//! -//! # Examples -//! -//! ``` -//! # use watchexec::cli::ConfigBuilder; -//! ConfigBuilder::default() -//! .cmd(vec!["echo hello world".into()]) -//! .paths(vec![".".into()]) -//! .build() -//! .expect("mission failed"); -//! ``` +//! CLI utilities. -use crate::error; -use clap::{App, Arg}; -use log::LevelFilter; -use std::{ - ffi::OsString, - path::{PathBuf, MAIN_SEPARATOR}, - process::Command, -}; +use std::process::Command; use crate::config::{Config, ConfigBuilder}; @@ -48,219 +28,7 @@ pub fn clear_screen() { } #[deprecated(since = "1.15.0", note = "this will be removed from the library API. use the builder")] -pub fn get_args() -> error::Result<(Config, LevelFilter)> { - get_args_impl(None::<&[&str]>) -} +pub use crate::args::get_args; #[deprecated(since = "1.15.0", note = "this will be removed from the library API. use the builder")] -pub fn get_args_from(from: I) -> error::Result<(Config, LevelFilter)> -where - I: IntoIterator, - T: Into + Clone, -{ - get_args_impl(Some(from)) -} - -fn get_args_impl(from: Option) -> error::Result<(Config, LevelFilter)> -where - I: IntoIterator, - T: Into + Clone, -{ - let app = App::new("watchexec") - .version(crate_version!()) - .about("Execute commands when watched files change") - .arg(Arg::with_name("command") - .help("Command to execute") - .multiple(true) - .required(true)) - .arg(Arg::with_name("extensions") - .help("Comma-separated list of file extensions to watch (js,css,html)") - .short("e") - .long("exts") - .takes_value(true)) - .arg(Arg::with_name("path") - .help("Watch a specific file or directory") - .short("w") - .long("watch") - .number_of_values(1) - .multiple(true) - .takes_value(true)) - .arg(Arg::with_name("clear") - .help("Clear screen before executing command") - .short("c") - .long("clear")) - .arg(Arg::with_name("restart") - .help("Restart the process if it's still running") - .short("r") - .long("restart")) - .arg(Arg::with_name("signal") - .help("Send signal to process upon changes, e.g. SIGHUP") - .short("s") - .long("signal") - .takes_value(true) - .number_of_values(1) - .value_name("signal")) - .arg(Arg::with_name("kill") - .hidden(true) - .short("k") - .long("kill")) - .arg(Arg::with_name("debounce") - .help("Set the timeout between detected change and command execution, defaults to 500ms") - .takes_value(true) - .value_name("milliseconds") - .short("d") - .long("debounce")) - .arg(Arg::with_name("verbose") - .help("Print debugging messages to stderr") - .short("v") - .long("verbose")) - .arg(Arg::with_name("changes") - .help("Only print path change information. Overridden by --verbose") - .long("changes-only")) - .arg(Arg::with_name("filter") - .help("Ignore all modifications except those matching the pattern") - .short("f") - .long("filter") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("ignore") - .help("Ignore modifications to paths matching the pattern") - .short("i") - .long("ignore") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("no-vcs-ignore") - .help("Skip auto-loading of .gitignore files for filtering") - .long("no-vcs-ignore")) - .arg(Arg::with_name("no-ignore") - .help("Skip auto-loading of ignore files (.gitignore, .ignore, etc.) for filtering") - .long("no-ignore")) - .arg(Arg::with_name("no-default-ignore") - .help("Skip auto-ignoring of commonly ignored globs") - .long("no-default-ignore")) - .arg(Arg::with_name("postpone") - .help("Wait until first change to execute command") - .short("p") - .long("postpone")) - .arg(Arg::with_name("poll") - .help("Force polling mode (interval in milliseconds)") - .long("force-poll") - .value_name("interval")) - .arg(Arg::with_name("no-shell") - .help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'") - .short("n") - .long("no-shell")) - .arg(Arg::with_name("no-meta") - .help("Ignore metadata changes") - .long("no-meta")) - .arg(Arg::with_name("no-environment") - .help("Do not set WATCHEXEC_*_PATH environment variables for child process") - .long("no-environment")) - .arg(Arg::with_name("once").short("1").hidden(true)) - .arg(Arg::with_name("watch-when-idle") - .help("Ignore events while the process is still running") - .short("W") - .long("watch-when-idle")); - - let args = match from { - None => app.get_matches(), - Some(i) => app.get_matches_from(i), - }; - - let mut builder = ConfigBuilder::default(); - - let cmd: Vec = values_t!(args.values_of("command"), String).map_err(|err| err.to_string())?; - builder.cmd(cmd); - - let paths: Vec = values_t!(args.values_of("path"), String) - .unwrap_or_else(|_| vec![".".into()]) - .iter() - .map(|string_path| string_path.into()) - .collect(); - builder.paths(paths); - - // Treat --kill as --signal SIGKILL (for compatibility with deprecated syntax) - if args.is_present("kill") { - builder.signal("SIGKILL"); - } - - if let Some(signal) = args.value_of("signal") { - builder.signal(signal); - } - - let mut filters = values_t!(args.values_of("filter"), String).unwrap_or_else(|_| Vec::new()); - if let Some(extensions) = args.values_of("extensions") { - for exts in extensions { // TODO: refactor with flatten() - filters.extend(exts.split(',').filter_map(|ext| { - if ext.is_empty() { - None - } else { - Some(format!("*.{}", ext.replace(".", ""))) - } - })); - } - } - - builder.filters(filters); - - let mut ignores = vec![]; - let default_ignores = vec![ - format!("**{}.DS_Store", MAIN_SEPARATOR), - String::from("*.py[co]"), - String::from("#*#"), - String::from(".#*"), - String::from(".*.kate-swp"), - String::from(".*.sw?"), - String::from(".*.sw?x"), - format!("**{}.git{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), - format!("**{}.hg{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), - format!("**{}.svn{}**", MAIN_SEPARATOR, MAIN_SEPARATOR), - ]; - - if args.occurrences_of("no-default-ignore") == 0 { - ignores.extend(default_ignores) - }; - ignores.extend(values_t!(args.values_of("ignore"), String).unwrap_or_else(|_| Vec::new())); - - builder.ignores(ignores); - - if args.occurrences_of("poll") > 0 { - builder.poll_interval(value_t!(args.value_of("poll"), u32).unwrap_or_else(|e| e.exit())); - } - - if args.occurrences_of("debounce") > 0 { - builder.debounce(value_t!(args.value_of("debounce"), u64).unwrap_or_else(|e| e.exit())); - } - - // TODO: check how postpone + signal behaves - - builder.clear_screen(args.is_present("clear")); - builder.restart(args.is_present("restart")); - builder.run_initially(!args.is_present("postpone")); - builder.no_shell(args.is_present("no-shell")); - builder.no_meta(args.is_present("no-meta")); - builder.no_environment(args.is_present("no-environment")); - builder.no_vcs_ignore(args.is_present("no-vcs-ignore")); - builder.no_ignore(args.is_present("no-ignore")); - builder.poll(args.occurrences_of("poll") > 0); - builder.watch_when_idle(args.is_present("watch-when-idle")); - - let mut config = builder.build()?; - if args.is_present("once") { - config.once = true; - } - - let loglevel = if args.is_present("verbose") { - LevelFilter::Debug - } else if args.is_present("changes") { - LevelFilter::Info - } else { - LevelFilter::Warn - }; - - Ok((config, loglevel)) -} +pub use crate::args::get_args_from; diff --git a/src/lib.rs b/src/lib.rs index 4235ad6..e7ed46b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,6 @@ clippy::result_unwrap_used, )] -#[macro_use] -extern crate clap; #[macro_use] extern crate derive_builder; #[macro_use] @@ -23,6 +21,7 @@ extern crate log; #[macro_use] extern crate lazy_static; +mod args; pub mod cli; pub mod config; pub mod error; diff --git a/src/main.rs b/src/main.rs index 4c7c15d..24e19c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ use std::io::Write; -extern crate watchexec; +// until args.rs is removed from the lib +pub(crate) use watchexec::{error, config}; -use watchexec::{cli, error, run}; +mod args; fn main() -> error::Result<()> { #[allow(deprecated)] - let (args, loglevel) = cli::get_args()?; + let (args, loglevel) = args::get_args()?; init_logger(loglevel); - run(args) + watchexec::run(args) } fn init_logger(level: log::LevelFilter) {