vdev/
environment.rs

1use std::{collections::BTreeMap, process::Command};
2
3use cfg_if::cfg_if;
4
5cfg_if! {
6    if #[cfg(unix)] {
7        use std::sync::OnceLock;
8        use regex::{Captures, Regex};
9    }
10}
11
12pub type Environment = BTreeMap<String, Option<String>>;
13
14pub(crate) fn rename_environment_keys(environment: &Environment) -> Environment {
15    environment
16        .iter()
17        .map(|(var, value)| {
18            (
19                format!("CONFIG_{}", var.replace('-', "_").to_uppercase()),
20                value.clone(),
21            )
22        })
23        .collect()
24}
25
26pub(crate) fn extract_present(environment: &Environment) -> BTreeMap<String, String> {
27    environment
28        .iter()
29        .filter_map(|(k, v)| v.as_ref().map(|s| (k.clone(), s.clone())))
30        .collect()
31}
32
33pub(crate) fn append_environment_variables(command: &mut Command, environment: &Environment) {
34    for (key, value) in environment {
35        command.arg("--env");
36        match value {
37            Some(value) => command.arg(format!("{key}={value}")),
38            None => command.arg(key),
39        };
40    }
41}
42
43cfg_if! {
44if #[cfg(unix)] {
45    /// Resolve all environment variable placeholders. If the variable is not found or is `None`, it is left unchanged.
46    pub fn resolve_placeholders(input: &str, environment: &Environment) -> String {
47        static BRACED: OnceLock<Regex> = OnceLock::new();
48        static BARE: OnceLock<Regex> = OnceLock::new();
49
50        let braced =
51            BRACED.get_or_init(|| Regex::new(r"\$\{([A-Za-z0-9_]+)\}").expect("cannot build regex"));
52        let bare = BARE.get_or_init(|| Regex::new(r"\$([A-Za-z0-9_]+)").expect("cannot build regex"));
53
54        // First replace ${VAR}
55        let step1 = braced.replace_all(input, |captures: &Captures| {
56            resolve_or_keep(&captures[0], &captures[1], environment)
57        });
58
59        // Then replace $VAR
60        bare.replace_all(&step1, |captures: &Captures| {
61            resolve_or_keep(&captures[0], &captures[1], environment)
62        })
63        .into_owned()
64    }
65
66    #[cfg(unix)]
67    fn resolve_or_keep(full: &str, name: &str, env: &Environment) -> String {
68        env.get(name)
69            .and_then(Clone::clone)
70            .or_else(|| std::env::var(name).ok())
71            .unwrap_or_else(|| full.to_string())
72    }
73}
74}