Skip to content

Commit 11952d4

Browse files
committed
feat: Implement tilde expansion for path env
1 parent 7130462 commit 11952d4

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ categories = ["os", "filesystem"]
1212
keywords = ["which", "which-rs", "unix", "command"]
1313

1414
[dependencies]
15+
dirs = "5.0.1"
1516
either = "1.6.1"
1617
libc = "0.2.121"
1718
regex = { version = "1.5.5", optional = true }

src/finder.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ use crate::checker::CompositeChecker;
22
use crate::error::*;
33
#[cfg(windows)]
44
use crate::helper::has_executable_extension;
5+
use dirs::home_dir;
56
use either::Either;
67
#[cfg(feature = "regex")]
78
use regex::Regex;
89
#[cfg(feature = "regex")]
910
use std::borrow::Borrow;
11+
use std::borrow::Cow;
1012
use std::env;
1113
use std::ffi::OsStr;
1214
#[cfg(any(feature = "regex", target_os = "windows"))]
1315
use std::fs;
1416
use std::iter;
15-
use std::path::{Path, PathBuf};
17+
use std::path::{Component, Path, PathBuf};
1618

1719
pub trait Checker {
1820
fn is_valid(&self, path: &Path) -> bool;
@@ -135,7 +137,9 @@ impl Finder {
135137
where
136138
P: IntoIterator<Item = PathBuf>,
137139
{
138-
let new_paths = paths.into_iter().map(move |p| p.join(binary_name.clone()));
140+
let new_paths = paths
141+
.into_iter()
142+
.map(move |p| tilde_expansion(&p).join(binary_name.clone()));
139143

140144
Self::append_extension(new_paths)
141145
}
@@ -210,6 +214,21 @@ impl Finder {
210214
}
211215
}
212216

217+
fn tilde_expansion(p: &PathBuf) -> Cow<'_, PathBuf> {
218+
let mut component_iter = p.components();
219+
if let Some(Component::Normal(o)) = component_iter.next() {
220+
if o == "~" {
221+
let mut new_path = home_dir().unwrap_or_default();
222+
new_path.extend(component_iter);
223+
Cow::Owned(new_path)
224+
} else {
225+
Cow::Borrowed(p)
226+
}
227+
} else {
228+
Cow::Borrowed(p)
229+
}
230+
}
231+
213232
#[cfg(target_os = "windows")]
214233
fn correct_casing(mut p: PathBuf) -> PathBuf {
215234
if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) {

tests/basic.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::path::{Path, PathBuf};
99
use std::{env, vec};
1010
use tempfile::TempDir;
1111

12+
#[derive(Debug)]
1213
struct TestFixture {
1314
/// Temp directory.
1415
pub tempdir: TempDir,
@@ -84,6 +85,33 @@ impl TestFixture {
8485
}
8586
}
8687

88+
#[cfg(unix)]
89+
pub fn new_with_tilde_path() -> TestFixture {
90+
let tempdir = tempfile::tempdir().unwrap();
91+
let mut builder = fs::DirBuilder::new();
92+
builder.recursive(true);
93+
let mut paths = vec![];
94+
let mut bins = vec![];
95+
for d in SUBDIRS.iter() {
96+
let p = PathBuf::from("~").join(d);
97+
let p_bin = tempdir.path().join(d);
98+
builder.create(&p_bin).unwrap();
99+
bins.push(mk_bin(&p_bin, BIN_NAME, "").unwrap());
100+
bins.push(mk_bin(&p_bin, BIN_NAME, "exe").unwrap());
101+
bins.push(mk_bin(&p_bin, BIN_NAME, "cmd").unwrap());
102+
paths.push(p);
103+
}
104+
let p = tempdir.path().join("win-bin");
105+
builder.create(&p).unwrap();
106+
bins.push(mk_bin(&p, "win-bin", "exe").unwrap());
107+
paths.push(p);
108+
TestFixture {
109+
tempdir,
110+
paths: env::join_paths(paths).unwrap(),
111+
bins,
112+
}
113+
}
114+
87115
#[allow(dead_code)]
88116
pub fn touch(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
89117
touch(self.tempdir.path(), path, extension)
@@ -136,6 +164,26 @@ fn test_which() {
136164
assert_eq!(_which(&f, BIN_NAME).unwrap(), f.bins[1])
137165
}
138166

167+
#[test]
168+
#[cfg(unix)]
169+
fn test_which_tilde() {
170+
let old_home = env::var_os("HOME");
171+
let f = TestFixture::new_with_tilde_path();
172+
env::set_var("HOME", f.tempdir.path().as_os_str());
173+
assert_eq!(_which(&f, BIN_NAME).unwrap(), f.bins[0]);
174+
if let Some(old_home) = old_home {
175+
env::set_var("HOME", old_home);
176+
} else {
177+
env::remove_var("HOME");
178+
}
179+
}
180+
181+
// Windows test_which_tilde intentionally omitted because
182+
// we don't want to pollute the home directory.
183+
// It's non-trivial to adjust which directory Windows thinks
184+
// is the home directory. At this time, tilde expansion has
185+
// no Windows specific behavior. It works as normal on Windows.
186+
139187
#[test]
140188
#[cfg(all(unix, feature = "regex"))]
141189
fn test_which_re_in_with_matches() {

0 commit comments

Comments
 (0)