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 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 let step1 = braced.replace_all(input, |captures: &Captures| {
56 resolve_or_keep(&captures[0], &captures[1], environment)
57 });
58
59 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}