starship/modules/
env_var.rs1use super::{Context, Module};
2use std::borrow::Cow;
3
4use crate::config::ModuleConfig;
5use crate::configs::env_var::EnvVarConfig;
6use crate::formatter::StringFormatter;
7
8pub fn module<'a>(name: Option<&str>, context: &'a Context) -> Option<Module<'a>> {
15 let toml_config = match name {
16 Some(name) => context
17 .config
18 .get_config(&["env_var", name])
19 .map(Cow::Borrowed),
20 None => context
21 .config
22 .get_module_config("env_var")
23 .and_then(filter_config)
24 .map(Cow::Owned)
25 .map(Some)?,
26 };
27
28 let mod_name = match name {
29 Some(name) => format!("env_var.{name}"),
30 None => "env_var".to_owned(),
31 };
32
33 let config = EnvVarConfig::try_load(toml_config.as_deref());
34 let mut module = Module::new(mod_name, config.description, None);
36 if config.disabled {
37 return None;
38 }
39
40 let variable_name = config.variable.or(name)?;
41
42 let env_value = context.get_env(variable_name);
43 let env_value = env_value.as_deref().or(config.default)?;
44 let parsed = StringFormatter::new(config.format).and_then(|formatter| {
45 formatter
46 .map_meta(|var, _| match var {
47 "symbol" => Some(config.symbol),
48 _ => None,
49 })
50 .map_style(|variable| match variable {
51 "style" => Some(Ok(config.style)),
52 _ => None,
53 })
54 .map(|variable| match variable {
55 "env_value" => Some(Ok(env_value)),
56 _ => None,
57 })
58 .parse(None, Some(context))
59 });
60
61 module.set_segments(match parsed {
62 Ok(segments) => segments,
63 Err(error) => {
64 log::warn!("Error in module `env_var`:\n{error}");
65 return None;
66 }
67 });
68
69 Some(module)
70}
71
72fn filter_config(config: &toml::Value) -> Option<toml::Value> {
75 let o = config
76 .as_table()
77 .map(|table| {
78 table
79 .iter()
80 .filter(|(_key, val)| !val.is_table())
81 .map(|(key, val)| (key.clone(), val.clone()))
82 .collect::<toml::value::Table>()
83 })
84 .filter(|table| !table.is_empty())
85 .map(toml::Value::Table);
86 log::trace!("Filtered top-level env_var config: {o:?}");
87 o
88}
89
90#[cfg(test)]
91mod test {
92 use crate::test::ModuleRenderer;
93 use nu_ansi_term::{Color, Style};
94
95 const TEST_VAR_VALUE: &str = "astronauts";
96
97 #[test]
98 fn empty_config() {
99 let actual = ModuleRenderer::new("env_var").collect();
100 let expected = None;
101
102 assert_eq!(expected, actual);
103 }
104
105 #[test]
106 fn fallback_config() {
107 let actual = ModuleRenderer::new("env_var")
108 .config(toml::toml! {
109 [env_var]
110 variable="TEST_VAR"
111 })
112 .env("TEST_VAR", TEST_VAR_VALUE)
113 .collect();
114 let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE)));
115
116 assert_eq!(expected, actual);
117 }
118
119 #[test]
120 fn defined_variable() {
121 let actual = ModuleRenderer::new("env_var.TEST_VAR")
122 .config(toml::toml! {
123 [env_var.TEST_VAR]
124 })
125 .env("TEST_VAR", TEST_VAR_VALUE)
126 .collect();
127 let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE)));
128
129 assert_eq!(expected, actual);
130 }
131
132 #[test]
133 fn undefined_variable() {
134 let actual = ModuleRenderer::new("env_var.TEST_VAR")
135 .config(toml::toml! {
136 [env_var.TEST_VAR]
137 })
138 .collect();
139 let expected = None;
140
141 assert_eq!(expected, actual);
142 }
143
144 #[test]
145 fn default_has_no_effect() {
146 let actual = ModuleRenderer::new("env_var.TEST_VAR")
147 .config(toml::toml! {
148 [env_var.TEST_VAR]
149 default = "N/A"
150 })
151 .env("TEST_VAR", TEST_VAR_VALUE)
152 .collect();
153 let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE)));
154
155 assert_eq!(expected, actual);
156 }
157
158 #[test]
159 fn default_takes_effect() {
160 let actual = ModuleRenderer::new("env_var.UNDEFINED_TEST_VAR")
161 .config(toml::toml! {
162 [env_var.UNDEFINED_TEST_VAR]
163 default = "N/A"
164 })
165 .collect();
166 let expected = Some(format!("with {} ", style().paint("N/A")));
167
168 assert_eq!(expected, actual);
169 }
170
171 #[test]
172 fn symbol() {
173 let actual = ModuleRenderer::new("env_var.TEST_VAR")
174 .config(toml::toml! {
175 [env_var.TEST_VAR]
176 format = "with [■ $env_value](black bold dimmed) "
177 })
178 .env("TEST_VAR", TEST_VAR_VALUE)
179 .collect();
180 let expected = Some(format!(
181 "with {} ",
182 style().paint(format!("■ {TEST_VAR_VALUE}"))
183 ));
184
185 assert_eq!(expected, actual);
186 }
187
188 #[test]
189 fn prefix() {
190 let actual = ModuleRenderer::new("env_var.TEST_VAR")
191 .config(toml::toml! {
192 [env_var.TEST_VAR]
193 format = "with [_$env_value](black bold dimmed) "
194 })
195 .env("TEST_VAR", TEST_VAR_VALUE)
196 .collect();
197 let expected = Some(format!(
198 "with {} ",
199 style().paint(format!("_{TEST_VAR_VALUE}"))
200 ));
201
202 assert_eq!(expected, actual);
203 }
204
205 #[test]
206 fn suffix() {
207 let actual = ModuleRenderer::new("env_var.TEST_VAR")
208 .config(toml::toml! {
209 [env_var.TEST_VAR]
210 format = "with [${env_value}_](black bold dimmed) "
211 })
212 .env("TEST_VAR", TEST_VAR_VALUE)
213 .collect();
214 let expected = Some(format!(
215 "with {} ",
216 style().paint(format!("{TEST_VAR_VALUE}_"))
217 ));
218
219 assert_eq!(expected, actual);
220 }
221
222 #[test]
223 fn display_few() {
224 let actual1 = ModuleRenderer::new("env_var.TEST_VAR")
225 .config(toml::toml! {
226 [env_var.TEST_VAR]
227 [env_var.TEST_VAR2]
228 })
229 .env("TEST_VAR", TEST_VAR_VALUE)
230 .env("TEST_VAR2", TEST_VAR_VALUE)
231 .collect();
232 let actual2 = ModuleRenderer::new("env_var.TEST_VAR2")
233 .config(toml::toml! {
234 [env_var.TEST_VAR]
235 [env_var.TEST_VAR2]
236 })
237 .env("TEST_VAR", TEST_VAR_VALUE)
238 .env("TEST_VAR2", TEST_VAR_VALUE)
239 .collect();
240 let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE)));
241
242 assert_eq!(expected, actual1);
243 assert_eq!(expected, actual2);
244 }
245
246 #[test]
247 fn mixed() {
248 let cfg = toml::toml! {
249 [env_var]
250 variable = "TEST_VAR_OUTER"
251 format = "$env_value"
252 [env_var.TEST_VAR_INNER]
253 format = "$env_value"
254 };
255 let actual_inner = ModuleRenderer::new("env_var.TEST_VAR_INNER")
256 .config(cfg.clone())
257 .env("TEST_VAR_OUTER", "outer")
258 .env("TEST_VAR_INNER", "inner")
259 .collect();
260
261 assert_eq!(
262 actual_inner.as_deref(),
263 Some("inner"),
264 "inner module should be rendered"
265 );
266
267 let actual_outer = ModuleRenderer::new("env_var")
268 .config(cfg)
269 .env("TEST_VAR_OUTER", "outer")
270 .env("TEST_VAR_INNER", "inner")
271 .collect();
272
273 assert_eq!(
274 actual_outer.as_deref(),
275 Some("outer"),
276 "outer module should be rendered"
277 );
278 }
279
280 #[test]
281 fn no_config() {
282 let actual = ModuleRenderer::new("env_var.TEST_VAR")
283 .env("TEST_VAR", TEST_VAR_VALUE)
284 .collect();
285 let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE)));
286
287 assert_eq!(expected, actual);
288 }
289
290 #[test]
291 fn disabled_child() {
292 let actual = ModuleRenderer::new("env_var.TEST_VAR")
293 .config(toml::toml! {
294 [env_var.TEST_VAR]
295 disabled = true
296 })
297 .env("TEST_VAR", TEST_VAR_VALUE)
298 .collect();
299 let expected = None;
300
301 assert_eq!(expected, actual);
302 }
303
304 #[test]
305 fn disabled_root() {
306 let actual = ModuleRenderer::new("env_var")
307 .config(toml::toml! {
308 [env_var]
309 disabled = true
310 })
311 .env("TEST_VAR", TEST_VAR_VALUE)
312 .collect();
313 let expected = None;
314
315 assert_eq!(expected, actual);
316 }
317
318 #[test]
319 fn variable_override() {
320 let actual = ModuleRenderer::new("env_var.TEST_VAR")
321 .config(toml::toml! {
322 [env_var.TEST_VAR]
323 variable = "TEST_VAR2"
324 })
325 .env("TEST_VAR", "implicit name")
326 .env("TEST_VAR2", "explicit name")
327 .collect();
328 let expected = Some(format!("with {} ", style().paint("explicit name")));
329
330 assert_eq!(expected, actual);
331 }
332
333 fn style() -> Style {
334 Color::Black.bold().dimmed()
336 }
337}