use std::path::PathBuf;
pub fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
}
pub fn resolve_shell(choice: &str) -> String {
if let Some(s) = std::env::var_os("BOHAY_SHELL") {
if !s.is_empty() {
return s.to_string_lossy().into_owned();
}
}
match choice {
"" | "default" => platform_default_shell(),
"powershell" => find_on_path("pwsh.exe")
.or_else(|| find_on_path("pwsh"))
.or_else(|| find_on_path("powershell.exe"))
.unwrap_or_else(platform_default_shell),
"cmd" => std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string()),
other => other.to_string(),
}
}
#[cfg(windows)]
fn platform_default_shell() -> String {
find_on_path("pwsh.exe")
.or_else(|| find_on_path("powershell.exe"))
.unwrap_or_else(|| std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string()))
}
#[cfg(not(windows))]
fn platform_default_shell() -> String {
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
}
fn find_on_path(exe: &str) -> Option<String> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|dir| dir.join(exe))
.find(|full| full.is_file())
.map(|full| full.to_string_lossy().into_owned())
}
#[cfg(windows)]
pub fn shell_choices() -> &'static [(&'static str, &'static str)] {
&[
("default", "Default"),
("powershell", "PowerShell"),
("cmd", "Command Prompt"),
]
}
#[cfg(windows)]
pub fn shell_label(choice: &str) -> &str {
shell_choices()
.iter()
.find(|(k, _)| *k == choice)
.map(|(_, label)| *label)
.unwrap_or(choice)
}
#[cfg(target_os = "macos")]
pub fn process_cwd(pid: u32) -> Option<PathBuf> {
use std::mem;
unsafe {
let mut info: libc::proc_vnodepathinfo = mem::zeroed();
let size = mem::size_of::<libc::proc_vnodepathinfo>() as libc::c_int;
let n = libc::proc_pidinfo(
pid as libc::c_int,
libc::PROC_PIDVNODEPATHINFO,
0,
&mut info as *mut _ as *mut libc::c_void,
size,
);
if n < size {
return None;
}
let raw = std::slice::from_raw_parts(
info.pvi_cdir.vip_path.as_ptr() as *const u8,
mem::size_of_val(&info.pvi_cdir.vip_path),
);
let end = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
if end == 0 {
return None;
}
Some(PathBuf::from(
String::from_utf8_lossy(&raw[..end]).into_owned(),
))
}
}
#[cfg(target_os = "linux")]
pub fn process_cwd(pid: u32) -> Option<PathBuf> {
std::fs::read_link(format!("/proc/{pid}/cwd")).ok()
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn process_cwd(_pid: u32) -> Option<PathBuf> {
None
}
#[must_use]
pub fn high_res_timer() -> TimerGuard {
#[cfg(windows)]
unsafe {
timeBeginPeriod(1);
}
TimerGuard
}
pub struct TimerGuard;
impl Drop for TimerGuard {
fn drop(&mut self) {
#[cfg(windows)]
unsafe {
timeEndPeriod(1);
}
}
}
#[cfg(windows)]
#[link(name = "winmm")]
extern "system" {
fn timeBeginPeriod(u_period: u32) -> u32;
fn timeEndPeriod(u_period: u32) -> u32;
}
#[cfg(test)]
mod tests {
#[cfg(unix)]
#[test]
fn shell_override_is_honored() {
std::env::set_var("BOHAY_SHELL", "/bin/sh");
assert_eq!(super::resolve_shell("default"), "/bin/sh");
assert_eq!(super::resolve_shell("zsh"), "/bin/sh");
std::env::remove_var("BOHAY_SHELL");
}
#[cfg(windows)]
#[test]
fn shell_choices_have_labels() {
for (keyword, label) in super::shell_choices() {
assert!(!label.is_empty());
assert_eq!(super::shell_label(keyword), *label);
}
assert_eq!(super::shell_label("nu"), "nu");
}
}