diff --git a/.clippy-lints b/.clippy-lints new file mode 100644 index 0000000..2ef95a8 --- /dev/null +++ b/.clippy-lints @@ -0,0 +1,12 @@ +-D clippy::all +-W clippy::nursery +-W clippy::pedantic +-A clippy::module-name-repetitions +-A clippy::similar-names +-A clippy::cognitive-complexity +-A clippy::too-many-lines +-A clippy::missing-errors-doc +-A clippy::missing-panics-doc +-A clippy::default-trait-access +-A clippy::enum-glob-use +-A clippy::option-if-let-else diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 5086426..e09e244 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -13,9 +13,14 @@ env: CARGO_TERM_COLOR: always CARGO_UNSTABLE_SPARSE_REGISTRY: "true" +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + jobs: - check: + clippy: strategy: + fail-fast: false matrix: platform: - ubuntu @@ -29,9 +34,8 @@ jobs: - uses: actions/checkout@v3 - name: Configure toolchain run: | - rustup toolchain install --profile minimal --no-self-update nightly - rustup default nightly - rustup component add clippy + rustup toolchain install stable --profile minimal --no-self-update --component clippy + rustup default stable # https://github.com/actions/cache/issues/752 - if: ${{ runner.os == 'Windows' }} @@ -54,5 +58,5 @@ jobs: ${{ runner.os }}-cargo- - name: Clippy - run: cargo clippy -- -D clippy::all - + run: cargo clippy -- $(cat .clippy-lints | tr -d '\r' | xargs) + shell: bash diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 173f8ca..01afac6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,10 @@ env: CARGO_TERM_COLOR: always CARGO_UNSTABLE_SPARSE_REGISTRY: "true" +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + jobs: test: strategy: @@ -23,7 +27,7 @@ jobs: - windows toolchain: - stable - - 1.60.0 + - 1.61.0 name: Test ${{ matrix.platform }} with Rust ${{ matrix.toolchain }} runs-on: "${{ matrix.platform }}-latest" @@ -32,7 +36,7 @@ jobs: - uses: actions/checkout@v3 - name: Configure toolchain run: | - rustup toolchain install --profile minimal --no-self-update ${{ matrix.toolchain }} + rustup toolchain install ${{ matrix.toolchain }} --profile minimal --no-self-update rustup default ${{ matrix.toolchain }} # https://github.com/actions/cache/issues/752 @@ -43,20 +47,60 @@ jobs: echo "Adding GNU tar to PATH" echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" - - name: Configure caching + - name: Cargo caching uses: actions/cache@v3 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - target/ key: ${{ runner.os }}-cargo-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-${{ matrix.toolchain }}- ${{ runner.os }}-cargo- + - name: Compilation caching + uses: actions/cache@v3 + with: + path: target/ + key: ${{ runner.os }}-target-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} + - name: Run test suite run: cargo test - name: Check that CLI runs run: cargo run -p watchexec-cli -- -1 echo + + cross-checks: + name: Checks only against select targets + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Configure toolchain + run: | + rustup toolchain install --profile minimal --no-self-update stable + rustup default stable + + sudo apt-get install -y musl-tools + rustup target add x86_64-unknown-linux-musl + + - name: Install cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Cargo caching + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-stable-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-stable- + ${{ runner.os }}-cargo- + + - run: cargo check --target x86_64-unknown-linux-musl + - run: cross check --target x86_64-unknown-freebsd + - run: cross check --target x86_64-unknown-netbsd diff --git a/Cargo.lock b/Cargo.lock index d204205..39bf82b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -542,12 +542,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dunce" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" - [[package]] name = "either" version = "1.8.0" @@ -642,9 +636,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -657,9 +651,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -667,15 +661,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -684,9 +678,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" @@ -705,9 +699,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -716,21 +710,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -1139,7 +1133,6 @@ dependencies = [ name = "ignore-files" version = "1.0.1" dependencies = [ - "dunce", "futures", "git-config", "ignore", @@ -1461,6 +1454,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf22e319b2e3cb517350572e3b70c6822e0a520abfb5c78f690e829a73e8d9f2" + [[package]] name = "notify" version = "5.0.0" @@ -1736,7 +1735,6 @@ dependencies = [ name = "project-origins" version = "1.1.1" dependencies = [ - "dunce", "futures", "miette", "tokio", @@ -2658,11 +2656,10 @@ dependencies = [ "atomic-take", "clearscreen", "command-group", - "dunce", "futures", "ignore-files", - "libc", "miette", + "normalize-path", "notify", "once_cell", "project-origins", @@ -2679,7 +2676,6 @@ dependencies = [ "clap", "console-subscriber", "dirs 4.0.0", - "dunce", "embed-resource", "futures", "ignore-files", @@ -2699,7 +2695,6 @@ dependencies = [ name = "watchexec-filterer-globset" version = "1.0.1" dependencies = [ - "dunce", "ignore", "ignore-files", "project-origins", @@ -2714,7 +2709,6 @@ dependencies = [ name = "watchexec-filterer-ignore" version = "1.0.0" dependencies = [ - "dunce", "ignore", "ignore-files", "project-origins", @@ -2728,7 +2722,7 @@ dependencies = [ name = "watchexec-filterer-tagged" version = "0.1.1" dependencies = [ - "dunce", + "futures", "globset", "ignore", "ignore-files", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 40ded60..665082f 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -22,7 +22,6 @@ path = "src/main.rs" [dependencies] console-subscriber = { version = "0.1.0", optional = true } dirs = "4.0.0" -dunce = "1.0.2" futures = "0.3.17" miette = { version = "5.3.0", features = ["fancy"] } notify-rust = "4.5.2" diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index ab40d5a..90988e2 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -275,7 +275,7 @@ pub fn get_args(tagged_filterer: bool) -> Result { let arg_file = BufReader::new( File::open(arg_path) .into_diagnostic() - .wrap_err_with(|| format!("Failed to open argument file {:?}", arg_path))?, + .wrap_err_with(|| format!("Failed to open argument file {arg_path:?}"))?, ); let mut more_args: Vec = arg_file diff --git a/crates/cli/src/config/init.rs b/crates/cli/src/config/init.rs index aa05b36..e1659e4 100644 --- a/crates/cli/src/config/init.rs +++ b/crates/cli/src/config/init.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use clap::ArgMatches; -use miette::{Report, Result}; +use miette::Report; use tracing::error; use watchexec::{ config::InitConfig, @@ -10,7 +10,7 @@ use watchexec::{ ErrorHook, }; -pub fn init(_args: &ArgMatches) -> Result { +pub fn init(_args: &ArgMatches) -> InitConfig { let mut config = InitConfig::default(); config.on_error(SyncFnHandler::from( |err: ErrorHook| -> std::result::Result<(), Infallible> { @@ -47,5 +47,5 @@ pub fn init(_args: &ArgMatches) -> Result { }, )); - Ok(config) + config } diff --git a/crates/cli/src/config/runtime.rs b/crates/cli/src/config/runtime.rs index 65833c1..70c33f4 100644 --- a/crates/cli/src/config/runtime.rs +++ b/crates/cli/src/config/runtime.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, convert::Infallible, env::current_dir, ffi::OsString, path::Path, - str::FromStr, time::Duration, + str::FromStr, string::ToString, time::Duration, }; use clap::ArgMatches; @@ -12,7 +12,7 @@ use watchexec::{ command::{Command, Shell}, config::RuntimeConfig, error::RuntimeError, - event::{ProcessEnd, Tag}, + event::{Event, ProcessEnd, Tag}, fs::Watcher, handler::SyncFnHandler, keyboard::Keyboard, @@ -52,28 +52,23 @@ pub fn runtime(args: &ArgMatches) -> Result { let clear = args.is_present("clear"); let notif = args.is_present("notif"); - let mut on_busy = args - .value_of("on-busy-update") - .unwrap_or("queue") - .to_owned(); - - if args.is_present("restart") { - on_busy = "restart".into(); + let on_busy = if args.is_present("restart") { + "restart" + } else if args.is_present("watch-when-idle") { + "do-nothing" + } else { + args.value_of("on-busy-update").unwrap_or("queue") } + .to_owned(); - if args.is_present("watch-when-idle") { - on_busy = "do-nothing".into(); - } - - let mut signal = args - .value_of("signal") - .map(SubSignal::from_str) - .transpose() - .into_diagnostic()?; - - if args.is_present("kill") { - signal = Some(SubSignal::ForceStop); - } + let signal = if args.is_present("kill") { + Some(SubSignal::ForceStop) + } else { + args.value_of("signal") + .map(SubSignal::from_str) + .transpose() + .into_diagnostic()? + }; let print_events = args.is_present("print-events"); let once = args.is_present("once"); @@ -89,7 +84,7 @@ pub fn runtime(args: &ArgMatches) -> Result { if print_events { for (n, event) in action.events.iter().enumerate() { - eprintln!("[EVENT {}] {}", n, event); + eprintln!("[EVENT {n}] {event}"); } } @@ -105,13 +100,8 @@ pub fn runtime(args: &ArgMatches) -> Result { return fut; } - let signals: Vec = action.events.iter().flat_map(|e| e.signals()).collect(); - let has_paths = action - .events - .iter() - .flat_map(|e| e.paths()) - .next() - .is_some(); + let signals: Vec = action.events.iter().flat_map(Event::signals).collect(); + let has_paths = action.events.iter().flat_map(Event::paths).next().is_some(); if signals.contains(&MainSignal::Terminate) { action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); @@ -144,28 +134,28 @@ pub fn runtime(args: &ArgMatches) -> Result { return fut; } - let completion = action.events.iter().flat_map(|e| e.completions()).next(); + let completion = action.events.iter().flat_map(Event::completions).next(); if let Some(status) = completion { let (msg, printit) = match status { Some(ProcessEnd::ExitError(code)) => { - (format!("Command exited with {}", code), true) + (format!("Command exited with {code}"), true) } Some(ProcessEnd::ExitSignal(sig)) => { - (format!("Command killed by {:?}", sig), true) + (format!("Command killed by {sig:?}"), true) } Some(ProcessEnd::ExitStop(sig)) => { - (format!("Command stopped by {:?}", sig), true) + (format!("Command stopped by {sig:?}"), true) } Some(ProcessEnd::Continued) => ("Command continued".to_string(), true), Some(ProcessEnd::Exception(ex)) => { - (format!("Command ended by exception {:#x}", ex), true) + (format!("Command ended by exception {ex:#x}"), true) } Some(ProcessEnd::Success) => ("Command was successful".to_string(), false), None => ("Command completed".to_string(), false), }; if printit { - eprintln!("[[{}]]", msg); + eprintln!("[[{msg}]]"); } if notif { @@ -173,10 +163,12 @@ pub fn runtime(args: &ArgMatches) -> Result { .summary("Watchexec: command ended") .body(&msg) .show() - .map(drop) - .unwrap_or_else(|err| { - eprintln!("[[Failed to send desktop notification: {}]]", err); - }); + .map_or_else( + |err| { + eprintln!("[[Failed to send desktop notification: {err}]]"); + }, + drop, + ); } action.outcome(Outcome::DoNothing); @@ -198,7 +190,6 @@ pub fn runtime(args: &ArgMatches) -> Result { let when_idle = start.clone(); let when_running = match on_busy.as_str() { - "do-nothing" => Outcome::DoNothing, "restart" => Outcome::both( if let Some(sig) = signal { Outcome::both( @@ -212,6 +203,7 @@ pub fn runtime(args: &ArgMatches) -> Result { ), "signal" => Outcome::Signal(signal.unwrap_or(SubSignal::Terminate)), "queue" => Outcome::wait(start), + // "do-nothing" => Outcome::DoNothing, _ => Outcome::DoNothing, }; @@ -250,7 +242,7 @@ pub fn runtime(args: &ArgMatches) -> Result { envs.extend( summarise_events_to_env(prespawn.events.iter()) .into_iter() - .map(|(k, v)| (format!("WATCHEXEC_{}_PATH", k), v)), + .map(|(k, v)| (format!("WATCHEXEC_{k}_PATH"), v)), ); } @@ -276,10 +268,12 @@ pub fn runtime(args: &ArgMatches) -> Result { .summary("Watchexec: change detected") .body(&format!("Running {}", postspawn.command)) .show() - .map(drop) - .unwrap_or_else(|err| { - eprintln!("[[Failed to send desktop notification: {}]]", err); - }); + .map_or_else( + |err| { + eprintln!("[[Failed to send desktop notification: {err}]]"); + }, + drop, + ); } Ok::<(), Infallible>(()) @@ -292,7 +286,7 @@ fn interpret_command_args(args: &ArgMatches) -> Result { let mut cmd = args .values_of("command") .expect("(clap) Bug: command is not present") - .map(|s| s.to_string()) + .map(ToString::to_string) .collect::>(); Ok(if args.is_present("no-shell") { @@ -321,8 +315,8 @@ fn interpret_command_args(args: &ArgMatches) -> Result { let (shprog, shopts) = sh.split_first().unwrap(); ( - Shell::Unix(shprog.to_string()), - shopts.iter().map(|s| s.to_string()).collect(), + Shell::Unix((*shprog).to_string()), + shopts.iter().map(|s| (*s).to_string()).collect(), ) } } else { diff --git a/crates/cli/src/filterer/common.rs b/crates/cli/src/filterer/common.rs index 47c7d4c..e1ef0b4 100644 --- a/crates/cli/src/filterer/common.rs +++ b/crates/cli/src/filterer/common.rs @@ -5,32 +5,31 @@ use std::{ }; use clap::ArgMatches; -use dunce::canonicalize; use ignore_files::IgnoreFile; use miette::{miette, IntoDiagnostic, Result}; use project_origins::ProjectType; +use tokio::fs::canonicalize; use tracing::{debug, info, warn}; use watchexec::paths::common_prefix; pub async fn dirs(args: &ArgMatches) -> Result<(PathBuf, PathBuf)> { - let curdir = env::current_dir() - .and_then(canonicalize) - .into_diagnostic()?; + let curdir = env::current_dir().into_diagnostic()?; + let curdir = canonicalize(curdir).await.into_diagnostic()?; debug!(?curdir, "current directory"); let project_origin = if let Some(origin) = args.value_of_os("project-origin") { debug!(?origin, "project origin override"); - canonicalize(origin).into_diagnostic()? + canonicalize(origin).await.into_diagnostic()? } else { - let homedir = dirs::home_dir() - .map(canonicalize) - .transpose() - .into_diagnostic()?; + let homedir = match dirs::home_dir() { + None => None, + Some(dir) => Some(canonicalize(dir).await.into_diagnostic()?), + }; debug!(?homedir, "home directory"); let mut paths = HashSet::new(); for path in args.values_of_os("paths").unwrap_or_default() { - paths.insert(canonicalize(path).into_diagnostic()?); + paths.insert(canonicalize(path).await.into_diagnostic()?); } let homedir_requested = homedir.as_ref().map_or(false, |home| paths.contains(home)); @@ -71,6 +70,7 @@ pub async fn dirs(args: &ArgMatches) -> Result<(PathBuf, PathBuf)> { common_prefix(&origins) .ok_or_else(|| miette!("no common prefix, but this should never fail"))?, ) + .await .into_diagnostic()? }; info!(?project_origin, "resolved common/project origin"); diff --git a/crates/cli/src/filterer/globset.rs b/crates/cli/src/filterer/globset.rs index 9d58afb..ee77de4 100644 --- a/crates/cli/src/filterer/globset.rs +++ b/crates/cli/src/filterer/globset.rs @@ -26,23 +26,23 @@ pub async fn globset(args: &ArgMatches) -> Result> { if !args.is_present("no-default-ignore") { ignores.extend([ - (format!("**{s}.DS_Store", s = MAIN_SEPARATOR), None), + (format!("**{MAIN_SEPARATOR}.DS_Store"), None), (String::from("*.py[co]"), None), (String::from("#*#"), None), (String::from(".#*"), None), (String::from(".*.kate-swp"), None), (String::from(".*.sw?"), None), (String::from(".*.sw?x"), None), - (format!("**{s}.bzr{s}**", s = MAIN_SEPARATOR), None), - (format!("**{s}_darcs{s}**", s = MAIN_SEPARATOR), None), + (format!("**{MAIN_SEPARATOR}.bzr{MAIN_SEPARATOR}**"), None), + (format!("**{MAIN_SEPARATOR}_darcs{MAIN_SEPARATOR}**"), None), ( - format!("**{s}.fossil-settings{s}**", s = MAIN_SEPARATOR), + format!("**{MAIN_SEPARATOR}.fossil-settings{MAIN_SEPARATOR}**"), None, ), - (format!("**{s}.git{s}**", s = MAIN_SEPARATOR), None), - (format!("**{s}.hg{s}**", s = MAIN_SEPARATOR), None), - (format!("**{s}.pijul{s}**", s = MAIN_SEPARATOR), None), - (format!("**{s}.svn{s}**", s = MAIN_SEPARATOR), None), + (format!("**{MAIN_SEPARATOR}.git{MAIN_SEPARATOR}**"), None), + (format!("**{MAIN_SEPARATOR}.hg{MAIN_SEPARATOR}**"), None), + (format!("**{MAIN_SEPARATOR}.pijul{MAIN_SEPARATOR}**"), None), + (format!("**{MAIN_SEPARATOR}.svn{MAIN_SEPARATOR}**"), None), ]); } diff --git a/crates/cli/src/filterer/tagged.rs b/crates/cli/src/filterer/tagged.rs index 293b982..dc726ff 100644 --- a/crates/cli/src/filterer/tagged.rs +++ b/crates/cli/src/filterer/tagged.rs @@ -14,7 +14,7 @@ pub async fn tagged(args: &ArgMatches) -> Result> { let vcs_types = super::common::vcs_types(&project_origin).await; let ignores = super::common::ignores(args, &vcs_types, &project_origin).await; - let filterer = TaggedFilterer::new(project_origin, workdir.clone())?; + let filterer = TaggedFilterer::new(project_origin, workdir.clone()).await?; for ignore in &ignores { filterer.add_ignore_file(ignore).await?; @@ -25,7 +25,7 @@ pub async fn tagged(args: &ArgMatches) -> Result> { let file = FilterFile(IgnoreFile { applies_in: None, applies_to: None, - path: dunce::canonicalize(path).into_diagnostic()?, + path: tokio::fs::canonicalize(path).await.into_diagnostic()?, }); filter_files.push(file); } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 6131efa..ebe48ca 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,4 +1,5 @@ #![deny(rust_2018_idioms)] +#![allow(clippy::missing_const_for_fn, clippy::future_not_send)] use std::{env::var, fs::File, sync::Mutex}; @@ -82,7 +83,7 @@ pub async fn run() -> Result<()> { info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI"); debug!(?args, "arguments"); - let init = config::init(&args)?; + let init = config::init(&args); let mut runtime = config::runtime(&args)?; runtime.filterer(if tagged_filterer { eprintln!("!!! EXPERIMENTAL: using tagged filterer !!!"); diff --git a/crates/filterer/globset/Cargo.toml b/crates/filterer/globset/Cargo.toml index ad000a6..3e6db52 100644 --- a/crates/filterer/globset/Cargo.toml +++ b/crates/filterer/globset/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://watchexec.github.io" repository = "https://github.com/watchexec/watchexec" readme = "README.md" -rust-version = "1.60.0" +rust-version = "1.61.0" edition = "2021" [dependencies] @@ -32,7 +32,6 @@ version = "1.0.0" path = "../ignore" [dev-dependencies] -dunce = "1.0.2" tracing-subscriber = "0.3.6" [dev-dependencies.project-origins] diff --git a/crates/filterer/globset/README.md b/crates/filterer/globset/README.md index 7e86323..c15c019 100644 --- a/crates/filterer/globset/README.md +++ b/crates/filterer/globset/README.md @@ -9,7 +9,7 @@ _The default filterer implementation for Watchexec._ - **[API documentation][docs]**. - Licensed under [Apache 2.0][license]. -- Minimum Supported Rust Version: 1.60.0 (incurs a minor semver bump). +- Minimum Supported Rust Version: 1.61.0 (incurs a minor semver bump). - Status: maintained. [docs]: https://docs.rs/watchexec-filterer-globset diff --git a/crates/filterer/globset/src/lib.rs b/crates/filterer/globset/src/lib.rs index 58d7e3e..30b7ec9 100644 --- a/crates/filterer/globset/src/lib.rs +++ b/crates/filterer/globset/src/lib.rs @@ -48,6 +48,7 @@ impl GlobsetFilterer { /// The extensions list is used to filter files by extension. /// /// Non-path events are always passed. + #[allow(clippy::future_not_send)] pub async fn new( origin: impl AsRef, filters: impl IntoIterator)>, @@ -133,9 +134,7 @@ impl Filterer for GlobsetFilterer { } else { Ok(paths.any(|(path, file_type)| { let _span = trace_span!("path", ?path).entered(); - let is_dir = file_type - .map(|t| matches!(t, FileType::Dir)) - .unwrap_or(false); + let is_dir = file_type.map_or(false, |t| matches!(t, FileType::Dir)); if self.ignores.matched(path, is_dir).is_ignore() { trace!("ignored by globset ignore"); diff --git a/crates/filterer/globset/tests/filtering.rs b/crates/filterer/globset/tests/filtering.rs index d59eed1..f59983a 100644 --- a/crates/filterer/globset/tests/filtering.rs +++ b/crates/filterer/globset/tests/filtering.rs @@ -380,7 +380,7 @@ async fn multipath_allow_on_any_one_pass() { }; let filterer = filt(&[], &[], &["py"]).await; - let origin = dunce::canonicalize(".").unwrap(); + let origin = tokio::fs::canonicalize(".").await.unwrap(); let event = Event { tags: vec![ diff --git a/crates/filterer/globset/tests/helpers/mod.rs b/crates/filterer/globset/tests/helpers/mod.rs index 2cb6527..166f7b8 100644 --- a/crates/filterer/globset/tests/helpers/mod.rs +++ b/crates/filterer/globset/tests/helpers/mod.rs @@ -35,7 +35,7 @@ pub trait PathHarness: Filterer { } fn path_pass(&self, path: &str, file_type: Option, pass: bool) { - let origin = dunce::canonicalize(".").unwrap(); + let origin = std::fs::canonicalize(".").unwrap(); let full_path = if let Some(suf) = path.strip_prefix("/test/") { origin.join(suf) } else if Path::new(path).has_root() { @@ -110,12 +110,12 @@ pub async fn globset_filt( ignores: &[&str], extensions: &[&str], ) -> GlobsetFilterer { - let origin = dunce::canonicalize(".").unwrap(); + let origin = tokio::fs::canonicalize(".").await.unwrap(); tracing_init(); GlobsetFilterer::new( origin, - filters.iter().map(|s| (s.to_string(), None)), - ignores.iter().map(|s| (s.to_string(), None)), + filters.iter().map(|s| ((*s).to_string(), None)), + ignores.iter().map(|s| ((*s).to_string(), None)), vec![], extensions.iter().map(OsString::from), ) @@ -130,7 +130,7 @@ pub trait Applies { impl Applies for IgnoreFile { fn applies_in(mut self, origin: &str) -> Self { - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = std::fs::canonicalize(".").unwrap().join(origin); self.applies_in = Some(origin); self } diff --git a/crates/filterer/ignore/Cargo.toml b/crates/filterer/ignore/Cargo.toml index 00d8271..2bb6490 100644 --- a/crates/filterer/ignore/Cargo.toml +++ b/crates/filterer/ignore/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://watchexec.github.io" repository = "https://github.com/watchexec/watchexec" readme = "README.md" -rust-version = "1.60.0" +rust-version = "1.61.0" edition = "2021" [dependencies] @@ -28,7 +28,6 @@ version = "2.0.2" path = "../../lib" [dev-dependencies] -dunce = "1.0.2" tracing-subscriber = "0.3.6" [dev-dependencies.project-origins] diff --git a/crates/filterer/ignore/README.md b/crates/filterer/ignore/README.md index 49c2676..76c07e1 100644 --- a/crates/filterer/ignore/README.md +++ b/crates/filterer/ignore/README.md @@ -9,7 +9,7 @@ _(Sub)filterer implementation for ignore files._ - **[API documentation][docs]**. - Licensed under [Apache 2.0][license]. -- Minimum Supported Rust Version: 1.60.0 (incurs a minor semver bump). +- Minimum Supported Rust Version: 1.61.0 (incurs a minor semver bump). - Status: maintained. This is mostly a thin layer above the [ignore-files](../../ignore-files) crate, and is meant to be diff --git a/crates/filterer/ignore/src/lib.rs b/crates/filterer/ignore/src/lib.rs index 2fc6613..556ebed 100644 --- a/crates/filterer/ignore/src/lib.rs +++ b/crates/filterer/ignore/src/lib.rs @@ -36,9 +36,7 @@ impl Filterer for IgnoreFilterer { for (path, file_type) in event.paths() { let _span = trace_span!("checking_against_compiled", ?path, ?file_type).entered(); - let is_dir = file_type - .map(|t| matches!(t, FileType::Dir)) - .unwrap_or(false); + let is_dir = file_type.map_or(false, |t| matches!(t, FileType::Dir)); match self.0.match_path(path, is_dir) { Match::None => { diff --git a/crates/filterer/ignore/tests/filtering.rs b/crates/filterer/ignore/tests/filtering.rs index daeb94a..98ad33e 100644 --- a/crates/filterer/ignore/tests/filtering.rs +++ b/crates/filterer/ignore/tests/filtering.rs @@ -37,11 +37,11 @@ fn folders_suite(filterer: &IgnoreFilterer, name: &str) { filterer.dir_does_pass("apples/carrots/cauliflowers/oranges"); filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges"); - filterer.file_does_pass(&format!("raw-{}", name)); - filterer.dir_does_pass(&format!("raw-{}", name)); - filterer.file_does_pass(&format!("raw-{}/carrots/cauliflowers/oranges", name)); - filterer.file_does_pass(&format!("raw-{}/oranges/bananas", name)); - filterer.dir_does_pass(&format!("raw-{}/carrots/cauliflowers/oranges", name)); + filterer.file_does_pass(&format!("raw-{name}")); + filterer.dir_does_pass(&format!("raw-{name}")); + filterer.file_does_pass(&format!("raw-{name}/carrots/cauliflowers/oranges")); + filterer.file_does_pass(&format!("raw-{name}/oranges/bananas")); + filterer.dir_does_pass(&format!("raw-{name}/carrots/cauliflowers/oranges")); filterer.file_does_pass(&format!( "raw-{}/carrots/cauliflowers/artichokes/oranges", name @@ -51,11 +51,11 @@ fn folders_suite(filterer: &IgnoreFilterer, name: &str) { name )); - filterer.dir_doesnt_pass(&format!("{}/carrots/cauliflowers/oranges", name)); - filterer.dir_doesnt_pass(&format!("{}/carrots/cauliflowers/artichokes/oranges", name)); - filterer.file_doesnt_pass(&format!("{}/carrots/cauliflowers/oranges", name)); - filterer.file_doesnt_pass(&format!("{}/carrots/cauliflowers/artichokes/oranges", name)); - filterer.file_doesnt_pass(&format!("{}/oranges/bananas", name)); + filterer.dir_doesnt_pass(&format!("{name}/carrots/cauliflowers/oranges")); + filterer.dir_doesnt_pass(&format!("{name}/carrots/cauliflowers/artichokes/oranges")); + filterer.file_doesnt_pass(&format!("{name}/carrots/cauliflowers/oranges")); + filterer.file_doesnt_pass(&format!("{name}/carrots/cauliflowers/artichokes/oranges")); + filterer.file_doesnt_pass(&format!("{name}/oranges/bananas")); } #[tokio::test] diff --git a/crates/filterer/ignore/tests/helpers/mod.rs b/crates/filterer/ignore/tests/helpers/mod.rs index a9ef403..17c5581 100644 --- a/crates/filterer/ignore/tests/helpers/mod.rs +++ b/crates/filterer/ignore/tests/helpers/mod.rs @@ -34,7 +34,7 @@ pub trait PathHarness: Filterer { } fn path_pass(&self, path: &str, file_type: Option, pass: bool) { - let origin = dunce::canonicalize(".").unwrap(); + let origin = std::fs::canonicalize(".").unwrap(); let full_path = if let Some(suf) = path.strip_prefix("/test/") { origin.join(suf) } else if Path::new(path).has_root() { @@ -180,7 +180,7 @@ fn tracing_init() { pub async fn ignore_filt(origin: &str, ignore_files: &[IgnoreFile]) -> IgnoreFilterer { tracing_init(); - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = tokio::fs::canonicalize(".").await.unwrap().join(origin); IgnoreFilterer( IgnoreFilter::new(origin, ignore_files) .await @@ -189,7 +189,7 @@ pub async fn ignore_filt(origin: &str, ignore_files: &[IgnoreFile]) -> IgnoreFil } pub fn ig_file(name: &str) -> IgnoreFile { - let path = dunce::canonicalize(".") + let path = std::fs::canonicalize(".") .unwrap() .join("tests") .join("ignores") @@ -208,7 +208,7 @@ pub trait Applies { impl Applies for IgnoreFile { fn applies_in(mut self, origin: &str) -> Self { - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = std::fs::canonicalize(".").unwrap().join(origin); self.applies_in = Some(origin); self } diff --git a/crates/filterer/tagged/Cargo.toml b/crates/filterer/tagged/Cargo.toml index d0a58e1..1228181 100644 --- a/crates/filterer/tagged/Cargo.toml +++ b/crates/filterer/tagged/Cargo.toml @@ -12,18 +12,18 @@ homepage = "https://watchexec.github.io" repository = "https://github.com/watchexec/watchexec" readme = "README.md" -rust-version = "1.60.0" +rust-version = "1.61.0" edition = "2021" [dependencies] -miette = "5.3.0" -thiserror = "1.0.26" -tracing = "0.1.26" -dunce = "1.0.2" +futures = "0.3.25" globset = "0.4.8" ignore = "0.4.18" +miette = "5.3.0" nom = "7.0.0" regex = "1.5.4" +thiserror = "1.0.26" +tracing = "0.1.26" unicase = "2.6.0" [dependencies.ignore-files] diff --git a/crates/filterer/tagged/README.md b/crates/filterer/tagged/README.md index 1067e34..02d3260 100644 --- a/crates/filterer/tagged/README.md +++ b/crates/filterer/tagged/README.md @@ -9,7 +9,7 @@ _Experimental filterer using tagged filters._ - **[API documentation][docs]**. - Licensed under [Apache 2.0][license]. -- Minimum Supported Rust Version: 1.60.0 (incurs a minor semver bump). +- Minimum Supported Rust Version: 1.61.0 (incurs a minor semver bump). - Status: maintained. [docs]: https://docs.rs/watchexec-filterer-tagged diff --git a/crates/filterer/tagged/src/error.rs b/crates/filterer/tagged/src/error.rs index 07bcc37..216cfcf 100644 --- a/crates/filterer/tagged/src/error.rs +++ b/crates/filterer/tagged/src/error.rs @@ -9,7 +9,7 @@ use watchexec_filterer_ignore::IgnoreFilterer; use crate::{Filter, Matcher}; -/// Errors emitted by the TaggedFilterer. +/// Errors emitted by the `TaggedFilterer`. #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] #[diagnostic(url(docsrs))] diff --git a/crates/filterer/tagged/src/filter.rs b/crates/filterer/tagged/src/filter.rs index 92f3812..398e79f 100644 --- a/crates/filterer/tagged/src/filter.rs +++ b/crates/filterer/tagged/src/filter.rs @@ -1,9 +1,9 @@ use std::collections::HashSet; use std::path::PathBuf; -use dunce::canonicalize; use globset::Glob; use regex::Regex; +use tokio::fs::canonicalize; use tracing::{trace, warn}; use unicase::UniCase; use watchexec::event::Tag; @@ -49,7 +49,7 @@ impl Filter { (Op::InSet, Pattern::Exact(pat)) => subject == pat, (Op::NotInSet, Pattern::Set(set)) => !set.contains(subject), (Op::NotInSet, Pattern::Exact(pat)) => subject != pat, - (op @ Op::Glob | op @ Op::NotGlob, Pattern::Glob(glob)) => { + (op @ (Op::Glob | Op::NotGlob), Pattern::Glob(glob)) => { // FIXME: someway that isn't this horrible match Glob::new(glob) { Ok(glob) => { @@ -86,6 +86,7 @@ impl Filter { /// /// The resulting filter matches on [`Path`][Matcher::Path], with the [`NotGlob`][Op::NotGlob] /// op, and a [`Glob`][Pattern::Glob] pattern. If it starts with a `!`, it is negated. + #[must_use] pub fn from_glob_ignore(in_path: Option, glob: &str) -> Self { let (glob, negate) = glob.strip_prefix('!').map_or((glob, false), |g| (g, true)); @@ -99,14 +100,16 @@ impl Filter { } /// Returns the filter with its `in_path` canonicalised. - pub fn canonicalised(mut self) -> Result { + pub async fn canonicalised(mut self) -> Result { if let Some(ctx) = self.in_path { self.in_path = Some( - canonicalize(&ctx).map_err(|err| TaggedFiltererError::IoError { - about: "canonicalise Filter in_path", - err, - })?, + canonicalize(&ctx) + .await + .map_err(|err| TaggedFiltererError::IoError { + about: "canonicalise Filter in_path", + err, + })?, ); trace!(canon=?ctx, "canonicalised in_path"); } @@ -186,13 +189,13 @@ impl Matcher { match tag { Tag::Path { file_type: None, .. - } => &[Matcher::Path], - Tag::Path { .. } => &[Matcher::Path, Matcher::FileType], - Tag::FileEventKind(_) => &[Matcher::FileEventKind], - Tag::Source(_) => &[Matcher::Source], - Tag::Process(_) => &[Matcher::Process], - Tag::Signal(_) => &[Matcher::Signal], - Tag::ProcessCompletion(_) => &[Matcher::ProcessCompletion], + } => &[Self::Path], + Tag::Path { .. } => &[Self::Path, Self::FileType], + Tag::FileEventKind(_) => &[Self::FileEventKind], + Tag::Source(_) => &[Self::Source], + Tag::Process(_) => &[Self::Process], + Tag::Signal(_) => &[Self::Signal], + Tag::ProcessCompletion(_) => &[Self::ProcessCompletion], _ => { warn!("unhandled tag: {:?}", tag); &[] diff --git a/crates/filterer/tagged/src/filterer.rs b/crates/filterer/tagged/src/filterer.rs index d5ddcb3..d83ff53 100644 --- a/crates/filterer/tagged/src/filterer.rs +++ b/crates/filterer/tagged/src/filterer.rs @@ -1,13 +1,14 @@ -use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; +use std::{collections::HashMap, convert::Into}; -use dunce::canonicalize; +use futures::{stream::FuturesOrdered, TryStreamExt}; use ignore::{ gitignore::{Gitignore, GitignoreBuilder}, Match, }; use ignore_files::{IgnoreFile, IgnoreFilter}; +use tokio::fs::canonicalize; use tracing::{debug, trace, trace_span}; use watchexec::{ error::RuntimeError, @@ -49,7 +50,7 @@ pub struct TaggedFilterer { impl Filterer for TaggedFilterer { fn check_event(&self, event: &Event, priority: Priority) -> Result { - self.check(event, priority).map_err(|e| e.into()) + self.check(event, priority).map_err(Into::into) } } @@ -77,9 +78,9 @@ impl TaggedFilterer { trace!(prev=%pri_match, now=%true, "negate filter passes, passing this priority"); pri_match = true; break; - } else { - trace!(prev=%pri_match, now=%pri_match, "negate filter fails, ignoring"); } + + trace!(prev=%pri_match, now=%pri_match, "negate filter fails, ignoring"); } else { trace!(prev=%pri_match, this=%applies, now=%(pri_match&applies), "filter applies to priority"); pri_match &= applies; @@ -246,9 +247,9 @@ impl TaggedFilterer { trace!(prev=%tag_match, now=%true, "negate filter passes, passing this matcher"); tag_match = true; break; - } else { - trace!(prev=%tag_match, now=%tag_match, "negate filter fails, ignoring"); } + + trace!(prev=%tag_match, now=%tag_match, "negate filter fails, ignoring"); } else { trace!(prev=%tag_match, this=%app, now=%(tag_match&app), "filter applies to this tag"); tag_match &= app; @@ -287,23 +288,24 @@ impl TaggedFilterer { /// So, if origin is `/path/to/project` and workdir is `/path/to/project/subtree`: /// - `path=foo.bar` is resolved to `/path/to/project/subtree/foo.bar` /// - `path=/foo.bar` is resolved to `/path/to/project/foo.bar` - pub fn new( - origin: impl Into, - workdir: impl Into, - ) -> Result, TaggedFiltererError> { - let origin = canonicalize(origin.into()).map_err(|err| TaggedFiltererError::IoError { - about: "canonicalise origin on new tagged filterer", - err, - })?; + pub async fn new(origin: PathBuf, workdir: PathBuf) -> Result, TaggedFiltererError> { + let origin = canonicalize(origin) + .await + .map_err(|err| TaggedFiltererError::IoError { + about: "canonicalise origin on new tagged filterer", + err, + })?; Ok(Arc::new(Self { filters: SwapLock::new(HashMap::new()), ignore_filterer: SwapLock::new(IgnoreFilterer(IgnoreFilter::empty(&origin))), glob_compiled: SwapLock::new(None), not_glob_compiled: SwapLock::new(None), - workdir: canonicalize(workdir.into()).map_err(|err| TaggedFiltererError::IoError { - about: "canonicalise workdir on new tagged filterer", - err, - })?, + workdir: canonicalize(workdir) + .await + .map_err(|err| TaggedFiltererError::IoError { + about: "canonicalise workdir on new tagged filterer", + err, + })?, origin, })) } @@ -341,9 +343,9 @@ impl TaggedFilterer { if matches!(filter.op, Op::Glob | Op::NotGlob) { trace!("path glob match with match_tag is already handled"); return Ok(None); - } else { - filter.matches(resolved.to_string_lossy()) } + + filter.matches(resolved.to_string_lossy()) } ( Tag::Path { @@ -353,7 +355,7 @@ impl TaggedFilterer { Matcher::FileType, ) => filter.matches(ft.to_string()), (Tag::FileEventKind(kind), Matcher::FileEventKind) => { - filter.matches(format!("{:?}", kind)) + filter.matches(format!("{kind:?}")) } (Tag::Source(src), Matcher::Source) => filter.matches(src.to_string()), (Tag::Process(pid), Matcher::Process) => filter.matches(pid.to_string()), @@ -368,13 +370,13 @@ impl TaggedFilterer { }; Ok(filter.matches(text)? - || filter.matches(format!("SIG{}", text))? + || filter.matches(format!("SIG{text}"))? || filter.matches(int.to_string())?) } (Tag::ProcessCompletion(ope), Matcher::ProcessCompletion) => match ope { None => filter.matches("_"), Some(ProcessEnd::Success) => filter.matches("success"), - Some(ProcessEnd::ExitError(int)) => filter.matches(format!("error({})", int)), + Some(ProcessEnd::ExitError(int)) => filter.matches(format!("error({int})")), Some(ProcessEnd::ExitSignal(sig)) => { let (text, int) = match sig { SubSignal::Hangup | SubSignal::Custom(1) => ("HUP", 1), @@ -387,12 +389,12 @@ impl TaggedFilterer { SubSignal::Custom(n) => ("UNK", *n), }; - Ok(filter.matches(format!("signal({})", text))? - || filter.matches(format!("signal(SIG{})", text))? - || filter.matches(format!("signal({})", int))?) + Ok(filter.matches(format!("signal({text})"))? + || filter.matches(format!("signal(SIG{text})"))? + || filter.matches(format!("signal({int})"))?) } - Some(ProcessEnd::ExitStop(int)) => filter.matches(format!("stop({})", int)), - Some(ProcessEnd::Exception(int)) => filter.matches(format!("exception({:X})", int)), + Some(ProcessEnd::ExitStop(int)) => filter.matches(format!("stop({int})")), + Some(ProcessEnd::Exception(int)) => filter.matches(format!("exception({int:X})")), Some(ProcessEnd::Continued) => filter.matches("continued"), }, (_, _) => { @@ -418,20 +420,24 @@ impl TaggedFilterer { let mut recompile_globs = false; let mut recompile_not_globs = false; - let filters = filters - .iter() - .cloned() - .inspect(|f| match f.op { - Op::Glob => { - recompile_globs = true; - } - Op::NotGlob => { - recompile_not_globs = true; - } - _ => {} - }) - .map(Filter::canonicalised) - .collect::, _>>()?; + #[allow(clippy::from_iter_instead_of_collect)] + let filters = FuturesOrdered::from_iter( + filters + .iter() + .cloned() + .inspect(|f| match f.op { + Op::Glob => { + recompile_globs = true; + } + Op::NotGlob => { + recompile_not_globs = true; + } + _ => {} + }) + .map(Filter::canonicalised), + ) + .try_collect::>() + .await?; trace!(?filters, "canonicalised filters"); // TODO: use miette's related and issue canonicalisation errors for all of them @@ -441,22 +447,21 @@ impl TaggedFilterer { fs.entry(filter.on).or_default().push(filter); } }) - .await .map_err(|err| TaggedFiltererError::FilterChange { action: "add", err })?; trace!("inserted filters into swaplock"); if recompile_globs { - self.recompile_globs(Op::Glob).await?; + self.recompile_globs(Op::Glob)?; } if recompile_not_globs { - self.recompile_globs(Op::NotGlob).await?; + self.recompile_globs(Op::NotGlob)?; } Ok(()) } - async fn recompile_globs(&self, op_filter: Op) -> Result<(), TaggedFiltererError> { + fn recompile_globs(&self, op_filter: Op) -> Result<(), TaggedFiltererError> { trace!(?op_filter, "recompiling globs"); let target = match op_filter { Op::Glob => &self.glob_compiled, @@ -477,7 +482,6 @@ impl TaggedFilterer { trace!(?op_filter, "no filters, erasing compiled glob"); return target .replace(None) - .await .map_err(TaggedFiltererError::GlobsetChange); } }; @@ -502,7 +506,6 @@ impl TaggedFilterer { trace!(?op_filter, "swapping in new compiled glob"); target .replace(Some(compiled)) - .await .map_err(TaggedFiltererError::GlobsetChange) } @@ -516,7 +519,6 @@ impl TaggedFilterer { .map_err(TaggedFiltererError::Ignore)?; self.ignore_filterer .replace(new) - .await .map_err(TaggedFiltererError::IgnoreSwap)?; Ok(()) } @@ -524,18 +526,17 @@ impl TaggedFilterer { /// Clears all filters from the filterer. /// /// This also recompiles the glob matchers, so essentially it resets the entire filterer state. - pub async fn clear_filters(&self) -> Result<(), TaggedFiltererError> { + pub fn clear_filters(&self) -> Result<(), TaggedFiltererError> { debug!("removing all filters from filterer"); - self.filters - .replace(Default::default()) - .await - .map_err(|err| TaggedFiltererError::FilterChange { + self.filters.replace(Default::default()).map_err(|err| { + TaggedFiltererError::FilterChange { action: "clear all", err, - })?; + } + })?; - self.recompile_globs(Op::Glob).await?; - self.recompile_globs(Op::NotGlob).await?; + self.recompile_globs(Op::Glob)?; + self.recompile_globs(Op::NotGlob)?; Ok(()) } diff --git a/crates/filterer/tagged/src/parse.rs b/crates/filterer/tagged/src/parse.rs index cabc2d2..1f317dd 100644 --- a/crates/filterer/tagged/src/parse.rs +++ b/crates/filterer/tagged/src/parse.rs @@ -45,7 +45,7 @@ impl FromStr for Filter { "process" | "pid" => Ok(Matcher::Process), "signal" | "sig" => Ok(Matcher::Signal), "complete" | "exit" => Ok(Matcher::ProcessCompletion), - m => Err(format!("unknown matcher: {}", m)), + m => Err(format!("unknown matcher: {m}")), }, )(i) } @@ -73,7 +73,7 @@ impl FromStr for Filter { ":=" => Ok(Op::InSet), ":!" => Ok(Op::NotInSet), "=" => Ok(Op::Auto), - o => Err(format!("unknown op: `{}`", o)), + o => Err(format!("unknown op: `{o}`")), }, )(i) } diff --git a/crates/filterer/tagged/src/swaplock.rs b/crates/filterer/tagged/src/swaplock.rs index 8adac84..7f1d923 100644 --- a/crates/filterer/tagged/src/swaplock.rs +++ b/crates/filterer/tagged/src/swaplock.rs @@ -33,7 +33,7 @@ where /// /// This obtains a clone of the value, and then calls the closure with a mutable reference to /// it. Once the closure returns, the value is swapped in. - pub async fn change(&self, f: impl FnOnce(&mut T)) -> Result<(), SendError> { + pub fn change(&self, f: impl FnOnce(&mut T)) -> Result<(), SendError> { let mut new = { self.r.borrow().clone() }; f(&mut new); @@ -41,7 +41,7 @@ where } /// Replace the value with a new one. - pub async fn replace(&self, new: T) -> Result<(), SendError> { + pub fn replace(&self, new: T) -> Result<(), SendError> { self.s.send(new) } } diff --git a/crates/filterer/tagged/tests/filter_files.rs b/crates/filterer/tagged/tests/filter_files.rs index acd1d69..5515cdb 100644 --- a/crates/filterer/tagged/tests/filter_files.rs +++ b/crates/filterer/tagged/tests/filter_files.rs @@ -8,7 +8,7 @@ use helpers::tagged_ff::*; #[tokio::test] async fn empty_filter_passes_everything() { - let filterer = filt("", &[], &[file("empty.wef")]).await; + let filterer = filt("", &[], &[file("empty.wef").await]).await; filterer.file_does_pass("Cargo.toml"); filterer.file_does_pass("Cargo.json"); @@ -35,7 +35,7 @@ async fn empty_filter_passes_everything() { #[tokio::test] async fn folder() { - let filterer = filt("", &[], &[file("folder.wef")]).await; + let filterer = filt("", &[], &[file("folder.wef").await]).await; filterer.file_doesnt_pass("apples"); filterer.file_doesnt_pass("apples/oranges/bananas"); @@ -54,7 +54,7 @@ async fn folder() { #[tokio::test] async fn patterns() { - let filterer = filt("", &[], &[file("path-patterns.wef")]).await; + let filterer = filt("", &[], &[file("path-patterns.wef").await]).await; // Unmatched filterer.file_does_pass("FINAL-FINAL.docx"); @@ -94,7 +94,7 @@ async fn patterns() { #[tokio::test] async fn negate() { - let filterer = filt("", &[], &[file("negate.wef")]).await; + let filterer = filt("", &[], &[file("negate.wef").await]).await; filterer.file_doesnt_pass("yeah"); filterer.file_does_pass("nah"); @@ -103,7 +103,7 @@ async fn negate() { #[tokio::test] async fn ignores_and_filters() { - let filterer = filt("", &[file("globs").0], &[file("folder.wef")]).await; + let filterer = filt("", &[file("globs").await.0], &[file("folder.wef").await]).await; // ignored filterer.dir_doesnt_pass("test-helper"); diff --git a/crates/filterer/tagged/tests/helpers/mod.rs b/crates/filterer/tagged/tests/helpers/mod.rs index 5abb5a0..0e9a2ce 100644 --- a/crates/filterer/tagged/tests/helpers/mod.rs +++ b/crates/filterer/tagged/tests/helpers/mod.rs @@ -8,6 +8,7 @@ use std::{ use ignore_files::{IgnoreFile, IgnoreFilter}; use project_origins::ProjectType; +use tokio::fs::canonicalize; use watchexec::{ error::RuntimeError, event::{filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag}, @@ -49,7 +50,7 @@ pub trait PathHarness: Filterer { } fn path_pass(&self, path: &str, file_type: Option, pass: bool) { - let origin = dunce::canonicalize(".").unwrap(); + let origin = std::fs::canonicalize(".").unwrap(); let full_path = if let Some(suf) = path.strip_prefix("/test/") { origin.join(suf) } else if Path::new(path).has_root() { @@ -207,24 +208,28 @@ fn tracing_init() { pub async fn ignore_filt(origin: &str, ignore_files: &[IgnoreFile]) -> IgnoreFilter { tracing_init(); - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = canonicalize(".").await.unwrap().join(origin); IgnoreFilter::new(origin, ignore_files) .await .expect("making filterer") } pub async fn tagged_filt(filters: &[Filter]) -> Arc { - let origin = dunce::canonicalize(".").unwrap(); + let origin = canonicalize(".").await.unwrap(); tracing_init(); - let filterer = TaggedFilterer::new(origin.clone(), origin).expect("creating filterer"); + let filterer = TaggedFilterer::new(origin.clone(), origin) + .await + .expect("creating filterer"); filterer.add_filters(filters).await.expect("adding filters"); filterer } pub async fn tagged_igfilt(origin: &str, ignore_files: &[IgnoreFile]) -> Arc { - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = canonicalize(".").await.unwrap().join(origin); tracing_init(); - let filterer = TaggedFilterer::new(origin.clone(), origin).expect("creating filterer"); + let filterer = TaggedFilterer::new(origin.clone(), origin) + .await + .expect("creating filterer"); for file in ignore_files { tracing::info!(?file, "loading ignore file"); filterer @@ -255,8 +260,9 @@ pub async fn tagged_fffilt( filterer } -pub fn ig_file(name: &str) -> IgnoreFile { - let path = dunce::canonicalize(".") +pub async fn ig_file(name: &str) -> IgnoreFile { + let path = canonicalize(".") + .await .unwrap() .join("tests") .join("ignores") @@ -268,8 +274,8 @@ pub fn ig_file(name: &str) -> IgnoreFile { } } -pub fn ff_file(name: &str) -> FilterFile { - FilterFile(ig_file(name)) +pub async fn ff_file(name: &str) -> FilterFile { + FilterFile(ig_file(name).await) } pub trait Applies { @@ -279,7 +285,7 @@ pub trait Applies { impl Applies for IgnoreFile { fn applies_in(mut self, origin: &str) -> Self { - let origin = dunce::canonicalize(".").unwrap().join(origin); + let origin = std::fs::canonicalize(".").unwrap().join(origin); self.applies_in = Some(origin); self } @@ -337,7 +343,7 @@ pub trait FilterExt { impl FilterExt for Filter { fn in_subpath(mut self, sub: impl AsRef) -> Self { - let origin = dunce::canonicalize(".").unwrap(); + let origin = std::fs::canonicalize(".").unwrap(); self.in_path = Some(origin.join(sub)); self } diff --git a/crates/ignore-files/Cargo.toml b/crates/ignore-files/Cargo.toml index 6697dd4..4b03444 100644 --- a/crates/ignore-files/Cargo.toml +++ b/crates/ignore-files/Cargo.toml @@ -16,13 +16,12 @@ edition = "2021" [dependencies] futures = "0.3.21" -ignore = "0.4.18" git-config = "0.12.0" -tokio = { version = "1.19.2", default-features = false, features = ["fs"] } -tracing = "0.1.35" -dunce = "1.0.2" +ignore = "0.4.18" miette = "5.3.0" thiserror = "1.0.31" +tokio = { version = "1.19.2", default-features = false, features = ["fs"] } +tracing = "0.1.35" [dependencies.project-origins] version = "1.1.1" diff --git a/crates/ignore-files/src/discover.rs b/crates/ignore-files/src/discover.rs index 3414391..a158d56 100644 --- a/crates/ignore-files/src/discover.rs +++ b/crates/ignore-files/src/discover.rs @@ -7,7 +7,7 @@ use std::{ use git_config::{path::interpolate::Context as InterpolateContext, File, Path as GitPath}; use project_origins::ProjectType; -use tokio::fs::{metadata, read_dir}; +use tokio::fs::{canonicalize, metadata, read_dir}; use tracing::{trace, trace_span}; use crate::{IgnoreFile, IgnoreFilter}; @@ -45,7 +45,12 @@ const PATH_SEPARATOR: &str = ";"; /// return an `IgnoreFile { path: path/to/that/file, applies_in: None, applies_to: Some(ProjectType::Git) }`. /// This is the only case in which the `applies_in` field is None from this function. When such is /// received the global Git ignore files found by [`from_environment()`] **should be ignored**. -pub async fn from_origin(path: impl AsRef) -> (Vec, Vec) { +/// +/// ## Async +/// +/// This future is not `Send` due to [`git_config`] internals. +#[allow(clippy::future_not_send)] +pub async fn from_origin(path: impl AsRef + Send) -> (Vec, Vec) { let base = path.as_ref().to_owned(); let mut files = Vec::new(); let mut errors = Vec::new(); @@ -60,7 +65,8 @@ pub async fn from_origin(path: impl AsRef) -> (Vec, Vec )), Some(Err(err)) => errors.push(Error::new(ErrorKind::Other, err)), Some(Ok(config)) => { - if let Ok(excludes) = config.value::>("core", None, "excludesFile") { + let config_excludes = config.value::>("core", None, "excludesFile"); + if let Ok(excludes) = config_excludes { match excludes.interpolate(InterpolateContext { home_dir: env::var("HOME").ok().map(PathBuf::from).as_deref(), ..Default::default() @@ -190,6 +196,11 @@ pub async fn from_origin(path: impl AsRef) -> (Vec, Vec /// All errors (permissions, etc) are collected and returned alongside the ignore files: you may /// want to show them to the user while still using whatever ignores were successfully found. Errors /// from files not being found are silently ignored (the files are just not returned). +/// +/// ## Async +/// +/// This future is not `Send` due to [`git_config`] internals. +#[allow(clippy::future_not_send)] pub async fn from_environment(appname: Option<&str>) -> (Vec, Vec) { let mut files = Vec::new(); let mut errors = Vec::new(); @@ -213,7 +224,8 @@ pub async fn from_environment(appname: Option<&str>) -> (Vec, Vec errors.push(Error::new(ErrorKind::Other, err)), Ok(Err(err)) => errors.push(Error::new(ErrorKind::Other, err)), Ok(Ok(config)) => { - if let Ok(excludes) = config.value::>("core", None, "excludesFile") { + let config_excludes = config.value::>("core", None, "excludesFile"); + if let Ok(excludes) = config_excludes { match excludes.interpolate(InterpolateContext { home_dir: env::var("HOME").ok().map(PathBuf::from).as_deref(), ..Default::default() @@ -314,6 +326,8 @@ pub async fn from_environment(appname: Option<&str>) -> (Vec, Vec, @@ -322,7 +336,6 @@ pub async fn discover_file( applies_to: Option, path: PathBuf, ) -> bool { - let _span = trace_span!("discover_file", ?path, ?applies_in, ?applies_to).entered(); match find_file(path).await { Err(err) => { trace!(?err, "found an error"); @@ -372,7 +385,7 @@ enum Visit { impl DirTourist { pub async fn new(base: &Path, files: &[IgnoreFile]) -> Result { - let base = dunce::canonicalize(base)?; + let base = canonicalize(base).await?; trace!("create IgnoreFilterer for visiting directories"); let mut filter = IgnoreFilter::new(&base, files) .await @@ -389,9 +402,8 @@ impl DirTourist { "/.svn", "/.pijul", ], - Some(base.clone()), + Some(&base), ) - .await .map_err(|err| Error::new(ErrorKind::Other, err))?; Ok(Self { @@ -403,74 +415,80 @@ impl DirTourist { }) } + #[allow(clippy::future_not_send)] pub async fn next(&mut self) -> Visit { if let Some(path) = self.to_visit.pop() { - let _span = trace_span!("visit_path", ?path).entered(); - if self.must_skip(&path) { - trace!("in skip list"); - return Visit::Skip; - } - - if !self.filter.check_dir(&path) { - trace!("path is ignored, adding to skip list"); - self.skip(path); - return Visit::Skip; - } - - let mut dir = match read_dir(&path).await { - Ok(dir) => dir, - Err(err) => { - trace!("failed to read dir: {}", err); - self.errors.push(err); - return Visit::Skip; - } - }; - - while let Some(entry) = match dir.next_entry().await { - Ok(entry) => entry, - Err(err) => { - trace!("failed to read dir entries: {}", err); - self.errors.push(err); - return Visit::Skip; - } - } { - let path = entry.path(); - let _span = trace_span!("dir_entry", ?path).entered(); - - if self.must_skip(&path) { - trace!("in skip list"); - continue; - } - - match entry.file_type().await { - Ok(ft) => { - if ft.is_dir() { - if !self.filter.check_dir(&path) { - trace!("path is ignored, adding to skip list"); - self.skip(path); - continue; - } - - trace!("found a dir, adding to list"); - self.to_visit.push(path); - } else { - trace!("not a dir"); - } - } - Err(err) => { - trace!("failed to read filetype, adding to skip list: {}", err); - self.errors.push(err); - self.skip(path); - } - } - } - - Visit::Find(path) + self.visit_path(path).await } else { Visit::Done } } + #[allow(clippy::future_not_send)] + #[tracing::instrument(skip(self), level = "trace")] + async fn visit_path(&mut self, path: PathBuf) -> Visit { + if self.must_skip(&path) { + trace!("in skip list"); + return Visit::Skip; + } + + if !self.filter.check_dir(&path) { + trace!("path is ignored, adding to skip list"); + self.skip(path); + return Visit::Skip; + } + + let mut dir = match read_dir(&path).await { + Ok(dir) => dir, + Err(err) => { + trace!("failed to read dir: {}", err); + self.errors.push(err); + return Visit::Skip; + } + }; + + while let Some(entry) = match dir.next_entry().await { + Ok(entry) => entry, + Err(err) => { + trace!("failed to read dir entries: {}", err); + self.errors.push(err); + return Visit::Skip; + } + } { + let path = entry.path(); + let _span = trace_span!("dir_entry", ?path).entered(); + + if self.must_skip(&path) { + trace!("in skip list"); + continue; + } + + match entry.file_type().await { + Ok(ft) => { + if ft.is_dir() { + if !self.filter.check_dir(&path) { + trace!("path is ignored, adding to skip list"); + self.skip(path); + continue; + } + + trace!("found a dir, adding to list"); + self.to_visit.push(path); + } else { + trace!("not a dir"); + } + } + Err(err) => { + trace!("failed to read filetype, adding to skip list: {}", err); + self.errors.push(err); + self.skip(path); + } + } + } + + Visit::Find(path) + } + pub fn skip(&mut self, path: PathBuf) { let check_path = path.as_path(); self.to_visit.retain(|p| !p.starts_with(check_path)); diff --git a/crates/ignore-files/src/filter.rs b/crates/ignore-files/src/filter.rs index 58ac249..2f3893c 100644 --- a/crates/ignore-files/src/filter.rs +++ b/crates/ignore-files/src/filter.rs @@ -39,7 +39,7 @@ impl IgnoreFilter { /// /// Use [`empty()`](IgnoreFilterer::empty()) if you want an empty filterer, /// or to construct one outside an async environment. - pub async fn new(origin: impl AsRef, files: &[IgnoreFile]) -> Result { + pub async fn new(origin: impl AsRef + Send, files: &[IgnoreFile]) -> Result { let origin = origin.as_ref(); let _span = trace_span!("build_filterer", ?origin); @@ -113,6 +113,7 @@ impl IgnoreFilter { } /// Returns the number of ignores and allowlists loaded. + #[must_use] pub fn num_ignores(&self) -> (u64, u64) { (self.compiled.num_ignores(), self.compiled.num_whitelists()) } @@ -183,11 +184,7 @@ impl IgnoreFilter { /// Adds some globs manually, if the builder is available. /// /// Does nothing silently otherwise. - pub async fn add_globs( - &mut self, - globs: &[&str], - applies_in: Option, - ) -> Result<(), Error> { + pub fn add_globs(&mut self, globs: &[&str], applies_in: Option<&PathBuf>) -> Result<(), Error> { if let Some(ref mut builder) = self.builder { let _span = trace_span!("loading ignore globs", ?globs).entered(); for line in globs { @@ -197,7 +194,7 @@ impl IgnoreFilter { trace!(?line, "adding ignore line"); builder - .add_line(applies_in.clone(), line) + .add_line(applies_in.cloned(), line) .map_err(|err| Error::Glob { file: None, err })?; } diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 1a8d07f..e9bdf51 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://watchexec.github.io" repository = "https://github.com/watchexec/watchexec" readme = "README.md" -rust-version = "1.60.0" +rust-version = "1.61.0" edition = "2021" [dependencies] @@ -20,11 +20,11 @@ async-priority-channel = "0.1.0" async-recursion = "1.0.0" atomic-take = "1.0.0" clearscreen = "1.0.9" -dunce = "1.0.2" futures = "0.3.16" miette = "5.3.0" once_cell = "1.8.0" thiserror = "1.0.26" +normalize-path = "0.2.0" [dependencies.command-group] version = "1.0.8" @@ -57,8 +57,5 @@ features = [ version = "0.1.26" features = ["log"] -[target.'cfg(unix)'.dependencies] -libc = "0.2.104" - [dev-dependencies] tracing-subscriber = "0.3.6" diff --git a/crates/lib/README.md b/crates/lib/README.md index e4755d4..c598594 100644 --- a/crates/lib/README.md +++ b/crates/lib/README.md @@ -1,7 +1,7 @@ [![Crates.io page](https://badgen.net/crates/v/watchexec)](https://crates.io/crates/watchexec) [![API Docs](https://docs.rs/watchexec/badge.svg)][docs] [![Crate license: Apache 2.0](https://badgen.net/badge/license/Apache%202.0)][license] -![MSRV: 1.60.0 (minor)](https://badgen.net/badge/MSRV/1.60.0%20%28minor%29/0b7261) +![MSRV: 1.61.0 (minor)](https://badgen.net/badge/MSRV/1.61.0%20%28minor%29/0b7261) [![CI status](https://github.com/watchexec/watchexec/actions/workflows/check.yml/badge.svg)](https://github.com/watchexec/watchexec/actions/workflows/check.yml) # Watchexec library @@ -10,7 +10,7 @@ _The library which powers [Watchexec CLI](https://watchexec.github.io) and other - **[API documentation][docs]**. - Licensed under [Apache 2.0][license]. -- Minimum Supported Rust Version: 1.60.0 (incurs a minor semver bump). +- Minimum Supported Rust Version: 1.61.0 (incurs a minor semver bump). - Status: maintained. [docs]: https://docs.rs/watchexec diff --git a/crates/lib/examples/demo.rs b/crates/lib/examples/demo.rs index 8a36a4e..edc5634 100644 --- a/crates/lib/examples/demo.rs +++ b/crates/lib/examples/demo.rs @@ -6,6 +6,7 @@ use watchexec::{ command::Command, config::{InitConfig, RuntimeConfig}, error::ReconfigError, + event::Event, fs::Watcher, signal::source::MainSignal, ErrorHook, Watchexec, @@ -37,12 +38,12 @@ async fn main() -> Result<()> { let mut config = config.clone(); let w = w.clone(); async move { - eprintln!("Watchexec Action: {:?}", action); + eprintln!("Watchexec Action: {action:?}"); let sigs = action .events .iter() - .flat_map(|event| event.signals()) + .flat_map(Event::signals) .collect::>(); if sigs.iter().any(|sig| sig == &MainSignal::Interrupt) { diff --git a/crates/lib/src/action/outcome.rs b/crates/lib/src/action/outcome.rs index 7b18c7e..b015596 100644 --- a/crates/lib/src/action/outcome.rs +++ b/crates/lib/src/action/outcome.rs @@ -66,21 +66,25 @@ impl Default for Outcome { impl Outcome { /// Convenience function to create an outcome conditional on the state of the subprocess. - pub fn if_running(then: Outcome, otherwise: Outcome) -> Self { + #[must_use] + pub fn if_running(then: Self, otherwise: Self) -> Self { Self::IfRunning(Box::new(then), Box::new(otherwise)) } /// Convenience function to create a sequence of outcomes. - pub fn both(one: Outcome, two: Outcome) -> Self { + #[must_use] + pub fn both(one: Self, two: Self) -> Self { Self::Both(Box::new(one), Box::new(two)) } /// Convenience function to wait for the subprocess to complete before executing the outcome. - pub fn wait(and_then: Outcome) -> Self { - Self::Both(Box::new(Outcome::Wait), Box::new(and_then)) + #[must_use] + pub fn wait(and_then: Self) -> Self { + Self::Both(Box::new(Self::Wait), Box::new(and_then)) } /// Resolves the outcome given the current state of the subprocess. + #[must_use] pub fn resolve(self, is_running: bool) -> Self { match (is_running, self) { (true, Self::IfRunning(then, _)) => then.resolve(true), diff --git a/crates/lib/src/action/outcome_worker.rs b/crates/lib/src/action/outcome_worker.rs index f2d08b5..602c5b4 100644 --- a/crates/lib/src/action/outcome_worker.rs +++ b/crates/lib/src/action/outcome_worker.rs @@ -79,7 +79,7 @@ impl OutcomeWorker { }); } - async fn check_gen(&self, f: impl Future) -> Option { + async fn check_gen(&self, f: impl Future + Send) -> Option { // TODO: use a select and a notifier of some kind so it cancels tasks if self.gencheck.load(Ordering::SeqCst) != self.gen { warn!(when=%"pre", gen=%self.gen, "outcome worker was cycled, aborting"); @@ -113,9 +113,7 @@ impl OutcomeWorker { notry!(self.process.wait())?; notry!(self.process.drop_inner()); } - (false, o @ Outcome::Stop) - | (false, o @ Outcome::Wait) - | (false, o @ Outcome::Signal(_)) => { + (false, o @ (Outcome::Stop | Outcome::Wait | Outcome::Signal(_))) => { debug!(outcome=?o, "meaningless without a process, not doing anything"); } (_, Outcome::Start) => { diff --git a/crates/lib/src/action/process_holder.rs b/crates/lib/src/action/process_holder.rs index 51c026b..f3792a9 100644 --- a/crates/lib/src/action/process_holder.rs +++ b/crates/lib/src/action/process_holder.rs @@ -13,8 +13,7 @@ impl ProcessHolder { .read() .await .as_ref() - .map(|p| p.is_running()) - .unwrap_or(false) + .map_or(false, Supervisor::is_running) } pub async fn is_some(&self) -> bool { diff --git a/crates/lib/src/action/worker.rs b/crates/lib/src/action/worker.rs index 831dbec..c38cf3f 100644 --- a/crates/lib/src/action/worker.rs +++ b/crates/lib/src/action/worker.rs @@ -55,9 +55,9 @@ pub async fn worker( trace!("out of throttle but nothing to do, resetting"); last = Instant::now(); continue; - } else { - trace!("out of throttle on recycle"); } + + trace!("out of throttle on recycle"); } else { trace!(?maxtime, "waiting for event"); let maybe_event = timeout(maxtime, events.recv()).await; @@ -116,6 +116,7 @@ pub async fn worker( trace!("out of throttle, starting action process"); last = Instant::now(); + #[allow(clippy::iter_with_drain)] let events = Arc::from(set.drain(..).collect::>().into_boxed_slice()); let action = Action::new(Arc::clone(&events)); debug!(?action, "action constructed"); @@ -130,7 +131,7 @@ pub async fn worker( let err = action_handler .call(action) .await - .map_err(|e| rte("action worker", e)); + .map_err(|e| rte("action worker", e.as_ref())); if let Err(err) = err { errors.send(err).await?; debug!("action handler errored, skipping"); diff --git a/crates/lib/src/command.rs b/crates/lib/src/command.rs index 0ddf305..3573cec 100644 --- a/crates/lib/src/command.rs +++ b/crates/lib/src/command.rs @@ -88,13 +88,13 @@ impl Command { trace!(cmd=?self, "constructing command"); match self { - Command::Exec { prog, args } => { + Self::Exec { prog, args } => { let mut c = TokioCommand::new(prog); c.args(args); Ok(c) } - Command::Shell { + Self::Shell { shell, args, command, @@ -133,16 +133,16 @@ impl Command { impl fmt::Display for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Command::Exec { prog, args } => { - write!(f, "{}", prog)?; + Self::Exec { prog, args } => { + write!(f, "{prog}")?; for arg in args { - write!(f, " {}", arg)?; + write!(f, " {arg}")?; } Ok(()) } - Command::Shell { command, .. } => { - write!(f, "{}", command) + Self::Shell { command, .. } => { + write!(f, "{command}") } } } diff --git a/crates/lib/src/command/process.rs b/crates/lib/src/command/process.rs index c43b902..a9c2181 100644 --- a/crates/lib/src/command/process.rs +++ b/crates/lib/src/command/process.rs @@ -25,7 +25,7 @@ pub enum Process { impl Default for Process { /// Returns [`Process::None`]. fn default() -> Self { - Process::None + Self::None } } diff --git a/crates/lib/src/command/supervisor.rs b/crates/lib/src/command/supervisor.rs index 650a5ac..8ee5306 100644 --- a/crates/lib/src/command/supervisor.rs +++ b/crates/lib/src/command/supervisor.rs @@ -160,7 +160,7 @@ impl Supervisor { let event = Event { tags: vec![ Tag::Source(Source::Internal), - Tag::ProcessCompletion(status.map(|s| s.into())), + Tag::ProcessCompletion(status.map(Into::into)), ], metadata: Default::default(), }; @@ -214,7 +214,7 @@ impl Supervisor { /// when the signal has been delivered. pub async fn signal(&self, signal: SubSignal) { if cfg!(windows) { - if let SubSignal::ForceStop = signal { + if signal == SubSignal::ForceStop { self.intervene.send(Intervention::Kill).await.ok(); } // else: https://github.com/watchexec/watchexec/issues/219 @@ -293,7 +293,7 @@ async fn spawn_process( pre_spawn_handler .call(pre_spawn) .await - .map_err(|e| rte("action pre-spawn", e))?; + .map_err(|e| rte("action pre-spawn", e.as_ref()))?; let (proc, id, post_spawn) = span.in_scope::<_, Result<_, RuntimeError>>(|| { let mut spawnable = Arc::try_unwrap(spawnable) @@ -337,7 +337,7 @@ async fn spawn_process( post_spawn_handler .call(post_spawn) .await - .map_err(|e| rte("action post-spawn", e))?; + .map_err(|e| rte("action post-spawn", e.as_ref()))?; Ok((proc, id)) } diff --git a/crates/lib/src/command/tests.rs b/crates/lib/src/command/tests.rs index a352e12..f8ab72a 100644 --- a/crates/lib/src/command/tests.rs +++ b/crates/lib/src/command/tests.rs @@ -9,7 +9,7 @@ async fn unix_shell_none() -> Result<(), std::io::Error> { args: vec!["hi".into()] } .to_spawnable() - .unwrap() + .expect("echo directly") .group_status() .await? .success()); @@ -25,7 +25,7 @@ async fn unix_shell_sh() -> Result<(), std::io::Error> { command: "echo hi".into() } .to_spawnable() - .unwrap() + .expect("echo with sh") .group_status() .await? .success()); @@ -41,7 +41,7 @@ async fn unix_shell_alternate() -> Result<(), std::io::Error> { command: "echo hi".into() } .to_spawnable() - .unwrap() + .expect("echo with bash") .group_status() .await? .success()); @@ -57,7 +57,7 @@ async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> { command: "echo hi".into() } .to_spawnable() - .unwrap() + .expect("echo with shopts") .group_status() .await? .success()); diff --git a/crates/lib/src/config.rs b/crates/lib/src/config.rs index c95cdab..328c454 100644 --- a/crates/lib/src/config.rs +++ b/crates/lib/src/config.rs @@ -25,8 +25,8 @@ use crate::{ /// Another advantage of using the convenience methods is that each one contains a call to the /// [`debug!`] macro, providing insight into what config your application sets for "free". /// -/// You should see the detailed documentation on [fs::WorkingData][crate::fs::WorkingData] and -/// [action::WorkingData][crate::action::WorkingData] for important information and particulars +/// You should see the detailed documentation on [`fs::WorkingData`][crate::fs::WorkingData] and +/// [`action::WorkingData`][crate::action::WorkingData] for important information and particulars /// about each field, especially the handlers. #[derive(Clone, Debug, Default)] #[non_exhaustive] diff --git a/crates/lib/src/event.rs b/crates/lib/src/event.rs index e07c9bf..3a6dffc 100644 --- a/crates/lib/src/event.rs +++ b/crates/lib/src/event.rs @@ -71,15 +71,16 @@ pub enum Tag { impl Tag { /// The name of the variant. + #[must_use] pub const fn discriminant_name(&self) -> &'static str { match self { - Tag::Path { .. } => "Path", - Tag::FileEventKind(_) => "FileEventKind", - Tag::Source(_) => "Source", - Tag::Keyboard(_) => "Keyboard", - Tag::Process(_) => "Process", - Tag::Signal(_) => "Signal", - Tag::ProcessCompletion(_) => "ProcessCompletion", + Self::Path { .. } => "Path", + Self::FileEventKind(_) => "FileEventKind", + Self::Source(_) => "Source", + Self::Keyboard(_) => "Keyboard", + Self::Process(_) => "Process", + Self::Signal(_) => "Signal", + Self::ProcessCompletion(_) => "ProcessCompletion", } } } @@ -164,39 +165,24 @@ pub enum ProcessEnd { } impl From for ProcessEnd { - #[cfg(target_os = "fuchsia")] - fn from(es: ExitStatus) -> Self { - // Once https://github.com/rust-lang/rust/pull/88300 (unix_process_wait_more) lands, use - // that API instead of doing the transmute, and clean up the forbid condition at crate root. - let raw: i64 = unsafe { std::mem::transmute(es) }; - NonZeroI64::try_from(raw) - .map(Self::ExitError) - .unwrap_or(Self::Success) - } - - #[cfg(all(unix, not(target_os = "fuchsia")))] + #[cfg(unix)] fn from(es: ExitStatus) -> Self { use std::os::unix::process::ExitStatusExt; - match (es.code(), es.signal()) { - (Some(_), Some(_)) => { + + match (es.code(), es.signal(), es.stopped_signal()) { + (Some(_), Some(_), _) => { unreachable!("exitstatus cannot both be code and signal?!") } - (Some(code), None) => match NonZeroI64::try_from(i64::from(code)) { - Ok(code) => Self::ExitError(code), - Err(_) => Self::Success, - }, - // TODO: once unix_process_wait_more lands, use stopped_signal() instead and clear the libc dep - (None, Some(signal)) if libc::WIFSTOPPED(-signal) => { - match NonZeroI32::try_from(libc::WSTOPSIG(-signal)) { - Ok(signal) => Self::ExitStop(signal), - Err(_) => Self::Success, - } + (Some(code), None, _) => { + NonZeroI64::try_from(i64::from(code)).map_or(Self::Success, Self::ExitError) + } + (None, Some(_), Some(stopsig)) => { + NonZeroI32::try_from(stopsig).map_or(Self::Success, Self::ExitStop) } - // TODO: once unix_process_wait_more lands, use continued() instead and clear the libc dep #[cfg(not(target_os = "vxworks"))] - (None, Some(signal)) if libc::WIFCONTINUED(-signal) => Self::Continued, - (None, Some(signal)) => Self::ExitSignal(signal.into()), - (None, None) => Self::Success, + (None, Some(_), _) if es.continued() => Self::Continued, + (None, Some(signal), _) => Self::ExitSignal(signal.into()), + (None, None, _) => Self::Success, } } @@ -302,6 +288,7 @@ impl Default for Priority { impl Event { /// Returns true if the event has an Internal source tag. + #[must_use] pub fn is_internal(&self) -> bool { self.tags .iter() @@ -309,6 +296,7 @@ impl Event { } /// Returns true if the event has no tags. + #[must_use] pub fn is_empty(&self) -> bool { self.tags.is_empty() } @@ -346,16 +334,16 @@ impl fmt::Display for Event { Tag::Path { path, file_type } => { write!(f, " path={}", path.display())?; if let Some(ft) = file_type { - write!(f, " filetype={}", ft)?; + write!(f, " filetype={ft}")?; } } - Tag::FileEventKind(kind) => write!(f, " kind={:?}", kind)?, - Tag::Source(s) => write!(f, " source={:?}", s)?, - Tag::Keyboard(k) => write!(f, " keyboard={:?}", k)?, - Tag::Process(p) => write!(f, " process={}", p)?, - Tag::Signal(s) => write!(f, " signal={:?}", s)?, + Tag::FileEventKind(kind) => write!(f, " kind={kind:?}")?, + Tag::Source(s) => write!(f, " source={s:?}")?, + Tag::Keyboard(k) => write!(f, " keyboard={k:?}")?, + Tag::Process(p) => write!(f, " process={p}")?, + Tag::Signal(s) => write!(f, " signal={s:?}")?, Tag::ProcessCompletion(None) => write!(f, " command-completed")?, - Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({:?})", c)?, + Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({c:?})")?, } } diff --git a/crates/lib/src/filter.rs b/crates/lib/src/filter.rs index 22ea101..5afc60f 100644 --- a/crates/lib/src/filter.rs +++ b/crates/lib/src/filter.rs @@ -30,6 +30,6 @@ impl Filterer for () { impl Filterer for Arc { fn check_event(&self, event: &Event, priority: Priority) -> Result { - Arc::as_ref(self).check_event(event, priority) + Self::as_ref(self).check_event(event, priority) } } diff --git a/crates/lib/src/fs.rs b/crates/lib/src/fs.rs index e4d2770..a24aac0 100644 --- a/crates/lib/src/fs.rs +++ b/crates/lib/src/fs.rs @@ -9,9 +9,10 @@ use std::{ }; use async_priority_channel as priority; +use normalize_path::NormalizePath; use notify::{Config, Watcher as _}; use tokio::sync::{mpsc, watch}; -use tracing::{debug, error, trace, warn}; +use tracing::{debug, error, trace}; use crate::{ error::{CriticalError, FsWatcherError, RuntimeError}, @@ -131,8 +132,8 @@ impl AsRef for WatchedPath { /// _not_ to drop the watch sender: this will cause the worker to stop gracefully, which may not be /// what was expected. /// -/// Note that the paths emitted by the watcher are canonicalised. No guarantee is made about the -/// implementation or output of that canonicalisation (i.e. it might not be `std`'s). +/// Note that the paths emitted by the watcher are normalised. No guarantee is made about the +/// implementation or output of that normalisation (it may change without notice). /// /// # Examples /// @@ -188,13 +189,13 @@ pub async fn worker( } else { let mut to_watch = Vec::with_capacity(data.pathset.len()); let mut to_drop = Vec::with_capacity(pathset.len()); - for path in data.pathset.iter() { + for path in &data.pathset { if !pathset.contains(path) { to_watch.push(path.clone()); } } - for path in pathset.iter() { + for path in &pathset { if !data.pathset.contains(path) { to_drop.push(path.clone()); } @@ -210,7 +211,7 @@ pub async fn worker( let n_events = events.clone(); match kind.create(move |nev: Result| { trace!(event = ?nev, "receiving possible event from watcher"); - if let Err(e) = process_event(nev, kind, n_events.clone()) { + if let Err(e) = process_event(nev, kind, &n_events) { n_errors.try_send(e).ok(); } }) { @@ -296,7 +297,7 @@ fn notify_multi_path_errors( fn process_event( nev: Result, kind: Watcher, - n_events: priority::Sender, + n_events: &priority::Sender, ) -> Result<(), RuntimeError> { let nev = nev.map_err(|err| RuntimeError::FsWatcher { kind, @@ -311,10 +312,7 @@ fn process_event( // possibly pull file_type from whatever notify (or the native driver) returns? tags.push(Tag::Path { file_type: metadata(&path).ok().map(|m| m.file_type().into()), - path: dunce::canonicalize(&path).unwrap_or_else(|err| { - warn!(?err, ?path, "failed to canonicalise event path"); - path - }), + path: path.normalize(), }); } diff --git a/crates/lib/src/handler.rs b/crates/lib/src/handler.rs index fdcf02a..b2c12ab 100644 --- a/crates/lib/src/handler.rs +++ b/crates/lib/src/handler.rs @@ -100,8 +100,12 @@ pub trait Handler { /// /// Internally this is a Tokio [`Mutex`]. pub struct HandlerLock(Arc + Send>>>); -impl HandlerLock { +impl HandlerLock +where + T: Send, +{ /// Wrap a [`Handler`] into a lock. + #[must_use] pub fn new(handler: Box + Send>) -> Self { Self(Arc::new(Mutex::new(handler))) } @@ -125,13 +129,16 @@ impl Clone for HandlerLock { } } -impl Default for HandlerLock { +impl Default for HandlerLock +where + T: Send, +{ fn default() -> Self { Self::new(Box::new(())) } } -pub(crate) fn rte(ctx: &'static str, err: Box) -> RuntimeError { +pub(crate) fn rte(ctx: &'static str, err: &dyn Error) -> RuntimeError { RuntimeError::Handler { ctx, err: err.to_string(), @@ -239,7 +246,7 @@ where W: Write, { fn handle(&mut self, data: T) -> Result<(), Box> { - writeln!(self.0, "{:?}", data).map_err(|e| Box::new(e) as _) + writeln!(self.0, "{data:?}").map_err(|e| Box::new(e) as _) } } @@ -252,6 +259,6 @@ where W: Write, { fn handle(&mut self, data: T) -> Result<(), Box> { - writeln!(self.0, "{}", data).map_err(|e| Box::new(e) as _) + writeln!(self.0, "{data}").map_err(|e| Box::new(e) as _) } } diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 259ee8c..ebd56e8 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -96,8 +96,7 @@ #![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")] #![warn(clippy::unwrap_used, missing_docs)] #![deny(rust_2018_idioms)] -#![cfg_attr(not(target_os = "fuchsia"), forbid(unsafe_code))] -// see event::ProcessEnd for why this is disabled on fuchsia +#![forbid(unsafe_code)] // the toolkit to make your own pub mod action; diff --git a/crates/lib/src/paths.rs b/crates/lib/src/paths.rs index 056fc78..6f5ff73 100644 --- a/crates/lib/src/paths.rs +++ b/crates/lib/src/paths.rs @@ -129,15 +129,14 @@ pub fn summarise_events_to_env<'events>( _ => "OTHERWISE_CHANGED", }) .or_insert_with(HashSet::new) - .extend(paths.into_iter().map(|p| { - if let Some(suffix) = common_path + .extend(paths.into_iter().map(|ref p| { + common_path .as_ref() .and_then(|prefix| p.strip_prefix(prefix).ok()) - { - suffix.as_os_str().to_owned() - } else { - p.into_os_string() - } + .map_or_else( + || p.clone().into_os_string(), + |suffix| suffix.as_os_str().to_owned(), + ) })); } diff --git a/crates/lib/src/signal/process.rs b/crates/lib/src/signal/process.rs index cf7d6f7..cab2dca 100644 --- a/crates/lib/src/signal/process.rs +++ b/crates/lib/src/signal/process.rs @@ -114,6 +114,7 @@ impl SubSignal { /// This will return `None` if the signal is not supported on the current platform (only for /// [`Custom`][SubSignal::Custom], as the first-class ones are always supported). #[cfg(unix)] + #[must_use] pub fn to_nix(self) -> Option { match self { Self::Hangup => Some(NixSignal::SIGHUP), @@ -129,6 +130,8 @@ impl SubSignal { /// Converts from a [`nix::Signal`][command_group::Signal]. #[cfg(unix)] + #[allow(clippy::missing_const_for_fn)] + #[must_use] pub fn from_nix(sig: NixSignal) -> Self { match sig { NixSignal::SIGHUP => Self::Hangup, diff --git a/crates/lib/src/watchexec.rs b/crates/lib/src/watchexec.rs index b9a7cb6..22b51a2 100644 --- a/crates/lib/src/watchexec.rs +++ b/crates/lib/src/watchexec.rs @@ -204,7 +204,7 @@ async fn error_hook( let crit = hook.critical.clone(); if let Err(err) = handler.handle(hook) { error!(%err, "error while handling error"); - let rehook = ErrorHook::new(rte("error hook", err)); + let rehook = ErrorHook::new(rte("error hook", err.as_ref())); let recrit = rehook.critical.clone(); handler.handle(rehook).unwrap_or_else(|err| { error!(%err, "error while handling error of handling error"); @@ -248,17 +248,16 @@ impl ErrorHook { ) -> Result<(), CriticalError> { match Arc::try_unwrap(crit) { Err(err) => { - error!(?err, "{} hook has an outstanding ref", name); + error!(?err, "{name} hook has an outstanding ref"); Ok(()) } - Ok(crit) => { - if let Some(crit) = crit.into_inner() { - debug!(%crit, "{} output a critical error", name); + Ok(crit) => crit.into_inner().map_or_else( + || Ok(()), + |crit| { + debug!(%crit, "{name} output a critical error"); Err(crit) - } else { - Ok(()) - } - } + }, + ), } } diff --git a/crates/lib/tests/env_reporting.rs b/crates/lib/tests/env_reporting.rs index 76e413e..3dfc947 100644 --- a/crates/lib/tests/env_reporting.rs +++ b/crates/lib/tests/env_reporting.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ffi::OsString}; +use std::{collections::HashMap, ffi::OsString, path::MAIN_SEPARATOR}; use notify::event::CreateKind; use watchexec::{ @@ -12,7 +12,7 @@ const ENV_SEP: &str = ":"; const ENV_SEP: &str = ";"; fn ospath(path: &str) -> OsString { - let root = dunce::canonicalize(".").unwrap(); + let root = std::fs::canonicalize(".").unwrap(); if path.is_empty() { root } else { @@ -171,11 +171,13 @@ fn single_type_multipath() { ( "CREATED", OsString::from( - "".to_string() - + "deeper/sub/folder.txt" - + ENV_SEP + "dom/folder.txt" - + ENV_SEP + "root.txt" + ENV_SEP - + "sub/folder.txt" + [ + format!("deeper{MAIN_SEPARATOR}sub{MAIN_SEPARATOR}folder.txt"), + format!("dom{MAIN_SEPARATOR}folder.txt"), + "root.txt".to_string(), + format!("sub{MAIN_SEPARATOR}folder.txt"), + ] + .join(ENV_SEP) ) ), ("COMMON", ospath("")), @@ -194,7 +196,13 @@ fn single_type_divergent_paths() { HashMap::from([ ( "CREATED", - OsString::from("".to_string() + "dom/folder.txt" + ENV_SEP + "sub/folder.txt") + OsString::from( + [ + format!("dom{MAIN_SEPARATOR}folder.txt"), + format!("sub{MAIN_SEPARATOR}folder.txt"), + ] + .join(ENV_SEP) + ) ), ("COMMON", ospath("")), ]) @@ -218,11 +226,22 @@ fn multitype_multipath() { HashMap::from([ ( "CREATED", - OsString::from("".to_string() + "root.txt" + ENV_SEP + "sibling.txt"), + OsString::from(["root.txt", "sibling.txt"].join(ENV_SEP)), + ), + ( + "META_CHANGED", + OsString::from(format!("sub{MAIN_SEPARATOR}folder.txt")) + ), + ( + "REMOVED", + OsString::from(format!("dom{MAIN_SEPARATOR}folder.txt")) + ), + ( + "OTHERWISE_CHANGED", + OsString::from(format!( + "deeper{MAIN_SEPARATOR}sub{MAIN_SEPARATOR}folder.txt" + )) ), - ("META_CHANGED", OsString::from("sub/folder.txt"),), - ("REMOVED", OsString::from("dom/folder.txt"),), - ("OTHERWISE_CHANGED", OsString::from("deeper/sub/folder.txt"),), ("COMMON", ospath("")), ]) ); @@ -249,7 +268,7 @@ fn multiple_paths_in_one_event() { HashMap::from([ ( "OTHERWISE_CHANGED", - OsString::from("".to_string() + "one.txt" + ENV_SEP + "two.txt") + OsString::from(String::new() + "one.txt" + ENV_SEP + "two.txt") ), ("COMMON", ospath("")), ]) @@ -275,7 +294,7 @@ fn mixed_non_paths_events() { HashMap::from([ ( "OTHERWISE_CHANGED", - OsString::from("".to_string() + "one.txt" + ENV_SEP + "two.txt") + OsString::from(String::new() + "one.txt" + ENV_SEP + "two.txt") ), ("COMMON", ospath("")), ]) @@ -312,7 +331,7 @@ fn multipath_is_sorted() { ( "OTHERWISE_CHANGED", OsString::from( - "".to_string() + String::new() + "0123.txt" + ENV_SEP + "a.txt" + ENV_SEP + "b.txt" + ENV_SEP + "c.txt" + ENV_SEP + "ᄁ.txt" @@ -342,7 +361,7 @@ fn multipath_is_deduped() { ( "OTHERWISE_CHANGED", OsString::from( - "".to_string() + String::new() + "0123.txt" + ENV_SEP + "a.txt" + ENV_SEP + "b.txt" + ENV_SEP + "c.txt" + ENV_SEP + "ᄁ.txt" diff --git a/crates/project-origins/Cargo.toml b/crates/project-origins/Cargo.toml index 96e03db..f7300ad 100644 --- a/crates/project-origins/Cargo.toml +++ b/crates/project-origins/Cargo.toml @@ -20,6 +20,5 @@ tokio = { version = "1.19.2", features = ["fs"] } tokio-stream = { version = "0.1.9", features = ["fs"] } [dev-dependencies] -dunce = "1.0.2" miette = "5.3.0" tracing-subscriber = "0.3.11" diff --git a/crates/project-origins/examples/find-origins.rs b/crates/project-origins/examples/find-origins.rs index e1b3d04..abeaea7 100644 --- a/crates/project-origins/examples/find-origins.rs +++ b/crates/project-origins/examples/find-origins.rs @@ -9,7 +9,7 @@ async fn main() -> Result<()> { tracing_subscriber::fmt::init(); let first_arg = args().nth(1).unwrap_or_else(|| ".".to_string()); - let path = dunce::canonicalize(first_arg).into_diagnostic()?; + let path = tokio::fs::canonicalize(first_arg).await.into_diagnostic()?; for origin in origins(&path).await { println!("{}", origin.display()); diff --git a/crates/project-origins/src/lib.rs b/crates/project-origins/src/lib.rs index 36c8fda..c88fa51 100644 --- a/crates/project-origins/src/lib.rs +++ b/crates/project-origins/src/lib.rs @@ -152,7 +152,8 @@ pub enum ProjectType { impl ProjectType { /// Returns true if the project type is a VCS. - pub fn is_vcs(self) -> bool { + #[must_use] + pub const fn is_vcs(self) -> bool { matches!( self, Self::Bazaar @@ -163,7 +164,8 @@ impl ProjectType { } /// Returns true if the project type is a software suite. - pub fn is_soft(self) -> bool { + #[must_use] + pub const fn is_soft(self) -> bool { matches!( self, Self::Bundler @@ -187,10 +189,8 @@ impl ProjectType { /// /// This looks at a wider variety of files than the [`types`] function does: something can be /// detected as an origin but not be able to match to any particular [`ProjectType`]. -pub async fn origins(path: impl AsRef) -> HashSet { - let mut origins = HashSet::new(); - - fn check_list(list: DirList) -> bool { +pub async fn origins(path: impl AsRef + Send) -> HashSet { + fn check_list(list: &DirList) -> bool { if list.is_empty() { return false; } @@ -252,14 +252,17 @@ pub async fn origins(path: impl AsRef) -> HashSet { .any(|f| f) } - let mut current = path.as_ref(); - if check_list(DirList::obtain(current).await) { + let mut origins = HashSet::new(); + + let path = path.as_ref(); + let mut current = path; + if check_list(&DirList::obtain(current).await) { origins.insert(current.to_owned()); } while let Some(parent) = current.parent() { current = parent; - if check_list(DirList::obtain(current).await) { + if check_list(&DirList::obtain(current).await) { origins.insert(current.to_owned()); continue; } @@ -277,8 +280,9 @@ pub async fn origins(path: impl AsRef) -> HashSet { /// /// Note that this only detects project types listed in the [`ProjectType`] enum, and may not detect /// anything for some paths returned by [`origins()`]. -pub async fn types(path: impl AsRef) -> HashSet { - let list = DirList::obtain(path.as_ref()).await; +pub async fn types(path: impl AsRef + Send) -> HashSet { + let path = path.as_ref(); + let list = DirList::obtain(path).await; [ list.if_has_dir("_darcs", ProjectType::Darcs), list.if_has_dir(".bzr", ProjectType::Bazaar), @@ -353,13 +357,13 @@ impl DirList { #[inline] fn has_file(&self, name: impl AsRef) -> bool { let name = name.as_ref(); - self.0.get(name).map(|x| x.is_file()).unwrap_or(false) + self.0.get(name).map_or(false, std::fs::FileType::is_file) } #[inline] fn has_dir(&self, name: impl AsRef) -> bool { let name = name.as_ref(); - self.0.get(name).map(|x| x.is_dir()).unwrap_or(false) + self.0.get(name).map_or(false, std::fs::FileType::is_dir) } #[inline]