Clippy fixes (#465)
This commit is contained in:
parent
6d65c05e35
commit
dc98370492
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -275,7 +275,7 @@ pub fn get_args(tagged_filterer: bool) -> Result<ArgMatches> {
|
|||
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<OsString> = arg_file
|
||||
|
|
|
@ -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<InitConfig> {
|
||||
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<InitConfig> {
|
|||
},
|
||||
));
|
||||
|
||||
Ok(config)
|
||||
config
|
||||
}
|
||||
|
|
|
@ -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<RuntimeConfig> {
|
|||
|
||||
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<RuntimeConfig> {
|
|||
|
||||
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<RuntimeConfig> {
|
|||
return fut;
|
||||
}
|
||||
|
||||
let signals: Vec<MainSignal> = 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<MainSignal> = 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<RuntimeConfig> {
|
|||
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<RuntimeConfig> {
|
|||
.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<RuntimeConfig> {
|
|||
|
||||
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<RuntimeConfig> {
|
|||
),
|
||||
"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<RuntimeConfig> {
|
|||
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<RuntimeConfig> {
|
|||
.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<Command> {
|
|||
let mut cmd = args
|
||||
.values_of("command")
|
||||
.expect("(clap) Bug: command is not present")
|
||||
.map(|s| s.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(if args.is_present("no-shell") {
|
||||
|
@ -321,8 +315,8 @@ fn interpret_command_args(args: &ArgMatches) -> Result<Command> {
|
|||
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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -26,23 +26,23 @@ pub async fn globset(args: &ArgMatches) -> Result<Arc<WatchexecFilterer>> {
|
|||
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ pub async fn tagged(args: &ArgMatches) -> Result<Arc<TaggedFilterer>> {
|
|||
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<Arc<TaggedFilterer>> {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -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 !!!");
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Path>,
|
||||
filters: impl IntoIterator<Item = (String, Option<PathBuf>)>,
|
||||
|
@ -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");
|
||||
|
|
|
@ -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![
|
||||
|
|
|
@ -35,7 +35,7 @@ pub trait PathHarness: Filterer {
|
|||
}
|
||||
|
||||
fn path_pass(&self, path: &str, file_type: Option<FileType>, 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
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -34,7 +34,7 @@ pub trait PathHarness: Filterer {
|
|||
}
|
||||
|
||||
fn path_pass(&self, path: &str, file_type: Option<FileType>, 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
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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<PathBuf>, 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<Self, TaggedFiltererError> {
|
||||
pub async fn canonicalised(mut self) -> Result<Self, TaggedFiltererError> {
|
||||
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);
|
||||
&[]
|
||||
|
|
|
@ -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<bool, RuntimeError> {
|
||||
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<PathBuf>,
|
||||
workdir: impl Into<PathBuf>,
|
||||
) -> Result<Arc<Self>, 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<Arc<Self>, 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::<Result<Vec<_>, _>>()?;
|
||||
#[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::<Vec<_>>()
|
||||
.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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<T>> {
|
||||
pub fn change(&self, f: impl FnOnce(&mut T)) -> Result<(), SendError<T>> {
|
||||
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<T>> {
|
||||
pub fn replace(&self, new: T) -> Result<(), SendError<T>> {
|
||||
self.s.send(new)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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<FileType>, 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<TaggedFilterer> {
|
||||
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<TaggedFilterer> {
|
||||
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<Path>) -> Self {
|
||||
let origin = dunce::canonicalize(".").unwrap();
|
||||
let origin = std::fs::canonicalize(".").unwrap();
|
||||
self.in_path = Some(origin.join(sub));
|
||||
self
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<Path>) -> (Vec<IgnoreFile>, Vec<Error>) {
|
||||
///
|
||||
/// ## Async
|
||||
///
|
||||
/// This future is not `Send` due to [`git_config`] internals.
|
||||
#[allow(clippy::future_not_send)]
|
||||
pub async fn from_origin(path: impl AsRef<Path> + Send) -> (Vec<IgnoreFile>, Vec<Error>) {
|
||||
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<Path>) -> (Vec<IgnoreFile>, Vec<Error>
|
|||
)),
|
||||
Some(Err(err)) => errors.push(Error::new(ErrorKind::Other, err)),
|
||||
Some(Ok(config)) => {
|
||||
if let Ok(excludes) = config.value::<GitPath<'_>>("core", None, "excludesFile") {
|
||||
let config_excludes = config.value::<GitPath<'_>>("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<Path>) -> (Vec<IgnoreFile>, Vec<Error>
|
|||
/// 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<IgnoreFile>, Vec<Error>) {
|
||||
let mut files = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
@ -213,7 +224,8 @@ pub async fn from_environment(appname: Option<&str>) -> (Vec<IgnoreFile>, Vec<Er
|
|||
Err(err) => 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::<GitPath<'_>>("core", None, "excludesFile") {
|
||||
let config_excludes = config.value::<GitPath<'_>>("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<IgnoreFile>, Vec<Er
|
|||
/// Utility function to handle looking for an ignore file and adding it to a list if found.
|
||||
///
|
||||
/// This is mostly an internal function, but it is exposed for other filterers to use.
|
||||
#[allow(clippy::future_not_send)]
|
||||
#[tracing::instrument(skip(files, errors), level = "trace")]
|
||||
#[inline]
|
||||
pub async fn discover_file(
|
||||
files: &mut Vec<IgnoreFile>,
|
||||
|
@ -322,7 +336,6 @@ pub async fn discover_file(
|
|||
applies_to: Option<ProjectType>,
|
||||
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<Self, Error> {
|
||||
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));
|
||||
|
|
|
@ -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<Path>, files: &[IgnoreFile]) -> Result<Self, Error> {
|
||||
pub async fn new(origin: impl AsRef<Path> + Send, files: &[IgnoreFile]) -> Result<Self, Error> {
|
||||
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<PathBuf>,
|
||||
) -> 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 })?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
|
||||
if sigs.iter().any(|sig| sig == &MainSignal::Interrupt) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -79,7 +79,7 @@ impl OutcomeWorker {
|
|||
});
|
||||
}
|
||||
|
||||
async fn check_gen<O>(&self, f: impl Future<Output = O>) -> Option<O> {
|
||||
async fn check_gen<O>(&self, f: impl Future<Output = O> + Send) -> Option<O> {
|
||||
// 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) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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::<Vec<_>>().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");
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub enum Process {
|
|||
impl Default for Process {
|
||||
/// Returns [`Process::None`].
|
||||
fn default() -> Self {
|
||||
Process::None
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<ExitStatus> 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:?})")?,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,6 @@ impl Filterer for () {
|
|||
|
||||
impl<T: Filterer> Filterer for Arc<T> {
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
||||
Arc::as_ref(self).check_event(event, priority)
|
||||
Self::as_ref(self).check_event(event, priority)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Path> 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<notify::Event, notify::Error>| {
|
||||
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<notify::Event, notify::Error>,
|
||||
kind: Watcher,
|
||||
n_events: priority::Sender<Event, Priority>,
|
||||
n_events: &priority::Sender<Event, Priority>,
|
||||
) -> 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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -100,8 +100,12 @@ pub trait Handler<T> {
|
|||
///
|
||||
/// Internally this is a Tokio [`Mutex`].
|
||||
pub struct HandlerLock<T>(Arc<Mutex<Box<dyn Handler<T> + Send>>>);
|
||||
impl<T> HandlerLock<T> {
|
||||
impl<T> HandlerLock<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
/// Wrap a [`Handler`] into a lock.
|
||||
#[must_use]
|
||||
pub fn new(handler: Box<dyn Handler<T> + Send>) -> Self {
|
||||
Self(Arc::new(Mutex::new(handler)))
|
||||
}
|
||||
|
@ -125,13 +129,16 @@ impl<T> Clone for HandlerLock<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Default for HandlerLock<T> {
|
||||
impl<T> Default for HandlerLock<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(Box::new(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rte(ctx: &'static str, err: Box<dyn Error>) -> 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<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
writeln!(self.0, "{}", data).map_err(|e| Box::new(e) as _)
|
||||
writeln!(self.0, "{data}").map_err(|e| Box::new(e) as _)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<NixSignal> {
|
||||
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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<Path>) -> HashSet<PathBuf> {
|
||||
let mut origins = HashSet::new();
|
||||
|
||||
fn check_list(list: DirList) -> bool {
|
||||
pub async fn origins(path: impl AsRef<Path> + Send) -> HashSet<PathBuf> {
|
||||
fn check_list(list: &DirList) -> bool {
|
||||
if list.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
@ -252,14 +252,17 @@ pub async fn origins(path: impl AsRef<Path>) -> HashSet<PathBuf> {
|
|||
.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<Path>) -> HashSet<PathBuf> {
|
|||
///
|
||||
/// 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<Path>) -> HashSet<ProjectType> {
|
||||
let list = DirList::obtain(path.as_ref()).await;
|
||||
pub async fn types(path: impl AsRef<Path> + Send) -> HashSet<ProjectType> {
|
||||
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<Path>) -> 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<Path>) -> 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]
|
||||
|
|
Loading…
Reference in New Issue