1pub fn is_truthy(input: &str) -> bool {
2 matches!(input.to_lowercase().as_str(), "1" | "true" | "yes" | "on")
3}
4
5pub fn is_truthy_or(input: Option<&str>, default: bool) -> bool {
6 input.map(is_truthy).unwrap_or(default)
7}
8
9fn truthy_from_env(name: &str) -> Option<bool> {
10 std::env::var_os(name).map(|value| {
11 let value = value.to_string_lossy();
12 is_truthy(value.trim())
13 })
14}
15
16pub fn env_present(name: &str) -> bool {
17 std::env::var_os(name).is_some()
18}
19
20pub fn env_truthy_if_present(name: &str) -> Option<bool> {
21 std::env::var(name)
22 .ok()
23 .map(|value| is_truthy(value.trim()))
24}
25
26pub fn env_truthy(name: &str) -> bool {
27 truthy_from_env(name).unwrap_or(false)
28}
29
30pub fn env_truthy_or(name: &str, default: bool) -> bool {
31 truthy_from_env(name).unwrap_or(default)
32}
33
34pub fn env_or_default(name: &str, default: &str) -> String {
35 std::env::var(name).unwrap_or_else(|_| default.to_string())
36}
37
38pub fn env_non_empty(name: &str) -> Option<String> {
39 std::env::var(name)
40 .ok()
41 .map(|value| value.trim().to_string())
42 .filter(|value| !value.is_empty())
43}
44
45pub fn parse_duration_seconds(raw: &str) -> Option<u64> {
46 let raw = raw.trim();
47 if raw.is_empty() {
48 return None;
49 }
50
51 let raw = raw.to_ascii_lowercase();
52 let (num_part, multiplier): (&str, u64) = match raw.chars().last()? {
53 's' => (&raw[..raw.len().saturating_sub(1)], 1),
54 'm' => (&raw[..raw.len().saturating_sub(1)], 60),
55 'h' => (&raw[..raw.len().saturating_sub(1)], 60 * 60),
56 'd' => (&raw[..raw.len().saturating_sub(1)], 60 * 60 * 24),
57 'w' => (&raw[..raw.len().saturating_sub(1)], 60 * 60 * 24 * 7),
58 ch if ch.is_ascii_digit() => (raw.as_str(), 1),
59 _ => return None,
60 };
61
62 let num_part = num_part.trim();
63 if num_part.is_empty() {
64 return None;
65 }
66
67 let value = num_part.parse::<u64>().ok()?;
68 if value == 0 {
69 return None;
70 }
71
72 value.checked_mul(multiplier)
73}
74
75pub fn no_color_enabled() -> bool {
76 env_present("NO_COLOR")
77}
78
79pub fn no_color_non_empty_enabled() -> bool {
80 std::env::var("NO_COLOR")
81 .ok()
82 .is_some_and(|value| !value.trim().is_empty())
83}
84
85pub fn no_color_requested(explicit_no_color: bool) -> bool {
86 explicit_no_color || no_color_enabled()
87}
88
89pub fn prompt_segment_color_enabled(explicit_toggle_env: &str) -> bool {
90 use std::io::IsTerminal;
91
92 if no_color_enabled() {
93 return false;
94 }
95
96 if env_present(explicit_toggle_env) {
97 return env_truthy(explicit_toggle_env);
98 }
99
100 if env_present("STARSHIP_SESSION_KEY") || env_present("STARSHIP_SHELL") {
101 return true;
102 }
103
104 std::io::stdout().is_terminal()
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use nils_test_support::{EnvGuard, GlobalStateLock};
111
112 #[test]
113 fn is_truthy_matches_expected_values() {
114 for value in ["1", "true", "TRUE", "yes", "On"] {
115 assert!(is_truthy(value), "expected truthy value: {value}");
116 }
117 }
118
119 #[test]
120 fn is_truthy_rejects_falsey_or_unknown_values() {
121 for value in ["", "0", "false", "no", "off", " yes ", "enabled"] {
122 assert!(!is_truthy(value), "expected falsey value: {value}");
123 }
124 }
125
126 #[test]
127 fn is_truthy_or_uses_default_when_missing() {
128 assert!(is_truthy_or(None, true));
129 assert!(!is_truthy_or(None, false));
130 assert!(is_truthy_or(Some("1"), false));
131 }
132
133 #[test]
134 fn env_truthy_reads_process_environment() {
135 let lock = GlobalStateLock::new();
136 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_TRUTHY_TEST", "yes");
137 assert!(env_truthy("NILS_COMMON_ENV_TRUTHY_TEST"));
138 }
139
140 #[test]
141 fn env_present_checks_var_presence() {
142 let lock = GlobalStateLock::new();
143 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_PRESENT_TEST", "");
144 assert!(env_present("NILS_COMMON_ENV_PRESENT_TEST"));
145 }
146
147 #[test]
148 fn env_truthy_if_present_returns_none_when_missing() {
149 let lock = GlobalStateLock::new();
150 let _guard = EnvGuard::remove(&lock, "NILS_COMMON_ENV_TRUTHY_IF_PRESENT_MISSING_TEST");
151 assert_eq!(
152 env_truthy_if_present("NILS_COMMON_ENV_TRUTHY_IF_PRESENT_MISSING_TEST"),
153 None
154 );
155 }
156
157 #[test]
158 fn env_truthy_if_present_parses_trimmed_value() {
159 let lock = GlobalStateLock::new();
160 let _guard = EnvGuard::set(
161 &lock,
162 "NILS_COMMON_ENV_TRUTHY_IF_PRESENT_VALUE_TEST",
163 " yes ",
164 );
165 assert_eq!(
166 env_truthy_if_present("NILS_COMMON_ENV_TRUTHY_IF_PRESENT_VALUE_TEST"),
167 Some(true)
168 );
169 }
170
171 #[test]
172 fn env_truthy_trims_whitespace() {
173 let lock = GlobalStateLock::new();
174 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_TRUTHY_TRIM_TEST", " yes ");
175 assert!(env_truthy("NILS_COMMON_ENV_TRUTHY_TRIM_TEST"));
176 }
177
178 #[test]
179 fn env_truthy_or_falls_back_to_default() {
180 let lock = GlobalStateLock::new();
181 let _guard = EnvGuard::remove(&lock, "NILS_COMMON_ENV_TRUTHY_OR_TEST");
182 assert!(env_truthy_or("NILS_COMMON_ENV_TRUTHY_OR_TEST", true));
183 assert!(!env_truthy_or("NILS_COMMON_ENV_TRUTHY_OR_TEST", false));
184 }
185
186 #[test]
187 fn env_truthy_or_prefers_present_trimmed_values() {
188 let lock = GlobalStateLock::new();
189 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_TRUTHY_OR_VALUE_TEST", " off ");
190 assert!(!env_truthy_or("NILS_COMMON_ENV_TRUTHY_OR_VALUE_TEST", true));
191 }
192
193 #[test]
194 fn env_or_default_prefers_present_value() {
195 let lock = GlobalStateLock::new();
196 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_OR_DEFAULT_PRESENT_TEST", "custom");
197 assert_eq!(
198 env_or_default("NILS_COMMON_ENV_OR_DEFAULT_PRESENT_TEST", "fallback"),
199 "custom"
200 );
201 }
202
203 #[test]
204 fn env_or_default_uses_default_when_missing() {
205 let lock = GlobalStateLock::new();
206 let _guard = EnvGuard::remove(&lock, "NILS_COMMON_ENV_OR_DEFAULT_MISSING_TEST");
207 assert_eq!(
208 env_or_default("NILS_COMMON_ENV_OR_DEFAULT_MISSING_TEST", "fallback"),
209 "fallback"
210 );
211 }
212
213 #[test]
214 fn env_non_empty_returns_none_for_missing_or_blank_values() {
215 let lock = GlobalStateLock::new();
216 let _missing = EnvGuard::remove(&lock, "NILS_COMMON_ENV_NON_EMPTY_MISSING_TEST");
217 assert_eq!(
218 env_non_empty("NILS_COMMON_ENV_NON_EMPTY_MISSING_TEST"),
219 None
220 );
221
222 let _blank = EnvGuard::set(&lock, "NILS_COMMON_ENV_NON_EMPTY_MISSING_TEST", " ");
223 assert_eq!(
224 env_non_empty("NILS_COMMON_ENV_NON_EMPTY_MISSING_TEST"),
225 None
226 );
227 }
228
229 #[test]
230 fn env_non_empty_returns_trimmed_value_when_present() {
231 let lock = GlobalStateLock::new();
232 let _guard = EnvGuard::set(&lock, "NILS_COMMON_ENV_NON_EMPTY_VALUE_TEST", " value ");
233 assert_eq!(
234 env_non_empty("NILS_COMMON_ENV_NON_EMPTY_VALUE_TEST"),
235 Some("value".to_string())
236 );
237 }
238
239 #[test]
240 fn parse_duration_seconds_accepts_plain_and_suffixed_values() {
241 assert_eq!(parse_duration_seconds("45"), Some(45));
242 assert_eq!(parse_duration_seconds("45s"), Some(45));
243 assert_eq!(parse_duration_seconds("2m"), Some(120));
244 assert_eq!(parse_duration_seconds("3h"), Some(10_800));
245 assert_eq!(parse_duration_seconds("4d"), Some(345_600));
246 assert_eq!(parse_duration_seconds("2w"), Some(1_209_600));
247 assert_eq!(parse_duration_seconds(" 7H "), Some(25_200));
248 }
249
250 #[test]
251 fn parse_duration_seconds_rejects_invalid_inputs() {
252 for value in ["", " ", "0", "0s", "s", "-1", "1x", "ms"] {
253 assert_eq!(parse_duration_seconds(value), None, "value={value}");
254 }
255 }
256
257 #[test]
258 fn parse_duration_seconds_rejects_overflow() {
259 assert_eq!(parse_duration_seconds("18446744073709551615w"), None);
260 }
261
262 #[test]
263 fn no_color_enabled_checks_var_presence() {
264 let lock = GlobalStateLock::new();
265 let _guard = EnvGuard::set(&lock, "NO_COLOR", "");
266 assert!(no_color_enabled());
267 }
268
269 #[test]
270 fn no_color_non_empty_enabled_distinguishes_empty_and_non_empty() {
271 let lock = GlobalStateLock::new();
272
273 {
274 let _guard = EnvGuard::set(&lock, "NO_COLOR", "1");
275 assert!(no_color_non_empty_enabled());
276 }
277
278 {
279 let _guard = EnvGuard::set(&lock, "NO_COLOR", "");
280 assert!(!no_color_non_empty_enabled());
281 }
282 }
283
284 #[test]
285 fn no_color_requested_respects_explicit_flag() {
286 let lock = GlobalStateLock::new();
287 let _guard = EnvGuard::remove(&lock, "NO_COLOR");
288 assert!(no_color_requested(true));
289 assert!(!no_color_requested(false));
290 }
291
292 #[test]
293 fn no_color_requested_respects_env_presence() {
294 let lock = GlobalStateLock::new();
295 let _guard = EnvGuard::set(&lock, "NO_COLOR", "1");
296 assert!(no_color_requested(false));
297 }
298
299 #[test]
300 fn prompt_segment_color_enabled_no_color_has_highest_priority() {
301 let lock = GlobalStateLock::new();
302 let _no_color = EnvGuard::set(&lock, "NO_COLOR", "1");
303 let _explicit = EnvGuard::set(&lock, "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED", "1");
304 let _session = EnvGuard::set(&lock, "STARSHIP_SESSION_KEY", "session");
305 assert!(!prompt_segment_color_enabled(
306 "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED"
307 ));
308 }
309
310 #[test]
311 fn prompt_segment_color_enabled_honors_explicit_truthy_and_falsey_values() {
312 let lock = GlobalStateLock::new();
313 let _no_color = EnvGuard::remove(&lock, "NO_COLOR");
314 let _session = EnvGuard::remove(&lock, "STARSHIP_SESSION_KEY");
315 let _shell = EnvGuard::remove(&lock, "STARSHIP_SHELL");
316
317 for value in ["1", " true ", "YES", "on"] {
318 let _explicit = EnvGuard::set(&lock, "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED", value);
319 assert!(
320 prompt_segment_color_enabled("NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED"),
321 "expected truthy value: {value}"
322 );
323 }
324
325 for value in ["", " ", "0", "false", "no", "off", "y", "enabled"] {
326 let _explicit = EnvGuard::set(&lock, "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED", value);
327 assert!(
328 !prompt_segment_color_enabled("NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED"),
329 "expected falsey value: {value}"
330 );
331 }
332 }
333
334 #[test]
335 fn prompt_segment_color_enabled_uses_prompt_markers_when_not_overridden() {
336 let lock = GlobalStateLock::new();
337 let _no_color = EnvGuard::remove(&lock, "NO_COLOR");
338 let _explicit = EnvGuard::remove(&lock, "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED");
339 let _session = EnvGuard::set(&lock, "STARSHIP_SESSION_KEY", "session");
340 assert!(prompt_segment_color_enabled(
341 "NILS_COMMON_PROMPT_SEGMENT_COLOR_ENABLED"
342 ));
343 }
344}