starship/modules/
env_var.rs

1use super::{Context, Module};
2use std::borrow::Cow;
3
4use crate::config::ModuleConfig;
5use crate::configs::env_var::EnvVarConfig;
6use crate::formatter::StringFormatter;
7
8/// Creates a module with the value of the chosen environment variable
9///
10/// Will display the environment variable's value if all of the following criteria are met:
11///     - `env_var.disabled` is absent or false
12///     - `env_var.variable` is defined
13///     - a variable named as the value of `env_var.variable` is defined
14pub 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    // Note: Forward config if `Module` ends up needing `config`
35    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
72/// Filter `config` to only includes non-table values
73/// This filters the top-level table to only include its specific configuration
74fn 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        // default style
335        Color::Black.bold().dimmed()
336    }
337}