Skip to content

Commit

Permalink
implement find path with windows executeable extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
WLBF committed Mar 11, 2018
1 parent 3601ed1 commit 78e0291
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 65 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ keywords = ["which", "which-rs", "unix", "command"]
failure = "0.1.1"
libc = "0.2.10"

[target.'cfg(windows)'.dependencies]
lazy_static = "1.0"

[dev-dependencies]
tempdir = "0.3.4"
2 changes: 1 addition & 1 deletion src/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Checker for ExecutableChecker {
.unwrap_or(false)
}

#[cfg(not(unix))]
#[cfg(windows)]
fn is_valid(&self, _path: &Path) -> bool {
true
}
Expand Down
79 changes: 58 additions & 21 deletions src/finder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use helper::ensure_exe_extension;
use error::*;
use helper::check_extension;

#[cfg(windows)]
lazy_static! {
static ref EXE_EXTENSION_VEC: Vec<String> = {
// Read PATHEXT env variable and split it into vector of String
let path_exts = env::var_os("PATHEXT").unwrap_or(OsString::from(env::consts::EXE_EXTENSION));
env::split_paths(&path_exts)
.map(|e| e.to_str().map(|e| e.to_owned()))
.filter_map(|e| e).collect::<Vec<_>>()
};
}

pub trait Checker {
fn is_valid(&self, path: &Path) -> bool;
Expand All @@ -27,40 +39,65 @@ impl Finder {
U: AsRef<OsStr>,
V: AsRef<Path>,
{
let path = ensure_exe_extension(binary_name.as_ref());

let path = PathBuf::from(&binary_name);
// Does it have a path separator?
if path.components().count() > 1 {
if path.is_absolute() {
if binary_checker.is_valid(&path) {
// Already fine.
Ok(path)
} else {
// Absolute path but it's not usable.
Err(ErrorKind::BadAbsolutePath.into())
}
check_with_exe_extension(path, binary_checker)
.ok_or(ErrorKind::BadAbsolutePath.into())
} else {
// Try to make it absolute.
let mut new_path = PathBuf::from(cwd.as_ref());
new_path.push(path);
let new_path = ensure_exe_extension(new_path);
if binary_checker.is_valid(&new_path) {
Ok(new_path)
} else {
// File doesn't exist or isn't executable.
Err(ErrorKind::BadRelativePath.into())
}
check_with_exe_extension(new_path, binary_checker)
.ok_or(ErrorKind::BadRelativePath.into())
}
} else {
// No separator, look it up in `paths`.
paths
.and_then(|paths| {
env::split_paths(paths.as_ref())
.map(|p| ensure_exe_extension(p.join(binary_name.as_ref())))
.skip_while(|p| !(binary_checker.is_valid(p)))
env::split_paths(&paths)
.map(|p| p.join(binary_name.as_ref()))
.map(|p| check_with_exe_extension(p, binary_checker))
.skip_while(|res| res.is_none())
.next()
})
.ok_or_else(|| ErrorKind::CannotFindBinaryPath.into())
.map(|res| res.unwrap())
.ok_or(ErrorKind::CannotFindBinaryPath.into())
}
}
}

#[cfg(unix)]
/// Check if given path with platform specification is valid
pub fn check_with_exe_extension<T: AsRef<Path>>(path: T, binary_checker: &Checker) -> Option<PathBuf> {
if binary_checker.is_valid(&path) {
Some(path)
} else {
None
}
}

#[cfg(windows)]
/// Check if given path with platform specification is valid
pub fn check_with_exe_extension<T: AsRef<Path>>(path: T, binary_checker: &Checker) -> Option<PathBuf> {
// Check if path already have executable extension
if check_extension(&path, &EXE_EXTENSION_VEC) {
if binary_checker.is_valid(path.as_ref()) {
Some(path.as_ref().to_path_buf())
} else {
None
}
} else {
// Check paths with windows executable extensions
EXE_EXTENSION_VEC.iter()
.map(|e| {
// Append the extension.
let mut s = path.as_ref().to_path_buf().into_os_string();
s.push(e);
PathBuf::from(s)
})
.skip_while(|p| !(binary_checker.is_valid(p)))
.next()
}
}
81 changes: 39 additions & 42 deletions src/helper.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,47 @@
use std::env;
use std::path::PathBuf;
use std::path::Path;

/// Like `Path::with_extension`, but don't replace an existing extension.
pub fn ensure_exe_extension<T: AsRef<Path>>(path: T) -> PathBuf {
if env::consts::EXE_EXTENSION.is_empty() {
// Nothing to do.
path.as_ref().to_path_buf()
} else {
match path.as_ref()
.extension()
.and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case(env::consts::EXE_EXTENSION))
{
// Already has the right extension.
Some(true) => path.as_ref().to_path_buf(),
_ => {
// Append the extension.
let mut s = path.as_ref().to_path_buf().into_os_string();
s.push(".");
s.push(env::consts::EXE_EXTENSION);
PathBuf::from(s)
}
}
/// Check if given path has extension which in the given vector.
pub fn check_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, exts_vec: &Vec<S>) -> bool {
match path.as_ref()
.extension()
.and_then(|e| e.to_str())
.map(|e| exts_vec.iter().any(|ext| e.eq_ignore_ascii_case(&ext.as_ref()[1..])))
{
Some(true) => true,
_ => false,
}
}

#[test]
fn test_exe_extension() {
let expected = PathBuf::from("foo").with_extension(env::consts::EXE_EXTENSION);
assert_eq!(expected, ensure_exe_extension(PathBuf::from("foo")));
let p = expected.clone();
assert_eq!(expected, ensure_exe_extension(p));
}
#[cfg(test)]
mod test {
use super::*;
use std::path::PathBuf;

#[test]
#[cfg(windows)]
fn test_exe_extension_existing_extension() {
assert_eq!(
PathBuf::from("foo.bar.exe"),
ensure_exe_extension("foo.bar")
);
}
#[test]
fn test_extension_in_extension_vector() {
// Case insensitive
assert!(
check_extension(
PathBuf::from("foo.exe"),
&vec![".COM", ".EXE", ".CMD"]
)
);

#[test]
#[cfg(windows)]
fn test_exe_extension_existing_extension_uppercase() {
assert_eq!(PathBuf::from("foo.EXE"), ensure_exe_extension("foo.EXE"));
assert!(
check_extension(
PathBuf::from("foo.CMD"),
&vec![".COM", ".EXE", ".CMD"]
)
);
}

#[test]
fn test_extension_not_in_extension_vector() {
assert!(
!check_extension(
PathBuf::from("foo.bar"),
&vec![".COM", ".EXE", ".CMD"]
)
);
}
}
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@

#[macro_use]
extern crate failure;
#[cfg(windows)]
#[macro_use]
extern crate lazy_static;

extern crate libc;
#[cfg(test)]
extern crate tempdir;

use failure::ResultExt;
mod finder;
mod checker;
#[cfg(windows)]
mod helper;
mod error;

Expand Down Expand Up @@ -124,7 +129,7 @@ mod test {
fs::File::create(&b).and_then(|_f| b.canonicalize())
}

#[cfg(not(unix))]
#[cfg(windows)]
fn mk_bin(dir: &Path, path: &str) -> io::Result<PathBuf> {
touch(dir, path)
}
Expand Down

0 comments on commit 78e0291

Please sign in to comment.