forked from openkylin/rust-which
Import Upstream version 4.2.5
This commit is contained in:
commit
349ba27a05
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"git": {
|
||||
"sha1": "ca9cc93b392fa1d0eb4eb2a60c74b3b3ecff03bb"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
name: Main workflow
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
# Run the `rustfmt` code formatter
|
||||
rustfmt:
|
||||
name: Rustfmt [Formatter]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup | Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
|
||||
- name: Build | Format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
# Run the `clippy` linting tool
|
||||
clippy:
|
||||
name: Clippy [Linter]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup | Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: clippy
|
||||
|
||||
- name: Build | Lint
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --all-features -- -Dwarnings
|
||||
|
||||
# Ensure that the project could be successfully compiled
|
||||
cargo_check:
|
||||
name: Compile
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup | Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Build | Check
|
||||
run: cargo check --workspace
|
||||
|
||||
# Run tests on Linux, macOS, and Windows
|
||||
# On both Rust stable and Rust nightly
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: cargo_check # First check then run expansive tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
rust: [stable, nightly]
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup | Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
# Run the ignored tests that expect the above setup
|
||||
- name: Build | Test
|
||||
run: cargo test --workspace --all-features -- -Z unstable-options --include-ignored
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
Cargo.lock
|
||||
.vscode/
|
|
@ -0,0 +1,38 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "which"
|
||||
version = "4.2.5"
|
||||
authors = ["Harry Fei <tiziyuanfang@gmail.com>"]
|
||||
description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms."
|
||||
documentation = "https://docs.rs/which/"
|
||||
readme = "README.md"
|
||||
keywords = ["which", "which-rs", "unix", "command"]
|
||||
categories = ["os", "filesystem"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/harryfei/which-rs.git"
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
[dependencies.either]
|
||||
version = "1.6.1"
|
||||
|
||||
[dependencies.libc]
|
||||
version = "0.2.121"
|
||||
|
||||
[dependencies.regex]
|
||||
version = "1.5.5"
|
||||
optional = true
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3.3.0"
|
||||
[target."cfg(windows)".dependencies.lazy_static]
|
||||
version = "1.4.0"
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "which"
|
||||
version = "4.2.5"
|
||||
edition = "2018"
|
||||
authors = ["Harry Fei <tiziyuanfang@gmail.com>"]
|
||||
repository = "https://github.com/harryfei/which-rs.git"
|
||||
documentation = "https://docs.rs/which/"
|
||||
license = "MIT"
|
||||
description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms."
|
||||
readme = "README.md"
|
||||
categories = ["os", "filesystem"]
|
||||
keywords = ["which", "which-rs", "unix", "command"]
|
||||
|
||||
[dependencies]
|
||||
either = "1.6.1"
|
||||
libc = "0.2.121"
|
||||
regex = { version = "1.5.5", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015 fangyuanziti
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,35 @@
|
|||
[](https://github.com/harryfei/which-rs/actions/workflows/rust.yml)
|
||||
|
||||
# which
|
||||
|
||||
A Rust equivalent of Unix command "which". Locate installed executable in cross platforms.
|
||||
|
||||
## Support platforms
|
||||
|
||||
* Linux
|
||||
* Windows
|
||||
* macOS
|
||||
|
||||
## Examples
|
||||
|
||||
1) To find which rustc executable binary is using.
|
||||
|
||||
``` rust
|
||||
use which::which;
|
||||
|
||||
let result = which("rustc").unwrap();
|
||||
assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
|
||||
```
|
||||
|
||||
2. After enabling the `regex` feature, find all cargo subcommand executables on the path:
|
||||
|
||||
``` rust
|
||||
use which::which_re;
|
||||
|
||||
which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
|
||||
.for_each(|pth| println!("{}", pth.to_string_lossy()));
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation is [available online](https://docs.rs/which/).
|
|
@ -0,0 +1 @@
|
|||
{"package":"5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae","files":{}}
|
|
@ -0,0 +1,44 @@
|
|||
rust-which (4.2.5-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package which 4.2.5 from crates.io using debcargo 2.5.0
|
||||
* Drop tempfile patch, included in this release
|
||||
|
||||
-- James McCoy <jamessan@debian.org> Fri, 05 Aug 2022 21:40:33 -0400
|
||||
|
||||
rust-which (4.2.4-2) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package which 4.2.4 from crates.io using debcargo 2.5.0
|
||||
* Upload to unstable
|
||||
|
||||
-- James McCoy <jamessan@debian.org> Sat, 05 Mar 2022 16:13:04 -0500
|
||||
|
||||
rust-which (4.2.4-1) experimental; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package which 4.2.4 from crates.io using debcargo 2.5.0
|
||||
* Backport patch from upstream to use tempfile crate instead of tempdir
|
||||
|
||||
-- James McCoy <jamessan@debian.org> Fri, 18 Feb 2022 20:50:23 -0500
|
||||
|
||||
rust-which (3.0.0-2) unstable; urgency=medium
|
||||
|
||||
[ Sylvestre Ledru ]
|
||||
* Team upload.
|
||||
* Package which 3.0.0 from crates.io using debcargo 2.2.10
|
||||
|
||||
-- Andrej Shadura <andrewsh@debian.org> Tue, 29 Oct 2019 10:38:05 +0100
|
||||
|
||||
rust-which (3.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package which 3.0.0 from crates.io using debcargo 2.4.0
|
||||
|
||||
-- Daniel Kahn Gillmor <dkg@fifthhorseman.net> Fri, 27 Sep 2019 22:19:21 +0200
|
||||
|
||||
rust-which (2.0.1-1) unstable; urgency=medium
|
||||
|
||||
* Package which 2.0.1 from crates.io using debcargo 2.2.9
|
||||
|
||||
-- Ximin Luo <infinity0@debian.org> Tue, 18 Dec 2018 17:07:59 -0800
|
|
@ -0,0 +1 @@
|
|||
12
|
|
@ -0,0 +1,59 @@
|
|||
Source: rust-which
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-either-1+default-dev (>= 1.6.1-~~) <!nocheck>,
|
||||
librust-lazy-static-1+default-dev (>= 1.4.0-~~) <!nocheck>,
|
||||
librust-libc-0.2+default-dev (>= 0.2.121-~~) <!nocheck>
|
||||
Maintainer: Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
Uploaders:
|
||||
Ximin Luo <infinity0@debian.org>
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/which]
|
||||
Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/which
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: librust-which-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-either-1+default-dev (>= 1.6.1-~~),
|
||||
librust-lazy-static-1+default-dev (>= 1.4.0-~~),
|
||||
librust-libc-0.2+default-dev (>= 0.2.121-~~)
|
||||
Suggests:
|
||||
librust-which+regex-dev (= ${binary:Version})
|
||||
Provides:
|
||||
librust-which+default-dev (= ${binary:Version}),
|
||||
librust-which-4-dev (= ${binary:Version}),
|
||||
librust-which-4+default-dev (= ${binary:Version}),
|
||||
librust-which-4.2-dev (= ${binary:Version}),
|
||||
librust-which-4.2+default-dev (= ${binary:Version}),
|
||||
librust-which-4.2.5-dev (= ${binary:Version}),
|
||||
librust-which-4.2.5+default-dev (= ${binary:Version})
|
||||
Description: Rust equivalent of Unix command "which" - Rust source code
|
||||
Locate installed executable in cross platforms.
|
||||
.
|
||||
This package contains the source for the Rust which crate, packaged by debcargo
|
||||
for use with cargo and dh-cargo.
|
||||
|
||||
Package: librust-which+regex-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-which-dev (= ${binary:Version}),
|
||||
librust-regex-1+default-dev (>= 1.5.5-~~)
|
||||
Provides:
|
||||
librust-which-4+regex-dev (= ${binary:Version}),
|
||||
librust-which-4.2+regex-dev (= ${binary:Version}),
|
||||
librust-which-4.2.5+regex-dev (= ${binary:Version})
|
||||
Description: Rust equivalent of Unix command "which" - feature "regex"
|
||||
Locate installed executable in cross platforms.
|
||||
.
|
||||
This metapackage enables feature "regex" for the Rust which crate, by pulling
|
||||
in any additional dependencies needed by that feature.
|
|
@ -0,0 +1,33 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: which
|
||||
Upstream-Contact: Harry Fei <tiziyuanfang@gmail.com>
|
||||
Source: https://github.com/harryfei/which-rs.git
|
||||
|
||||
Files: *
|
||||
Copyright: 2015-2018 Harry Fei <tiziyuanfang@gmail.com>
|
||||
License: MIT
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2018 Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
2018 Ximin Luo <infinity0@debian.org>
|
||||
License: MIT
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,45 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: which
|
||||
Upstream-Contact: Harry Fei <tiziyuanfang@gmail.com>
|
||||
Source: https://github.com/harryfei/which-rs.git
|
||||
|
||||
Files: *
|
||||
Copyright: FIXME (overlay) UNKNOWN-YEARS Harry Fei <tiziyuanfang@gmail.com>
|
||||
License: MIT
|
||||
Comment:
|
||||
FIXME (overlay): Since upstream copyright years are not available in
|
||||
Cargo.toml, they were extracted from the upstream Git repository. This may not
|
||||
be correct information so you should review and fix this before uploading to
|
||||
the archive.
|
||||
|
||||
Files: ./LICENSE.txt
|
||||
Copyright: 2015 fangyuanziti
|
||||
License: UNKNOWN-LICENSE; FIXME (overlay)
|
||||
Comment:
|
||||
FIXME (overlay): These notices are extracted from files. Please review them
|
||||
before uploading to the archive.
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2018-2022 Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
2018-2022 Ximin Luo <infinity0@debian.org>
|
||||
License: MIT
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
overlay = "."
|
||||
uploaders = ["Ximin Luo <infinity0@debian.org>"]
|
|
@ -0,0 +1 @@
|
|||
librust-which+regex-dev binary: empty-rust-library-declares-provides *
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@ --buildsystem cargo
|
|
@ -0,0 +1 @@
|
|||
3.0 (quilt)
|
|
@ -0,0 +1,19 @@
|
|||
Test-Command: /usr/share/cargo/bin/cargo-auto-test which 4.2.5 --all-targets --all-features
|
||||
Features: test-name=rust-which:@
|
||||
Depends: dh-cargo (>= 18), librust-tempfile-3+default-dev (>= 3.3.0-~~), @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test which 4.2.5 --all-targets
|
||||
Features: test-name=librust-which-dev:default
|
||||
Depends: dh-cargo (>= 18), librust-tempfile-3+default-dev (>= 3.3.0-~~), @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test which 4.2.5 --all-targets --no-default-features
|
||||
Features: test-name=librust-which-dev:
|
||||
Depends: dh-cargo (>= 18), librust-tempfile-3+default-dev (>= 3.3.0-~~), @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test which 4.2.5 --all-targets --no-default-features --features regex
|
||||
Features: test-name=librust-which+regex-dev:regex
|
||||
Depends: dh-cargo (>= 18), librust-tempfile-3+default-dev (>= 3.3.0-~~), @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
|
@ -0,0 +1,4 @@
|
|||
version=4
|
||||
opts=filenamemangle=s/.*\/(.*)\/download/which-$1\.tar\.gz/g,\
|
||||
uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/ \
|
||||
https://qa.debian.org/cgi-bin/fakeupstream.cgi?upstream=crates.io/which .*/crates/which/@ANY_VERSION@/download
|
|
@ -0,0 +1,79 @@
|
|||
use crate::finder::Checker;
|
||||
#[cfg(unix)]
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct ExecutableChecker;
|
||||
|
||||
impl ExecutableChecker {
|
||||
pub fn new() -> ExecutableChecker {
|
||||
ExecutableChecker
|
||||
}
|
||||
}
|
||||
|
||||
impl Checker for ExecutableChecker {
|
||||
#[cfg(unix)]
|
||||
fn is_valid(&self, path: &Path) -> bool {
|
||||
CString::new(path.as_os_str().as_bytes())
|
||||
.map(|c| unsafe { libc::access(c.as_ptr(), libc::X_OK) == 0 })
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_valid(&self, _path: &Path) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExistedChecker;
|
||||
|
||||
impl ExistedChecker {
|
||||
pub fn new() -> ExistedChecker {
|
||||
ExistedChecker
|
||||
}
|
||||
}
|
||||
|
||||
impl Checker for ExistedChecker {
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_valid(&self, path: &Path) -> bool {
|
||||
fs::symlink_metadata(path)
|
||||
.map(|metadata| {
|
||||
let file_type = metadata.file_type();
|
||||
file_type.is_file() || file_type.is_symlink()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn is_valid(&self, path: &Path) -> bool {
|
||||
fs::metadata(path)
|
||||
.map(|metadata| metadata.is_file())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompositeChecker {
|
||||
checkers: Vec<Box<dyn Checker>>,
|
||||
}
|
||||
|
||||
impl CompositeChecker {
|
||||
pub fn new() -> CompositeChecker {
|
||||
CompositeChecker {
|
||||
checkers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_checker(mut self, checker: Box<dyn Checker>) -> CompositeChecker {
|
||||
self.checkers.push(checker);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Checker for CompositeChecker {
|
||||
fn is_valid(&self, path: &Path) -> bool {
|
||||
self.checkers.iter().all(|checker| checker.is_valid(path))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
use std::fmt;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Error {
|
||||
BadAbsolutePath,
|
||||
BadRelativePath,
|
||||
CannotFindBinaryPath,
|
||||
CannotGetCurrentDir,
|
||||
CannotCanonicalize,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadAbsolutePath => write!(f, "bad absolute path"),
|
||||
Error::BadRelativePath => write!(f, "bad relative path"),
|
||||
Error::CannotFindBinaryPath => write!(f, "cannot find binary path"),
|
||||
Error::CannotGetCurrentDir => write!(f, "cannot get current directory"),
|
||||
Error::CannotCanonicalize => write!(f, "cannot canonicalize path"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
use crate::checker::CompositeChecker;
|
||||
use crate::error::*;
|
||||
#[cfg(windows)]
|
||||
use crate::helper::has_executable_extension;
|
||||
use either::Either;
|
||||
#[cfg(feature = "regex")]
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "regex")]
|
||||
use std::borrow::Borrow;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
#[cfg(feature = "regex")]
|
||||
use std::fs;
|
||||
use std::iter;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait Checker {
|
||||
fn is_valid(&self, path: &Path) -> bool;
|
||||
}
|
||||
|
||||
trait PathExt {
|
||||
fn has_separator(&self) -> bool;
|
||||
|
||||
fn to_absolute<P>(self, cwd: P) -> PathBuf
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
}
|
||||
|
||||
impl PathExt for PathBuf {
|
||||
fn has_separator(&self) -> bool {
|
||||
self.components().count() > 1
|
||||
}
|
||||
|
||||
fn to_absolute<P>(self, cwd: P) -> PathBuf
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if self.is_absolute() {
|
||||
self
|
||||
} else {
|
||||
let mut new_path = PathBuf::from(cwd.as_ref());
|
||||
new_path.push(self);
|
||||
new_path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Finder;
|
||||
|
||||
impl Finder {
|
||||
pub fn new() -> Finder {
|
||||
Finder
|
||||
}
|
||||
|
||||
pub fn find<T, U, V>(
|
||||
&self,
|
||||
binary_name: T,
|
||||
paths: Option<U>,
|
||||
cwd: Option<V>,
|
||||
binary_checker: CompositeChecker,
|
||||
) -> Result<impl Iterator<Item = PathBuf>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<Path>,
|
||||
{
|
||||
let path = PathBuf::from(&binary_name);
|
||||
|
||||
let binary_path_candidates = match cwd {
|
||||
Some(cwd) if path.has_separator() => {
|
||||
// Search binary in cwd if the path have a path separator.
|
||||
Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
|
||||
}
|
||||
_ => {
|
||||
// Search binary in PATHs(defined in environment variable).
|
||||
let p = paths.ok_or(Error::CannotFindBinaryPath)?;
|
||||
let paths: Vec<_> = env::split_paths(&p).collect();
|
||||
|
||||
Either::Right(Self::path_search_candidates(path, paths).into_iter())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(binary_path_candidates.filter(move |p| binary_checker.is_valid(p)))
|
||||
}
|
||||
|
||||
#[cfg(feature = "regex")]
|
||||
pub fn find_re<T>(
|
||||
&self,
|
||||
binary_regex: impl Borrow<Regex>,
|
||||
paths: Option<T>,
|
||||
binary_checker: CompositeChecker,
|
||||
) -> Result<impl Iterator<Item = PathBuf>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
{
|
||||
let p = paths.ok_or(Error::CannotFindBinaryPath)?;
|
||||
// Collect needs to happen in order to not have to
|
||||
// change the API to borrow on `paths`.
|
||||
#[allow(clippy::needless_collect)]
|
||||
let paths: Vec<_> = env::split_paths(&p).collect();
|
||||
|
||||
let matching_re = paths
|
||||
.into_iter()
|
||||
.flat_map(fs::read_dir)
|
||||
.flatten()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(move |p| {
|
||||
if let Some(unicode_file_name) = p.file_name().unwrap().to_str() {
|
||||
binary_regex.borrow().is_match(unicode_file_name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.filter(move |p| binary_checker.is_valid(p));
|
||||
|
||||
Ok(matching_re)
|
||||
}
|
||||
|
||||
fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>
|
||||
where
|
||||
C: AsRef<Path>,
|
||||
{
|
||||
let path = binary_name.to_absolute(cwd);
|
||||
|
||||
Self::append_extension(iter::once(path))
|
||||
}
|
||||
|
||||
fn path_search_candidates<P>(
|
||||
binary_name: PathBuf,
|
||||
paths: P,
|
||||
) -> impl IntoIterator<Item = PathBuf>
|
||||
where
|
||||
P: IntoIterator<Item = PathBuf>,
|
||||
{
|
||||
let new_paths = paths.into_iter().map(move |p| p.join(binary_name.clone()));
|
||||
|
||||
Self::append_extension(new_paths)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
|
||||
where
|
||||
P: IntoIterator<Item = PathBuf>,
|
||||
{
|
||||
paths
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
|
||||
where
|
||||
P: IntoIterator<Item = PathBuf>,
|
||||
{
|
||||
// Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
|
||||
// PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …].
|
||||
// (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it;
|
||||
// hence its retention.)
|
||||
lazy_static! {
|
||||
static ref PATH_EXTENSIONS: Vec<String> =
|
||||
env::var("PATHEXT")
|
||||
.map(|pathext| {
|
||||
pathext.split(';')
|
||||
.filter_map(|s| {
|
||||
if s.as_bytes().first() == Some(&b'.') {
|
||||
Some(s.to_owned())
|
||||
} else {
|
||||
// Invalid segment; just ignore it.
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
// PATHEXT not being set or not being a proper Unicode string is exceedingly
|
||||
// improbable and would probably break Windows badly. Still, don't crash:
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
paths
|
||||
.into_iter()
|
||||
.flat_map(move |p| -> Box<dyn Iterator<Item = _>> {
|
||||
// Check if path already have executable extension
|
||||
if has_executable_extension(&p, &PATH_EXTENSIONS) {
|
||||
Box::new(iter::once(p))
|
||||
} else {
|
||||
// Appended paths with windows executable extensions.
|
||||
// e.g. path `c:/windows/bin` will expend to:
|
||||
// c:/windows/bin.COM
|
||||
// c:/windows/bin.EXE
|
||||
// c:/windows/bin.CMD
|
||||
// ...
|
||||
Box::new(PATH_EXTENSIONS.iter().map(move |e| {
|
||||
// Append the extension.
|
||||
let mut p = p.clone().into_os_string();
|
||||
p.push(e);
|
||||
|
||||
PathBuf::from(p)
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use std::path::Path;
|
||||
|
||||
/// Check if given path has extension which in the given vector.
|
||||
pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, pathext: &[S]) -> bool {
|
||||
let ext = path.as_ref().extension().and_then(|e| e.to_str());
|
||||
match ext {
|
||||
Some(ext) => pathext
|
||||
.iter()
|
||||
.any(|e| ext.eq_ignore_ascii_case(&e.as_ref()[1..])),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_extension_in_extension_vector() {
|
||||
// Case insensitive
|
||||
assert!(has_executable_extension(
|
||||
PathBuf::from("foo.exe"),
|
||||
&[".COM", ".EXE", ".CMD"]
|
||||
));
|
||||
|
||||
assert!(has_executable_extension(
|
||||
PathBuf::from("foo.CMD"),
|
||||
&[".COM", ".EXE", ".CMD"]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extension_not_in_extension_vector() {
|
||||
assert!(!has_executable_extension(
|
||||
PathBuf::from("foo.bar"),
|
||||
&[".COM", ".EXE", ".CMD"]
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,430 @@
|
|||
//! which
|
||||
//!
|
||||
//! A Rust equivalent of Unix command `which(1)`.
|
||||
//! # Example:
|
||||
//!
|
||||
//! To find which rustc executable binary is using:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use which::which;
|
||||
//! use std::path::PathBuf;
|
||||
//!
|
||||
//! let result = which("rustc").unwrap();
|
||||
//! assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
|
||||
//!
|
||||
//! ```
|
||||
|
||||
#[cfg(windows)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod checker;
|
||||
mod error;
|
||||
mod finder;
|
||||
#[cfg(windows)]
|
||||
mod helper;
|
||||
|
||||
#[cfg(feature = "regex")]
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "regex")]
|
||||
use std::borrow::Borrow;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::path;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use crate::checker::{CompositeChecker, ExecutableChecker, ExistedChecker};
|
||||
pub use crate::error::*;
|
||||
use crate::finder::Finder;
|
||||
|
||||
/// Find a exectable binary's path by name.
|
||||
///
|
||||
/// If given an absolute path, returns it if the file exists and is executable.
|
||||
///
|
||||
/// If given a relative path, returns an absolute path to the file if
|
||||
/// it exists and is executable.
|
||||
///
|
||||
/// If given a string without path separators, looks for a file named
|
||||
/// `binary_name` at each directory in `$PATH` and if it finds an executable
|
||||
/// file there, returns it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use which::which;
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// let result = which::which("rustc").unwrap();
|
||||
/// assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
|
||||
///
|
||||
/// ```
|
||||
pub fn which<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> {
|
||||
which_all(binary_name).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
|
||||
}
|
||||
|
||||
/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
|
||||
pub fn which_all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = path::PathBuf>> {
|
||||
let cwd = env::current_dir().ok();
|
||||
|
||||
let binary_checker = build_binary_checker();
|
||||
|
||||
let finder = Finder::new();
|
||||
|
||||
finder.find(binary_name, env::var_os("PATH"), cwd, binary_checker)
|
||||
}
|
||||
|
||||
/// Find all binaries matching a regular expression in a the system PATH.
|
||||
///
|
||||
/// Only available when feature `regex` is enabled.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `regex` - A regular expression to match binaries with
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Find Python executables:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use regex::Regex;
|
||||
/// use which::which;
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// let re = Regex::new(r"python\d$").unwrap();
|
||||
/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect();
|
||||
/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
|
||||
/// assert_eq!(binaries, python_paths);
|
||||
/// ```
|
||||
///
|
||||
/// Find all cargo subcommand executables on the path:
|
||||
///
|
||||
/// ```
|
||||
/// use which::which_re;
|
||||
/// use regex::Regex;
|
||||
///
|
||||
/// which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
|
||||
/// .for_each(|pth| println!("{}", pth.to_string_lossy()));
|
||||
/// ```
|
||||
#[cfg(feature = "regex")]
|
||||
pub fn which_re(regex: impl Borrow<Regex>) -> Result<impl Iterator<Item = path::PathBuf>> {
|
||||
which_re_in(regex, env::var_os("PATH"))
|
||||
}
|
||||
|
||||
/// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
|
||||
pub fn which_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<path::PathBuf>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
which_in_all(binary_name, paths, cwd)
|
||||
.and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
|
||||
}
|
||||
|
||||
/// Find all binaries matching a regular expression in a list of paths.
|
||||
///
|
||||
/// Only available when feature `regex` is enabled.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `regex` - A regular expression to match binaries with
|
||||
/// * `paths` - A string containing the paths to search
|
||||
/// (separated in the same way as the PATH environment variable)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use regex::Regex;
|
||||
/// use which::which;
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// let re = Regex::new(r"python\d$").unwrap();
|
||||
/// let paths = Some("/usr/bin:/usr/local/bin");
|
||||
/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect();
|
||||
/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
|
||||
/// assert_eq!(binaries, python_paths);
|
||||
/// ```
|
||||
#[cfg(feature = "regex")]
|
||||
pub fn which_re_in<T>(
|
||||
regex: impl Borrow<Regex>,
|
||||
paths: Option<T>,
|
||||
) -> Result<impl Iterator<Item = path::PathBuf>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
{
|
||||
let binary_checker = build_binary_checker();
|
||||
|
||||
let finder = Finder::new();
|
||||
|
||||
finder.find_re(regex, paths, binary_checker)
|
||||
}
|
||||
|
||||
/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
|
||||
pub fn which_in_all<T, U, V>(
|
||||
binary_name: T,
|
||||
paths: Option<U>,
|
||||
cwd: V,
|
||||
) -> Result<impl Iterator<Item = path::PathBuf>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
let binary_checker = build_binary_checker();
|
||||
|
||||
let finder = Finder::new();
|
||||
|
||||
finder.find(binary_name, paths, Some(cwd), binary_checker)
|
||||
}
|
||||
|
||||
fn build_binary_checker() -> CompositeChecker {
|
||||
CompositeChecker::new()
|
||||
.add_checker(Box::new(ExistedChecker::new()))
|
||||
.add_checker(Box::new(ExecutableChecker::new()))
|
||||
}
|
||||
|
||||
/// An owned, immutable wrapper around a `PathBuf` containing the path of an executable.
|
||||
///
|
||||
/// The constructed `PathBuf` is the output of `which` or `which_in`, but `which::Path` has the
|
||||
/// advantage of being a type distinct from `std::path::Path` and `std::path::PathBuf`.
|
||||
///
|
||||
/// It can be beneficial to use `which::Path` instead of `std::path::Path` when you want the type
|
||||
/// system to enforce the need for a path that exists and points to a binary that is executable.
|
||||
///
|
||||
/// Since `which::Path` implements `Deref` for `std::path::Path`, all methods on `&std::path::Path`
|
||||
/// are also available to `&which::Path` values.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Path {
|
||||
inner: path::PathBuf,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Returns the path of an executable binary by name.
|
||||
///
|
||||
/// This calls `which` and maps the result into a `Path`.
|
||||
pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<Path> {
|
||||
which(binary_name).map(|inner| Path { inner })
|
||||
}
|
||||
|
||||
/// Returns the paths of all executable binaries by a name.
|
||||
///
|
||||
/// this calls `which_all` and maps the results into `Path`s.
|
||||
pub fn all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = Path>> {
|
||||
which_all(binary_name).map(|inner| inner.map(|inner| Path { inner }))
|
||||
}
|
||||
|
||||
/// Returns the path of an executable binary by name in the path list `paths` and using the
|
||||
/// current working directory `cwd` to resolve relative paths.
|
||||
///
|
||||
/// This calls `which_in` and maps the result into a `Path`.
|
||||
pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<Path>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
which_in(binary_name, paths, cwd).map(|inner| Path { inner })
|
||||
}
|
||||
|
||||
/// Returns all paths of an executable binary by name in the path list `paths` and using the
|
||||
/// current working directory `cwd` to resolve relative paths.
|
||||
///
|
||||
/// This calls `which_in_all` and maps the results into a `Path`.
|
||||
pub fn all_in<T, U, V>(
|
||||
binary_name: T,
|
||||
paths: Option<U>,
|
||||
cwd: V,
|
||||
) -> Result<impl Iterator<Item = Path>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
which_in_all(binary_name, paths, cwd).map(|inner| inner.map(|inner| Path { inner }))
|
||||
}
|
||||
|
||||
/// Returns a reference to a `std::path::Path`.
|
||||
pub fn as_path(&self) -> &path::Path {
|
||||
self.inner.as_path()
|
||||
}
|
||||
|
||||
/// Consumes the `which::Path`, yielding its underlying `std::path::PathBuf`.
|
||||
pub fn into_path_buf(self) -> path::PathBuf {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Path {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Path {
|
||||
type Target = path::Path;
|
||||
|
||||
fn deref(&self) -> &path::Path {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<path::Path> for Path {
|
||||
fn as_ref(&self) -> &path::Path {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for Path {
|
||||
fn as_ref(&self) -> &OsStr {
|
||||
self.as_os_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Path {}
|
||||
|
||||
impl PartialEq<path::PathBuf> for Path {
|
||||
fn eq(&self, other: &path::PathBuf) -> bool {
|
||||
self.inner == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Path> for path::PathBuf {
|
||||
fn eq(&self, other: &Path) -> bool {
|
||||
*self == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned, immutable wrapper around a `PathBuf` containing the _canonical_ path of an
|
||||
/// executable.
|
||||
///
|
||||
/// The constructed `PathBuf` is the result of `which` or `which_in` followed by
|
||||
/// `Path::canonicalize`, but `CanonicalPath` has the advantage of being a type distinct from
|
||||
/// `std::path::Path` and `std::path::PathBuf`.
|
||||
///
|
||||
/// It can be beneficial to use `CanonicalPath` instead of `std::path::Path` when you want the type
|
||||
/// system to enforce the need for a path that exists, points to a binary that is executable, is
|
||||
/// absolute, has all components normalized, and has all symbolic links resolved
|
||||
///
|
||||
/// Since `CanonicalPath` implements `Deref` for `std::path::Path`, all methods on
|
||||
/// `&std::path::Path` are also available to `&CanonicalPath` values.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CanonicalPath {
|
||||
inner: path::PathBuf,
|
||||
}
|
||||
|
||||
impl CanonicalPath {
|
||||
/// Returns the canonical path of an executable binary by name.
|
||||
///
|
||||
/// This calls `which` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
|
||||
pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<CanonicalPath> {
|
||||
which(binary_name)
|
||||
.and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
|
||||
.map(|inner| CanonicalPath { inner })
|
||||
}
|
||||
|
||||
/// Returns the canonical paths of an executable binary by name.
|
||||
///
|
||||
/// This calls `which_all` and `Path::canonicalize` and maps the results into `CanonicalPath`s.
|
||||
pub fn all<T: AsRef<OsStr>>(
|
||||
binary_name: T,
|
||||
) -> Result<impl Iterator<Item = Result<CanonicalPath>>> {
|
||||
which_all(binary_name).map(|inner| {
|
||||
inner.map(|inner| {
|
||||
inner
|
||||
.canonicalize()
|
||||
.map_err(|_| Error::CannotCanonicalize)
|
||||
.map(|inner| CanonicalPath { inner })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the canonical path of an executable binary by name in the path list `paths` and
|
||||
/// using the current working directory `cwd` to resolve relative paths.
|
||||
///
|
||||
/// This calls `which_in` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
|
||||
pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<CanonicalPath>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
which_in(binary_name, paths, cwd)
|
||||
.and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
|
||||
.map(|inner| CanonicalPath { inner })
|
||||
}
|
||||
|
||||
/// Returns all of the canonical paths of an executable binary by name in the path list `paths` and
|
||||
/// using the current working directory `cwd` to resolve relative paths.
|
||||
///
|
||||
/// This calls `which_in_all` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
|
||||
pub fn all_in<T, U, V>(
|
||||
binary_name: T,
|
||||
paths: Option<U>,
|
||||
cwd: V,
|
||||
) -> Result<impl Iterator<Item = Result<CanonicalPath>>>
|
||||
where
|
||||
T: AsRef<OsStr>,
|
||||
U: AsRef<OsStr>,
|
||||
V: AsRef<path::Path>,
|
||||
{
|
||||
which_in_all(binary_name, paths, cwd).map(|inner| {
|
||||
inner.map(|inner| {
|
||||
inner
|
||||
.canonicalize()
|
||||
.map_err(|_| Error::CannotCanonicalize)
|
||||
.map(|inner| CanonicalPath { inner })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to a `std::path::Path`.
|
||||
pub fn as_path(&self) -> &path::Path {
|
||||
self.inner.as_path()
|
||||
}
|
||||
|
||||
/// Consumes the `which::CanonicalPath`, yielding its underlying `std::path::PathBuf`.
|
||||
pub fn into_path_buf(self) -> path::PathBuf {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CanonicalPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CanonicalPath {
|
||||
type Target = path::Path;
|
||||
|
||||
fn deref(&self) -> &path::Path {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<path::Path> for CanonicalPath {
|
||||
fn as_ref(&self) -> &path::Path {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for CanonicalPath {
|
||||
fn as_ref(&self) -> &OsStr {
|
||||
self.as_os_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CanonicalPath {}
|
||||
|
||||
impl PartialEq<path::PathBuf> for CanonicalPath {
|
||||
fn eq(&self, other: &path::PathBuf) -> bool {
|
||||
self.inner == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<CanonicalPath> for path::PathBuf {
|
||||
fn eq(&self, other: &CanonicalPath) -> bool {
|
||||
*self == other.inner
|
||||
}
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
extern crate which;
|
||||
|
||||
#[cfg(all(unix, feature = "regex"))]
|
||||
use regex::Regex;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, vec};
|
||||
use tempfile::TempDir;
|
||||
|
||||
struct TestFixture {
|
||||
/// Temp directory.
|
||||
pub tempdir: TempDir,
|
||||
/// $PATH
|
||||
pub paths: OsString,
|
||||
/// Binaries created in $PATH
|
||||
pub bins: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
const SUBDIRS: &[&str] = &["a", "b", "c"];
|
||||
const BIN_NAME: &str = "bin";
|
||||
|
||||
#[cfg(unix)]
|
||||
fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
let bin = dir.join(path).with_extension(extension);
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.mode(0o666 | (libc::S_IXUSR as u32))
|
||||
.open(&bin)
|
||||
.and_then(|_f| bin.canonicalize())
|
||||
}
|
||||
|
||||
fn touch(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
|
||||
let b = dir.join(path).with_extension(extension);
|
||||
fs::File::create(&b).and_then(|_f| b.canonicalize())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
|
||||
touch(dir, path, extension)
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
// tmp/a/bin
|
||||
// tmp/a/bin.exe
|
||||
// tmp/a/bin.cmd
|
||||
// tmp/b/bin
|
||||
// tmp/b/bin.exe
|
||||
// tmp/b/bin.cmd
|
||||
// tmp/c/bin
|
||||
// tmp/c/bin.exe
|
||||
// tmp/c/bin.cmd
|
||||
pub fn new() -> TestFixture {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let mut builder = fs::DirBuilder::new();
|
||||
builder.recursive(true);
|
||||
let mut paths = vec![];
|
||||
let mut bins = vec![];
|
||||
for d in SUBDIRS.iter() {
|
||||
let p = tempdir.path().join(d);
|
||||
builder.create(&p).unwrap();
|
||||
bins.push(mk_bin(&p, BIN_NAME, "").unwrap());
|
||||
bins.push(mk_bin(&p, BIN_NAME, "exe").unwrap());
|
||||
bins.push(mk_bin(&p, BIN_NAME, "cmd").unwrap());
|
||||
paths.push(p);
|
||||
}
|
||||
TestFixture {
|
||||
tempdir,
|
||||
paths: env::join_paths(paths).unwrap(),
|
||||
bins,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn touch(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
|
||||
touch(self.tempdir.path(), path, extension)
|
||||
}
|
||||
|
||||
pub fn mk_bin(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
|
||||
mk_bin(self.tempdir.path(), path, extension)
|
||||
}
|
||||
}
|
||||
|
||||
fn _which<T: AsRef<OsStr>>(f: &TestFixture, path: T) -> which::Result<which::CanonicalPath> {
|
||||
which::CanonicalPath::new_in(path, Some(f.paths.clone()), f.tempdir.path())
|
||||
}
|
||||
|
||||
fn _which_all<'a, T: AsRef<OsStr> + 'a>(
|
||||
f: &'a TestFixture,
|
||||
path: T,
|
||||
) -> which::Result<impl Iterator<Item = which::Result<which::CanonicalPath>> + '_> {
|
||||
which::CanonicalPath::all_in(path, Some(f.paths.clone()), f.tempdir.path())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn it_works() {
|
||||
use std::process::Command;
|
||||
let result = which::Path::new("rustc");
|
||||
assert!(result.is_ok());
|
||||
|
||||
let which_result = Command::new("which").arg("rustc").output();
|
||||
|
||||
assert_eq!(
|
||||
String::from(result.unwrap().to_str().unwrap()),
|
||||
String::from_utf8(which_result.unwrap().stdout)
|
||||
.unwrap()
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[0])
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[1])
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, feature = "regex"))]
|
||||
fn test_which_re_in_with_matches() {
|
||||
let f = TestFixture::new();
|
||||
f.mk_bin("a/bin_0", "").unwrap();
|
||||
f.mk_bin("b/bin_1", "").unwrap();
|
||||
let re = Regex::new(r"bin_\d").unwrap();
|
||||
|
||||
let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let temp = f.tempdir;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![temp.path().join("a/bin_0"), temp.path().join("b/bin_1")]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, feature = "regex"))]
|
||||
fn test_which_re_in_without_matches() {
|
||||
let f = TestFixture::new();
|
||||
let re = Regex::new(r"bi[^n]").unwrap();
|
||||
|
||||
let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
assert_eq!(result, Vec::<PathBuf>::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, feature = "regex"))]
|
||||
fn test_which_re_accepts_owned_and_borrow() {
|
||||
which::which_re(Regex::new(r".").unwrap())
|
||||
.unwrap()
|
||||
.for_each(drop);
|
||||
which::which_re(&Regex::new(r".").unwrap())
|
||||
.unwrap()
|
||||
.for_each(drop);
|
||||
which::which_re_in(Regex::new(r".").unwrap(), Some("pth"))
|
||||
.unwrap()
|
||||
.for_each(drop);
|
||||
which::which_re_in(&Regex::new(r".").unwrap(), Some("pth"))
|
||||
.unwrap()
|
||||
.for_each(drop);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_extension() {
|
||||
let f = TestFixture::new();
|
||||
let b = Path::new(&BIN_NAME).with_extension("");
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[0])
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_extension() {
|
||||
let f = TestFixture::new();
|
||||
let b = Path::new(&BIN_NAME).with_extension("cmd");
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[2])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_which_not_found() {
|
||||
let f = TestFixture::new();
|
||||
assert!(_which(&f, "a").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_which_second() {
|
||||
let f = TestFixture::new();
|
||||
let b = f.mk_bin("b/another", env::consts::EXE_EXTENSION).unwrap();
|
||||
assert_eq!(_which(&f, "another").unwrap(), b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_which_all() {
|
||||
let f = TestFixture::new();
|
||||
let actual = _which_all(&f, BIN_NAME)
|
||||
.unwrap()
|
||||
.map(|c| c.unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let mut expected = f
|
||||
.bins
|
||||
.iter()
|
||||
.map(|p| p.canonicalize().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
#[cfg(windows)]
|
||||
{
|
||||
expected.retain(|p| p.extension().map(|ext| ext == "exe" || ext == "cmd") == Some(true));
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
expected.retain(|p| p.file_name().unwrap() == BIN_NAME);
|
||||
}
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_absolute() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, &f.bins[3]).unwrap(),
|
||||
f.bins[3].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_absolute() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, &f.bins[4]).unwrap(),
|
||||
f.bins[4].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_absolute_path_case() {
|
||||
// Test that an absolute path with an uppercase extension
|
||||
// is accepted.
|
||||
let f = TestFixture::new();
|
||||
let p = &f.bins[4];
|
||||
assert_eq!(_which(&f, &p).unwrap(), f.bins[4].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_absolute_extension() {
|
||||
let f = TestFixture::new();
|
||||
// Don't append EXE_EXTENSION here.
|
||||
let b = f.bins[3].parent().unwrap().join(&BIN_NAME);
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_absolute_extension() {
|
||||
let f = TestFixture::new();
|
||||
// Don't append EXE_EXTENSION here.
|
||||
let b = f.bins[4].parent().unwrap().join(&BIN_NAME);
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_relative() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, "b/bin").unwrap(),
|
||||
f.bins[3].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_relative() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, "b/bin").unwrap(),
|
||||
f.bins[4].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_relative_extension() {
|
||||
// test_which_relative tests a relative path without an extension,
|
||||
// so test a relative path with an extension here.
|
||||
let f = TestFixture::new();
|
||||
let b = Path::new("b/bin").with_extension(env::consts::EXE_EXTENSION);
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_relative_extension() {
|
||||
// test_which_relative tests a relative path without an extension,
|
||||
// so test a relative path with an extension here.
|
||||
let f = TestFixture::new();
|
||||
let b = Path::new("b/bin").with_extension("cmd");
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[5].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_relative_extension_case() {
|
||||
// Test that a relative path with an uppercase extension
|
||||
// is accepted.
|
||||
let f = TestFixture::new();
|
||||
let b = Path::new("b/bin").with_extension("EXE");
|
||||
assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_relative_leading_dot() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, "./b/bin").unwrap(),
|
||||
f.bins[3].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_which_relative_leading_dot() {
|
||||
let f = TestFixture::new();
|
||||
assert_eq!(
|
||||
_which(&f, "./b/bin").unwrap(),
|
||||
f.bins[4].canonicalize().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_non_executable() {
|
||||
// Shouldn't return non-executable files.
|
||||
let f = TestFixture::new();
|
||||
f.touch("b/another", "").unwrap();
|
||||
assert!(_which(&f, "another").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_absolute_non_executable() {
|
||||
// Shouldn't return non-executable files, even if given an absolute path.
|
||||
let f = TestFixture::new();
|
||||
let b = f.touch("b/another", "").unwrap();
|
||||
assert!(_which(&f, &b).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_which_relative_non_executable() {
|
||||
// Shouldn't return non-executable files.
|
||||
let f = TestFixture::new();
|
||||
f.touch("b/another", "").unwrap();
|
||||
assert!(_which(&f, "b/another").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failure() {
|
||||
let f = TestFixture::new();
|
||||
|
||||
let run = || -> which::Result<PathBuf> {
|
||||
let p = _which(&f, "./b/bin")?;
|
||||
Ok(p.into_path_buf())
|
||||
};
|
||||
|
||||
let _ = run();
|
||||
}
|
Loading…
Reference in New Issue