1use std::borrow::Cow;
4use std::collections::{HashMap, hash_map};
5
6use crate::variables::{self, ShellValue, ShellValueUnsetType, ShellVariable};
7use crate::{Shell, error, extensions};
8
9#[derive(Clone, Copy)]
11pub enum EnvironmentLookup {
12 Anywhere,
14 OnlyInGlobal,
16 OnlyInCurrent(EnvironmentScope),
19 OnlyInLocal,
21}
22
23impl EnvironmentLookup {
24 fn scope_filter(self) -> impl FnMut(EnvironmentScope) -> bool {
27 let mut found_current = false;
28 move |scope_type| match self {
29 Self::Anywhere => true,
30 Self::OnlyInGlobal => scope_type == EnvironmentScope::Global,
31 Self::OnlyInLocal => scope_type == EnvironmentScope::Local,
32 Self::OnlyInCurrent(current) => {
33 let is_current = !found_current && scope_type == current;
34 found_current |= is_current;
35 is_current
36 }
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44pub enum EnvironmentScope {
45 Local,
47 Global,
49 Command,
51}
52
53impl std::fmt::Display for EnvironmentScope {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Self::Local => write!(f, "local"),
57 Self::Global => write!(f, "global"),
58 Self::Command => write!(f, "command"),
59 }
60 }
61}
62
63#[derive(Clone, Debug)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct ShellEnvironment {
67 scopes: Vec<(EnvironmentScope, ShellVariableMap)>,
69 export_variables_on_modification: bool,
71 entry_count: usize,
73}
74
75impl Default for ShellEnvironment {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl ShellEnvironment {
82 pub fn new() -> Self {
84 Self {
85 scopes: vec![(EnvironmentScope::Global, ShellVariableMap::default())],
86 export_variables_on_modification: false,
87 entry_count: 0,
88 }
89 }
90
91 pub(crate) const fn scope_depth(&self) -> usize {
93 self.scopes.len()
94 }
95
96 pub fn push_scope(&mut self, scope_type: EnvironmentScope) {
102 self.scopes.push((scope_type, ShellVariableMap::default()));
103 }
104
105 pub fn pop_scope(&mut self, expected_scope_type: EnvironmentScope) -> Result<(), error::Error> {
112 match self.scopes.last() {
116 Some((actual_scope_type, _)) if *actual_scope_type == expected_scope_type => {
117 self.scopes.pop();
118 Ok(())
119 }
120 Some((actual_scope_type, _)) => Err(error::ErrorKind::UnexpectedScopeType {
121 expected: expected_scope_type,
122 actual: *actual_scope_type,
123 }
124 .into()),
125 None => Err(error::ErrorKind::MissingScope.into()),
126 }
127 }
128
129 pub fn iter_exported(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
135 let mut visible_vars: HashMap<&String, &ShellVariable> =
138 HashMap::with_capacity(self.entry_count);
139
140 for (_, var_map) in self.scopes.iter().rev() {
141 for (name, var) in var_map.iter().filter(|(_, v)| v.is_exported()) {
142 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
144 entry.insert(var);
145 }
146 }
147 }
148
149 visible_vars.into_iter()
150 }
151
152 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
154 self.iter_using_policy(EnvironmentLookup::Anywhere)
155 }
156
157 pub fn iter_using_policy(
164 &self,
165 lookup_policy: EnvironmentLookup,
166 ) -> impl Iterator<Item = (&String, &ShellVariable)> {
167 let mut visible_vars: HashMap<&String, &ShellVariable> =
170 HashMap::with_capacity(self.entry_count);
171
172 let mut looks_in = lookup_policy.scope_filter();
173 for (_, var_map) in self.scopes.iter().rev().filter(|(t, _)| looks_in(*t)) {
174 for (name, var) in var_map.iter() {
175 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
177 entry.insert(var);
178 }
179 }
180 }
181
182 visible_vars.into_iter()
183 }
184
185 pub fn get<S: AsRef<str>>(&self, name: S) -> Option<(EnvironmentScope, &ShellVariable)> {
192 for (scope_type, map) in self.scopes.iter().rev() {
194 if let Some(var) = map.get(name.as_ref()) {
195 return Some((*scope_type, var));
196 }
197 }
198
199 None
200 }
201
202 pub fn get_mut<S: AsRef<str>>(
209 &mut self,
210 name: S,
211 ) -> Option<(EnvironmentScope, &mut ShellVariable)> {
212 for (scope_type, map) in self.scopes.iter_mut().rev() {
214 if let Some(var) = map.get_mut(name.as_ref()) {
215 return Some((*scope_type, var));
216 }
217 }
218
219 None
220 }
221
222 pub fn get_str<S: AsRef<str>, SE: extensions::ShellExtensions>(
230 &self,
231 name: S,
232 shell: &Shell<SE>,
233 ) -> Option<Cow<'_, str>> {
234 self.get(name.as_ref())
235 .map(|(_, v)| v.value().to_cow_str(shell))
236 }
237
238 pub fn is_set<S: AsRef<str>>(&self, name: S) -> bool {
244 if let Some((_, var)) = self.get(name) {
245 !matches!(var.value(), ShellValue::Unset(_))
246 } else {
247 false
248 }
249 }
250
251 pub fn unset(&mut self, name: &str) -> Result<Option<ShellVariable>, error::Error> {
262 let mut local_count = 0;
263 for (scope_type, map) in self.scopes.iter_mut().rev() {
264 if matches!(scope_type, EnvironmentScope::Local) {
265 local_count += 1;
266 }
267
268 let unset_result = Self::try_unset_in_map(map, name)?;
269
270 if unset_result.is_some() {
271 if matches!(scope_type, EnvironmentScope::Local) && local_count == 1 {
274 map.set(
275 name,
276 ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped)),
277 );
278 } else if self.entry_count > 0 {
279 self.entry_count -= 1;
282 }
283
284 return Ok(unset_result);
285 }
286 }
287
288 Ok(None)
289 }
290
291 pub fn unset_index(&mut self, name: &str, index: &str) -> Result<bool, error::Error> {
300 if let Some((_, var)) = self.get_mut(name) {
301 var.unset_index(index)
302 } else {
303 Ok(false)
304 }
305 }
306
307 fn try_unset_in_map(
308 map: &mut ShellVariableMap,
309 name: &str,
310 ) -> Result<Option<ShellVariable>, error::Error> {
311 match map.get(name).map(|v| v.is_readonly()) {
312 Some(true) => Err(error::ErrorKind::ReadonlyVariable.into()),
313 Some(false) => Ok(map.unset(name)),
314 None => Ok(None),
315 }
316 }
317
318 pub fn get_using_policy<N: AsRef<str>>(
326 &self,
327 name: N,
328 lookup_policy: EnvironmentLookup,
329 ) -> Option<&ShellVariable> {
330 let mut looks_in = lookup_policy.scope_filter();
331 self.scopes
332 .iter()
333 .rev()
334 .filter(|(t, _)| looks_in(*t))
335 .find_map(|(_, var_map)| var_map.get(name.as_ref()))
336 }
337
338 pub fn get_mut_using_policy<N: AsRef<str>>(
346 &mut self,
347 name: N,
348 lookup_policy: EnvironmentLookup,
349 ) -> Option<&mut ShellVariable> {
350 let mut looks_in = lookup_policy.scope_filter();
351 self.scopes
352 .iter_mut()
353 .rev()
354 .filter(|(t, _)| looks_in(*t))
355 .find_map(|(_, var_map)| var_map.get_mut(name.as_ref()))
356 }
357
358 pub fn update_or_add<N: Into<String>>(
371 &mut self,
372 name: N,
373 value: variables::ShellValueLiteral,
374 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
375 lookup_policy: EnvironmentLookup,
376 scope_if_creating: EnvironmentScope,
377 ) -> Result<(), error::Error> {
378 let name = name.into();
379
380 let auto_export = self.export_variables_on_modification;
381 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
382 var.assign(value, false)?;
383 if auto_export {
384 var.export();
385 }
386 updater(var)
387 } else {
388 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
389 var.assign(value, false)?;
390 if auto_export {
391 var.export();
392 }
393 updater(&mut var)?;
394
395 self.add(name, var, scope_if_creating)
396 }
397 }
398
399 pub fn update_or_add_array_element<N: Into<String>>(
413 &mut self,
414 name: N,
415 index: String,
416 value: String,
417 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
418 lookup_policy: EnvironmentLookup,
419 scope_if_creating: EnvironmentScope,
420 ) -> Result<(), error::Error> {
421 let name = name.into();
422
423 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
424 var.assign_at_index(index, value, false)?;
425 updater(var)
426 } else {
427 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
428 var.assign(
429 variables::ShellValueLiteral::Array(variables::ArrayLiteral(vec![(
430 Some(index),
431 value,
432 )])),
433 false,
434 )?;
435 updater(&mut var)?;
436
437 self.add(name, var, scope_if_creating)
438 }
439 }
440
441 pub fn add<N: Into<String>>(
449 &mut self,
450 name: N,
451 mut var: ShellVariable,
452 target_scope: EnvironmentScope,
453 ) -> Result<(), error::Error> {
454 if self.export_variables_on_modification {
455 var.export();
456 }
457
458 for (scope_type, map) in self.scopes.iter_mut().rev() {
459 if *scope_type == target_scope {
460 let prev_var = map.set(name, var);
461 if prev_var.is_none() {
462 self.entry_count += 1;
463 }
464
465 return Ok(());
466 }
467 }
468
469 Err(error::ErrorKind::MissingScopeForNewVariable.into())
470 }
471
472 pub fn set_global<N: Into<String>>(
479 &mut self,
480 name: N,
481 var: ShellVariable,
482 ) -> Result<(), error::Error> {
483 self.add(name, var, EnvironmentScope::Global)
484 }
485}
486
487#[derive(Clone, Debug, Default)]
489#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
490pub struct ShellVariableMap {
491 variables: HashMap<String, ShellVariable>,
492}
493
494impl ShellVariableMap {
495 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
501 self.variables.iter()
502 }
503
504 pub fn get(&self, name: &str) -> Option<&ShellVariable> {
511 self.variables.get(name)
512 }
513
514 pub fn get_mut(&mut self, name: &str) -> Option<&mut ShellVariable> {
521 self.variables.get_mut(name)
522 }
523
524 pub fn unset(&mut self, name: &str) -> Option<ShellVariable> {
535 self.variables.remove(name)
536 }
537
538 pub fn set<N: Into<String>>(&mut self, name: N, var: ShellVariable) -> Option<ShellVariable> {
545 self.variables.insert(name.into(), var)
546 }
547}
548
549pub fn valid_variable_name(s: &str) -> bool {
551 let mut cs = s.chars();
552 match cs.next() {
553 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
554 cs.all(|c| c.is_ascii_alphanumeric() || c == '_')
555 }
556 Some(_) | None => false,
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_valid_variable_name() {
566 assert!(!valid_variable_name(""));
567 assert!(!valid_variable_name("1"));
568 assert!(!valid_variable_name(" a"));
569 assert!(!valid_variable_name(" "));
570
571 assert!(valid_variable_name("_"));
572 assert!(valid_variable_name("_a"));
573 assert!(valid_variable_name("_1"));
574 assert!(valid_variable_name("_a1"));
575 assert!(valid_variable_name("a"));
576 assert!(valid_variable_name("A"));
577 assert!(valid_variable_name("a1"));
578 assert!(valid_variable_name("A1"));
579 }
580}