1#![deny(missing_docs)]
7
8use std::ffi::OsString;
9use std::path::PathBuf;
10
11pub const CODEWHALE_APP_DIR: &str = ".codewhale";
13
14pub const LEGACY_APP_DIR: &str = ".deepseek";
16
17#[must_use]
24pub fn codewhale_home_override() -> Option<PathBuf> {
25 path_env("CODEWHALE_HOME")
26}
27
28#[must_use]
30pub fn codewhale_home_is_explicit() -> bool {
31 codewhale_home_override().is_some()
32}
33
34#[must_use]
40pub fn legacy_deepseek_home_override() -> Option<PathBuf> {
41 path_env("DEEPSEEK_HOME")
42}
43
44#[must_use]
51pub fn user_home() -> Option<PathBuf> {
52 path_env("HOME")
53 .or_else(|| path_env("USERPROFILE"))
54 .or_else(windows_home_from_environment)
55 .or_else(dirs::home_dir)
56}
57
58#[cfg(windows)]
59fn windows_home_from_environment() -> Option<PathBuf> {
60 let mut path = path_env("HOMEDRIVE")?;
61 path.push(path_env("HOMEPATH")?);
62 (!path.as_os_str().is_empty()).then_some(path)
63}
64
65#[cfg(not(windows))]
66fn windows_home_from_environment() -> Option<PathBuf> {
67 None
68}
69
70#[must_use]
75pub fn codewhale_home() -> Option<PathBuf> {
76 codewhale_home_override().or_else(|| user_home().map(|home| home.join(CODEWHALE_APP_DIR)))
77}
78
79#[must_use]
84pub fn legacy_deepseek_home() -> Option<PathBuf> {
85 user_home().map(|home| home.join(LEGACY_APP_DIR))
86}
87
88fn path_env(name: &str) -> Option<PathBuf> {
89 std::env::var_os(name).and_then(normalize_path_value)
90}
91
92fn normalize_path_value(value: OsString) -> Option<PathBuf> {
93 if value.is_empty() {
94 return None;
95 }
96 match value.to_str() {
97 Some(value) => {
98 let value = value.trim();
99 (!value.is_empty()).then(|| PathBuf::from(value))
100 }
101 None => Some(PathBuf::from(value)),
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn unicode_path_values_are_trimmed_and_whitespace_is_unset() {
111 assert_eq!(
112 normalize_path_value(OsString::from(" /tmp/codewhale ")),
113 Some(PathBuf::from("/tmp/codewhale"))
114 );
115 assert_eq!(normalize_path_value(OsString::from(" \t\n ")), None);
116 assert_eq!(normalize_path_value(OsString::new()), None);
117 }
118
119 #[cfg(unix)]
120 #[test]
121 fn unix_non_unicode_path_values_are_preserved() {
122 use std::os::unix::ffi::OsStringExt;
123
124 let value = OsString::from_vec(b"codewhale-\xff-home".to_vec());
125 assert_eq!(
126 normalize_path_value(value.clone()),
127 Some(PathBuf::from(value))
128 );
129 }
130
131 #[cfg(windows)]
132 #[test]
133 fn windows_non_unicode_path_values_are_preserved() {
134 use std::os::windows::ffi::OsStringExt;
135
136 let value = OsString::from_wide(&[b'C' as u16, b':' as u16, b'\\' as u16, 0xd800]);
137 assert_eq!(
138 normalize_path_value(value.clone()),
139 Some(PathBuf::from(value))
140 );
141 }
142}