mirror of
https://github.com/watchexec/watchexec.git
synced 2024-09-28 22:21:33 +02:00
feat(cli): add -W for non-recursive watches
This commit is contained in:
parent
0504c6c24f
commit
9c04011cde
@ -1,6 +1,9 @@
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
ffi::{OsStr, OsString},
|
||||
path::PathBuf,
|
||||
fs::canonicalize,
|
||||
mem::take,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
@ -11,7 +14,7 @@ use clap::{
|
||||
};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use watchexec::paths::PATH_SEPARATOR;
|
||||
use watchexec::{paths::PATH_SEPARATOR, sources::fs::WatchedPath};
|
||||
use watchexec_signals::Signal;
|
||||
|
||||
use crate::filterer::parse::parse_filter_program;
|
||||
@ -128,7 +131,25 @@ pub struct Args {
|
||||
value_hint = ValueHint::AnyPath,
|
||||
value_name = "PATH",
|
||||
)]
|
||||
pub paths: Vec<PathBuf>,
|
||||
pub recursive_paths: Vec<PathBuf>,
|
||||
|
||||
/// Watch a specific directory, non-recursively
|
||||
///
|
||||
/// Unlike '-w', folders watched with this option are not recursed into.
|
||||
///
|
||||
/// This option can be specified multiple times to watch multiple directories non-recursively.
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long = "watch-non-recursive",
|
||||
help_heading = OPTSET_FILTERING,
|
||||
value_hint = ValueHint::AnyPath,
|
||||
value_name = "PATH",
|
||||
)]
|
||||
pub non_recursive_paths: Vec<PathBuf>,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[arg(skip)]
|
||||
pub paths: Vec<WatchedPath>,
|
||||
|
||||
/// Clear screen before running command
|
||||
///
|
||||
@ -1221,6 +1242,61 @@ pub async fn get_args() -> Result<Args> {
|
||||
.exit();
|
||||
}
|
||||
|
||||
args.workdir = Some(if let Some(w) = take(&mut args.workdir) {
|
||||
w
|
||||
} else {
|
||||
let curdir = std::env::current_dir().into_diagnostic()?;
|
||||
canonicalize(curdir).into_diagnostic()?
|
||||
});
|
||||
debug!(workdir=?args.workdir, "current directory");
|
||||
|
||||
let project_origin = if let Some(p) = take(&mut args.project_origin) {
|
||||
p
|
||||
} else {
|
||||
crate::dirs::project_origin(&args).await?
|
||||
};
|
||||
|
||||
args.paths = take(&mut args.recursive_paths)
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
{
|
||||
if path.is_absolute() {
|
||||
Ok(path)
|
||||
} else {
|
||||
canonicalize(project_origin.join(path)).into_diagnostic()
|
||||
}
|
||||
}
|
||||
.map(WatchedPath::non_recursive)
|
||||
})
|
||||
.chain(take(&mut args.non_recursive_paths).into_iter().map(|path| {
|
||||
{
|
||||
if path.is_absolute() {
|
||||
Ok(path)
|
||||
} else {
|
||||
canonicalize(project_origin.join(path)).into_diagnostic()
|
||||
}
|
||||
}
|
||||
.map(WatchedPath::non_recursive)
|
||||
}))
|
||||
.collect::<Result<BTreeSet<_>>>()?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if args.paths.len() == 1
|
||||
&& args
|
||||
.paths
|
||||
.first()
|
||||
.map_or(false, |p| p.as_ref() == Path::new("/dev/null"))
|
||||
{
|
||||
debug!("only path is /dev/null, not watching anything");
|
||||
args.paths = Vec::new();
|
||||
} else if args.paths.is_empty() {
|
||||
debug!("no paths, using current directory");
|
||||
args.paths.push(args.workdir.clone().unwrap().into());
|
||||
}
|
||||
|
||||
debug!(paths=?args.paths, "resolved all watched paths");
|
||||
|
||||
for (n, prog) in args.filter_programs.iter_mut().enumerate() {
|
||||
if let Some(progpath) = prog.strip_prefix('@') {
|
||||
trace!(?n, path=?progpath, "reading filter program from file");
|
||||
@ -1233,7 +1309,7 @@ pub async fn get_args() -> Result<Args> {
|
||||
}
|
||||
}
|
||||
|
||||
args.filter_programs_parsed = std::mem::take(&mut args.filter_programs)
|
||||
args.filter_programs_parsed = take(&mut args.filter_programs)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(parse_filter_program)
|
||||
|
@ -1,11 +1,10 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
env::{current_dir, var},
|
||||
env::var,
|
||||
ffi::{OsStr, OsString},
|
||||
fs::File,
|
||||
io::{IsTerminal, Write},
|
||||
path::Path,
|
||||
process::Stdio,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||
@ -68,19 +67,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
|
||||
eprintln!("[[Error (not fatal)]]\n{}", Report::new(err.error));
|
||||
});
|
||||
|
||||
config.pathset(if args.paths.is_empty() {
|
||||
vec![current_dir().into_diagnostic()?]
|
||||
} else if args.paths.len() == 1
|
||||
&& args
|
||||
.paths
|
||||
.first()
|
||||
.map_or(false, |p| p == Path::new("/dev/null"))
|
||||
{
|
||||
// special case: /dev/null means "don't start the fs event source"
|
||||
Vec::new()
|
||||
} else {
|
||||
args.paths.clone()
|
||||
});
|
||||
config.pathset(args.paths.clone());
|
||||
|
||||
config.throttle(args.debounce.0);
|
||||
config.keyboard_events(args.stdin_quit);
|
||||
|
@ -1,7 +1,5 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
@ -14,16 +12,7 @@ use watchexec::paths::common_prefix;
|
||||
|
||||
use crate::args::Args;
|
||||
|
||||
type ProjectOriginPath = PathBuf;
|
||||
type WorkDirPath = PathBuf;
|
||||
|
||||
/// Extract relevant directories (in particular the project origin and work directory)
|
||||
/// given the command line arguments that were provided
|
||||
pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
|
||||
let curdir = env::current_dir().into_diagnostic()?;
|
||||
let curdir = canonicalize(curdir).await.into_diagnostic()?;
|
||||
debug!(?curdir, "current directory");
|
||||
|
||||
pub async fn project_origin(args: &Args) -> Result<PathBuf> {
|
||||
let project_origin = if let Some(origin) = &args.project_origin {
|
||||
debug!(?origin, "project origin override");
|
||||
canonicalize(origin).await.into_diagnostic()?
|
||||
@ -34,27 +23,19 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
|
||||
};
|
||||
debug!(?homedir, "home directory");
|
||||
|
||||
let mut paths = HashSet::new();
|
||||
for path in &args.paths {
|
||||
paths.insert(canonicalize(path).await.into_diagnostic()?);
|
||||
}
|
||||
|
||||
let homedir_requested = homedir.as_ref().map_or(false, |home| paths.contains(home));
|
||||
let homedir_requested = homedir.as_ref().map_or(false, |home| {
|
||||
args.paths
|
||||
.binary_search_by_key(home, |w| PathBuf::from(w.clone()))
|
||||
.is_ok()
|
||||
});
|
||||
debug!(
|
||||
?homedir_requested,
|
||||
"resolved whether the homedir is explicitly requested"
|
||||
);
|
||||
|
||||
if paths.is_empty() {
|
||||
debug!("no paths, using current directory");
|
||||
paths.insert(curdir.clone());
|
||||
}
|
||||
|
||||
debug!(?paths, "resolved all watched paths");
|
||||
|
||||
let mut origins = HashSet::new();
|
||||
for path in paths {
|
||||
origins.extend(project_origins::origins(&path).await);
|
||||
for path in &args.paths {
|
||||
origins.extend(project_origins::origins(path).await);
|
||||
}
|
||||
|
||||
match (homedir, homedir_requested) {
|
||||
@ -67,7 +48,7 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
|
||||
|
||||
if origins.is_empty() {
|
||||
debug!("no origins, using current directory");
|
||||
origins.insert(curdir.clone());
|
||||
origins.insert(args.workdir.clone().unwrap());
|
||||
}
|
||||
|
||||
debug!(?origins, "resolved all project origins");
|
||||
@ -82,10 +63,7 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
|
||||
};
|
||||
info!(?project_origin, "resolved common/project origin");
|
||||
|
||||
let workdir = curdir;
|
||||
info!(?workdir, "resolved working directory");
|
||||
|
||||
Ok((project_origin, workdir))
|
||||
Ok(project_origin)
|
||||
}
|
||||
|
||||
pub async fn vcs_types(origin: &Path) -> Vec<ProjectType> {
|
||||
@ -98,37 +76,30 @@ pub async fn vcs_types(origin: &Path) -> Vec<ProjectType> {
|
||||
vcs_types
|
||||
}
|
||||
|
||||
pub async fn ignores(
|
||||
args: &Args,
|
||||
vcs_types: &[ProjectType],
|
||||
origin: &Path,
|
||||
) -> Result<Vec<IgnoreFile>> {
|
||||
fn higher_make_absolute_if_needed<'a>(
|
||||
origin: &'a Path,
|
||||
) -> impl 'a + Fn(&'a PathBuf) -> Cow<'a, Path> {
|
||||
|path| {
|
||||
if path.is_absolute() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(origin.join(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ignores(args: &Args, vcs_types: &[ProjectType]) -> Result<Vec<IgnoreFile>> {
|
||||
let origin = args.project_origin.clone().unwrap();
|
||||
let mut skip_git_global_excludes = false;
|
||||
|
||||
let mut ignores = if args.no_project_ignore {
|
||||
Vec::new()
|
||||
} else {
|
||||
let make_absolute_if_needed = higher_make_absolute_if_needed(origin);
|
||||
let include_paths = args.paths.iter().map(&make_absolute_if_needed);
|
||||
let ignore_files = args.ignore_files.iter().map(&make_absolute_if_needed);
|
||||
let ignore_files = args.ignore_files.iter().map(|path| {
|
||||
if path.is_absolute() {
|
||||
path.into()
|
||||
} else {
|
||||
origin.join(path)
|
||||
}
|
||||
});
|
||||
|
||||
let (mut ignores, errors) = ignore_files::from_origin(
|
||||
IgnoreFilesFromOriginArgs::new_unchecked(origin, include_paths, ignore_files)
|
||||
.canonicalise()
|
||||
.await
|
||||
.into_diagnostic()?,
|
||||
IgnoreFilesFromOriginArgs::new_unchecked(
|
||||
&origin,
|
||||
args.paths.iter().map(PathBuf::from),
|
||||
ignore_files,
|
||||
)
|
||||
.canonicalise()
|
||||
.await
|
||||
.into_diagnostic()?,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -221,7 +192,7 @@ pub async fn ignores(
|
||||
.filter(|ig| {
|
||||
!ig.applies_in
|
||||
.as_ref()
|
||||
.map_or(false, |p| p.starts_with(origin))
|
||||
.map_or(false, |p| p.starts_with(&origin))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
debug!(
|
@ -16,7 +16,6 @@ use watchexec_filterer_globset::GlobsetFilterer;
|
||||
|
||||
use crate::args::{Args, FsEvent};
|
||||
|
||||
mod dirs;
|
||||
pub(crate) mod parse;
|
||||
mod proglib;
|
||||
mod progs;
|
||||
@ -71,13 +70,14 @@ impl Filterer for WatchexecFilterer {
|
||||
impl WatchexecFilterer {
|
||||
/// Create a new filterer from the given arguments
|
||||
pub async fn new(args: &Args) -> Result<Arc<Self>> {
|
||||
let (project_origin, workdir) = dirs::dirs(args).await?;
|
||||
let project_origin = args.project_origin.clone().unwrap();
|
||||
let workdir = args.workdir.clone().unwrap();
|
||||
|
||||
let ignore_files = if args.no_discover_ignore {
|
||||
Vec::new()
|
||||
} else {
|
||||
let vcs_types = dirs::vcs_types(&project_origin).await;
|
||||
dirs::ignores(args, &vcs_types, &project_origin).await?
|
||||
let vcs_types = crate::dirs::vcs_types(&project_origin).await;
|
||||
crate::dirs::ignores(args, &vcs_types).await?
|
||||
};
|
||||
|
||||
let mut ignores = Vec::new();
|
||||
|
@ -18,6 +18,7 @@ use crate::filterer::WatchexecFilterer;
|
||||
|
||||
pub mod args;
|
||||
mod config;
|
||||
mod dirs;
|
||||
mod emits;
|
||||
mod filterer;
|
||||
mod state;
|
||||
|
Loading…
Reference in New Issue
Block a user