#![deny(missing_docs)]
use std::ffi::OsString;
use std::path::PathBuf;
pub const CODEWHALE_APP_DIR: &str = ".codewhale";
pub const LEGACY_APP_DIR: &str = ".deepseek";
#[must_use]
pub fn codewhale_home_override() -> Option<PathBuf> {
path_env("CODEWHALE_HOME")
}
#[must_use]
pub fn codewhale_home_is_explicit() -> bool {
codewhale_home_override().is_some()
}
#[must_use]
pub fn legacy_deepseek_home_override() -> Option<PathBuf> {
path_env("DEEPSEEK_HOME")
}
#[must_use]
pub fn user_home() -> Option<PathBuf> {
path_env("HOME")
.or_else(|| path_env("USERPROFILE"))
.or_else(windows_home_from_environment)
.or_else(dirs::home_dir)
}
#[cfg(windows)]
fn windows_home_from_environment() -> Option<PathBuf> {
let mut path = path_env("HOMEDRIVE")?;
path.push(path_env("HOMEPATH")?);
(!path.as_os_str().is_empty()).then_some(path)
}
#[cfg(not(windows))]
fn windows_home_from_environment() -> Option<PathBuf> {
None
}
#[must_use]
pub fn codewhale_home() -> Option<PathBuf> {
codewhale_home_override().or_else(|| user_home().map(|home| home.join(CODEWHALE_APP_DIR)))
}
#[must_use]
pub fn legacy_deepseek_home() -> Option<PathBuf> {
user_home().map(|home| home.join(LEGACY_APP_DIR))
}
fn path_env(name: &str) -> Option<PathBuf> {
std::env::var_os(name).and_then(normalize_path_value)
}
fn normalize_path_value(value: OsString) -> Option<PathBuf> {
if value.is_empty() {
return None;
}
match value.to_str() {
Some(value) => {
let value = value.trim();
(!value.is_empty()).then(|| PathBuf::from(value))
}
None => Some(PathBuf::from(value)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unicode_path_values_are_trimmed_and_whitespace_is_unset() {
assert_eq!(
normalize_path_value(OsString::from(" /tmp/codewhale ")),
Some(PathBuf::from("/tmp/codewhale"))
);
assert_eq!(normalize_path_value(OsString::from(" \t\n ")), None);
assert_eq!(normalize_path_value(OsString::new()), None);
}
#[cfg(unix)]
#[test]
fn unix_non_unicode_path_values_are_preserved() {
use std::os::unix::ffi::OsStringExt;
let value = OsString::from_vec(b"codewhale-\xff-home".to_vec());
assert_eq!(
normalize_path_value(value.clone()),
Some(PathBuf::from(value))
);
}
#[cfg(windows)]
#[test]
fn windows_non_unicode_path_values_are_preserved() {
use std::os::windows::ffi::OsStringExt;
let value = OsString::from_wide(&[b'C' as u16, b':' as u16, b'\\' as u16, 0xd800]);
assert_eq!(
normalize_path_value(value.clone()),
Some(PathBuf::from(value))
);
}
}