Files
dbx/crates/dbx-platform/src/path_utils.rs
T

107 lines
3.6 KiB
Rust

pub fn expand_tilde(path: &str) -> String {
expand_tilde_with(path, home_dir, named_user_home)
}
fn expand_tilde_with(
path: &str,
current_home: impl FnOnce() -> Option<String>,
named_home: impl FnOnce(&str) -> Option<String>,
) -> String {
if !path.starts_with('~') {
return path.to_string();
}
if path.len() == 1 {
return current_home().unwrap_or_else(|| path.to_string());
}
if path.as_bytes().get(1) == Some(&b'/') {
return match current_home() {
Some(home) => home + &path[1..],
None => path.to_string(),
};
}
let named_path = &path[1..];
let (username, suffix) =
named_path.find('/').map_or((named_path, ""), |index| (&named_path[..index], &named_path[index..]));
named_home(username).map_or_else(|| path.to_string(), |home| home + suffix)
}
fn home_dir() -> Option<String> {
std::env::var("HOME").or_else(|_| std::env::var("USERPROFILE")).ok()
}
#[cfg(all(unix, not(target_os = "redox")))]
fn named_user_home(username: &str) -> Option<String> {
nix::unistd::User::from_name(username).ok().flatten().and_then(|user| user.dir.into_os_string().into_string().ok())
}
#[cfg(any(not(unix), target_os = "redox"))]
fn named_user_home(_username: &str) -> Option<String> {
None
}
#[cfg(test)]
mod tests {
use super::expand_tilde_with;
fn no_named_user(_username: &str) -> Option<String> {
None
}
#[test]
fn preserves_paths_without_a_leading_tilde() {
assert_eq!(
expand_tilde_with(
"relative/file",
|| panic!("ordinary paths must not resolve the current home"),
|_| panic!("ordinary paths must not resolve a named home")
),
"relative/file"
);
assert_eq!(
expand_tilde_with(
"/absolute/file",
|| panic!("ordinary paths must not resolve the current home"),
|_| panic!("ordinary paths must not resolve a named home")
),
"/absolute/file"
);
}
#[test]
fn expands_current_user_home_forms() {
assert_eq!(expand_tilde_with("~", || Some("/home/current".into()), no_named_user), "/home/current");
assert_eq!(
expand_tilde_with(
"~/.ssh/config",
|| Some("/home/current".into()),
|_| panic!("current-user paths must not resolve a named home")
),
"/home/current/.ssh/config"
);
}
#[test]
fn preserves_current_user_forms_when_home_is_unavailable() {
assert_eq!(expand_tilde_with("~", || None, no_named_user), "~");
assert_eq!(expand_tilde_with("~/.ssh/config", || None, no_named_user), "~/.ssh/config");
}
#[test]
fn expands_named_user_home_forms() {
let named_home = |username: &str| (username == "alice").then(|| "/Users/alice".to_string());
assert_eq!(expand_tilde_with("~alice", || None, named_home), "/Users/alice");
assert_eq!(expand_tilde_with("~alice/.ssh/agent.sock", || None, named_home), "/Users/alice/.ssh/agent.sock");
assert_eq!(expand_tilde_with("~alice/", || None, named_home), "/Users/alice/");
}
#[test]
fn preserves_unresolved_or_non_posix_named_user_forms() {
assert_eq!(expand_tilde_with("~missing/.ssh/agent.sock", || None, no_named_user), "~missing/.ssh/agent.sock");
assert_eq!(expand_tilde_with("~alice\\.ssh\\agent.sock", || None, no_named_user), "~alice\\.ssh\\agent.sock");
assert_eq!(expand_tilde_with("~~/.ssh/agent.sock", || None, no_named_user), "~~/.ssh/agent.sock");
}
}