1use crate::config_types::EnvironmentVariablePattern;
2use crate::config_types::ShellEnvironmentPolicy;
3use crate::config_types::ShellEnvironmentPolicyInherit;
4use std::collections::HashMap;
5use std::collections::HashSet;
6
7pub fn create_env(policy: &ShellEnvironmentPolicy) -> HashMap<String, String> {
15 populate_env(std::env::vars(), policy)
16}
17
18fn populate_env<I>(vars: I, policy: &ShellEnvironmentPolicy) -> HashMap<String, String>
19where
20 I: IntoIterator<Item = (String, String)>,
21{
22 let mut env_map: HashMap<String, String> = match policy.inherit {
25 ShellEnvironmentPolicyInherit::All => vars.into_iter().collect(),
26 ShellEnvironmentPolicyInherit::None => HashMap::new(),
27 ShellEnvironmentPolicyInherit::Core => {
28 const CORE_VARS: &[&str] = &[
29 "HOME", "LOGNAME", "PATH", "SHELL", "USER", "USERNAME", "TMPDIR", "TEMP", "TMP",
30 ];
31 let allow: HashSet<&str> = CORE_VARS.iter().copied().collect();
32 vars.into_iter()
33 .filter(|(k, _)| allow.contains(k.as_str()))
34 .collect()
35 }
36 };
37
38 let matches_any = |name: &str, patterns: &[EnvironmentVariablePattern]| -> bool {
40 patterns.iter().any(|pattern| pattern.matches(name))
41 };
42
43 if !policy.ignore_default_excludes {
45 let default_excludes = vec![
46 EnvironmentVariablePattern::new_case_insensitive("*KEY*"),
47 EnvironmentVariablePattern::new_case_insensitive("*SECRET*"),
48 EnvironmentVariablePattern::new_case_insensitive("*TOKEN*"),
49 ];
50 env_map.retain(|k, _| !matches_any(k, &default_excludes));
51 }
52
53 if !policy.exclude.is_empty() {
55 env_map.retain(|k, _| !matches_any(k, &policy.exclude));
56 }
57
58 for (key, val) in &policy.r#set {
60 env_map.insert(key.clone(), val.clone());
61 }
62
63 if !policy.include_only.is_empty() {
65 env_map.retain(|k, _| matches_any(k, &policy.include_only));
66 }
67
68 env_map
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::config_types::ShellEnvironmentPolicyInherit;
75 use maplit::hashmap;
76
77 fn make_vars(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
78 pairs
79 .iter()
80 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
81 .collect()
82 }
83
84 #[test]
85 fn test_core_inherit_and_default_excludes() {
86 let vars = make_vars(&[
87 ("PATH", "/usr/bin"),
88 ("HOME", "/home/user"),
89 ("API_KEY", "secret"),
90 ("SECRET_TOKEN", "t"),
91 ]);
92
93 let policy = ShellEnvironmentPolicy::default(); let result = populate_env(vars, &policy);
95
96 let expected: HashMap<String, String> = hashmap! {
97 "PATH".to_string() => "/usr/bin".to_string(),
98 "HOME".to_string() => "/home/user".to_string(),
99 };
100
101 assert_eq!(result, expected);
102 }
103
104 #[test]
105 fn test_include_only() {
106 let vars = make_vars(&[("PATH", "/usr/bin"), ("FOO", "bar")]);
107
108 let policy = ShellEnvironmentPolicy {
109 ignore_default_excludes: true,
111 include_only: vec![EnvironmentVariablePattern::new_case_insensitive("*PATH")],
112 ..Default::default()
113 };
114
115 let result = populate_env(vars, &policy);
116
117 let expected: HashMap<String, String> = hashmap! {
118 "PATH".to_string() => "/usr/bin".to_string(),
119 };
120
121 assert_eq!(result, expected);
122 }
123
124 #[test]
125 fn test_set_overrides() {
126 let vars = make_vars(&[("PATH", "/usr/bin")]);
127
128 let mut policy = ShellEnvironmentPolicy {
129 ignore_default_excludes: true,
130 ..Default::default()
131 };
132 policy.r#set.insert("NEW_VAR".to_string(), "42".to_string());
133
134 let result = populate_env(vars, &policy);
135
136 let expected: HashMap<String, String> = hashmap! {
137 "PATH".to_string() => "/usr/bin".to_string(),
138 "NEW_VAR".to_string() => "42".to_string(),
139 };
140
141 assert_eq!(result, expected);
142 }
143
144 #[test]
145 fn test_inherit_all() {
146 let vars = make_vars(&[("PATH", "/usr/bin"), ("FOO", "bar")]);
147
148 let policy = ShellEnvironmentPolicy {
149 inherit: ShellEnvironmentPolicyInherit::All,
150 ignore_default_excludes: true, ..Default::default()
152 };
153
154 let result = populate_env(vars.clone(), &policy);
155 let expected: HashMap<String, String> = vars.into_iter().collect();
156 assert_eq!(result, expected);
157 }
158
159 #[test]
160 fn test_inherit_all_with_default_excludes() {
161 let vars = make_vars(&[("PATH", "/usr/bin"), ("API_KEY", "secret")]);
162
163 let policy = ShellEnvironmentPolicy {
164 inherit: ShellEnvironmentPolicyInherit::All,
165 ..Default::default()
166 };
167
168 let result = populate_env(vars, &policy);
169 let expected: HashMap<String, String> = hashmap! {
170 "PATH".to_string() => "/usr/bin".to_string(),
171 };
172 assert_eq!(result, expected);
173 }
174
175 #[test]
176 fn test_inherit_none() {
177 let vars = make_vars(&[("PATH", "/usr/bin"), ("HOME", "/home")]);
178
179 let mut policy = ShellEnvironmentPolicy {
180 inherit: ShellEnvironmentPolicyInherit::None,
181 ignore_default_excludes: true,
182 ..Default::default()
183 };
184 policy
185 .r#set
186 .insert("ONLY_VAR".to_string(), "yes".to_string());
187
188 let result = populate_env(vars, &policy);
189 let expected: HashMap<String, String> = hashmap! {
190 "ONLY_VAR".to_string() => "yes".to_string(),
191 };
192 assert_eq!(result, expected);
193 }
194}