1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::collections::hash_map;
6
7use crate::error;
8use crate::shell;
9use crate::variables::{self, ShellValue, ShellValueUnsetType, ShellVariable};
10
11#[derive(Clone, Copy)]
13pub enum EnvironmentLookup {
14 Anywhere,
16 OnlyInGlobal,
18 OnlyInCurrentLocal,
20 OnlyInLocal,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum EnvironmentScope {
27 Local,
29 Global,
31 Command,
33}
34
35#[derive(Clone, Debug)]
37pub struct ShellEnvironment {
38 scopes: Vec<(EnvironmentScope, ShellVariableMap)>,
40 export_variables_on_modification: bool,
42 entry_count: usize,
44}
45
46impl Default for ShellEnvironment {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl ShellEnvironment {
53 pub fn new() -> Self {
55 Self {
56 scopes: vec![(EnvironmentScope::Global, ShellVariableMap::default())],
57 export_variables_on_modification: false,
58 entry_count: 0,
59 }
60 }
61
62 pub fn push_scope(&mut self, scope_type: EnvironmentScope) {
68 self.scopes.push((scope_type, ShellVariableMap::default()));
69 }
70
71 pub fn pop_scope(&mut self, expected_scope_type: EnvironmentScope) -> Result<(), error::Error> {
77 match self.scopes.pop() {
79 Some((actual_scope_type, _)) if actual_scope_type == expected_scope_type => Ok(()),
80 _ => Err(error::ErrorKind::MissingScope.into()),
81 }
82 }
83
84 pub fn iter_exported(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
90 let mut visible_vars: HashMap<&String, &ShellVariable> =
93 HashMap::with_capacity(self.entry_count);
94
95 for (_, var_map) in self.scopes.iter().rev() {
96 for (name, var) in var_map.iter().filter(|(_, v)| v.is_exported()) {
97 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
99 entry.insert(var);
100 }
101 }
102 }
103
104 visible_vars.into_iter()
105 }
106
107 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
109 self.iter_using_policy(EnvironmentLookup::Anywhere)
110 }
111
112 pub fn iter_using_policy(
119 &self,
120 lookup_policy: EnvironmentLookup,
121 ) -> impl Iterator<Item = (&String, &ShellVariable)> {
122 let mut visible_vars: HashMap<&String, &ShellVariable> =
125 HashMap::with_capacity(self.entry_count);
126
127 let mut local_count = 0;
128 for (scope_type, var_map) in self.scopes.iter().rev() {
129 if matches!(scope_type, EnvironmentScope::Local) {
130 local_count += 1;
131 }
132
133 match lookup_policy {
134 EnvironmentLookup::Anywhere => (),
135 EnvironmentLookup::OnlyInGlobal => {
136 if !matches!(scope_type, EnvironmentScope::Global) {
137 continue;
138 }
139 }
140 EnvironmentLookup::OnlyInCurrentLocal => {
141 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
142 continue;
143 }
144 }
145 EnvironmentLookup::OnlyInLocal => {
146 if !matches!(scope_type, EnvironmentScope::Local) {
147 continue;
148 }
149 }
150 }
151
152 for (name, var) in var_map.iter() {
153 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
155 entry.insert(var);
156 }
157 }
158
159 if matches!(scope_type, EnvironmentScope::Local)
160 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
161 {
162 break;
163 }
164 }
165
166 visible_vars.into_iter()
167 }
168
169 pub fn get<S: AsRef<str>>(&self, name: S) -> Option<(EnvironmentScope, &ShellVariable)> {
176 for (scope_type, map) in self.scopes.iter().rev() {
178 if let Some(var) = map.get(name.as_ref()) {
179 return Some((*scope_type, var));
180 }
181 }
182
183 None
184 }
185
186 pub fn get_mut<S: AsRef<str>>(
193 &mut self,
194 name: S,
195 ) -> Option<(EnvironmentScope, &mut ShellVariable)> {
196 for (scope_type, map) in self.scopes.iter_mut().rev() {
198 if let Some(var) = map.get_mut(name.as_ref()) {
199 return Some((*scope_type, var));
200 }
201 }
202
203 None
204 }
205
206 pub fn get_str<S: AsRef<str>>(&self, name: S, shell: &shell::Shell) -> Option<Cow<'_, str>> {
214 self.get(name.as_ref())
215 .map(|(_, v)| v.value().to_cow_str(shell))
216 }
217
218 pub fn is_set<S: AsRef<str>>(&self, name: S) -> bool {
224 if let Some((_, var)) = self.get(name) {
225 !matches!(var.value(), ShellValue::Unset(_))
226 } else {
227 false
228 }
229 }
230
231 pub fn unset(&mut self, name: &str) -> Result<Option<ShellVariable>, error::Error> {
242 let mut local_count = 0;
243 for (scope_type, map) in self.scopes.iter_mut().rev() {
244 if matches!(scope_type, EnvironmentScope::Local) {
245 local_count += 1;
246 }
247
248 let unset_result = Self::try_unset_in_map(map, name)?;
249
250 if unset_result.is_some() {
251 if matches!(scope_type, EnvironmentScope::Local) && local_count == 1 {
254 map.set(
255 name,
256 ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped)),
257 );
258 } else if self.entry_count > 0 {
259 self.entry_count -= 1;
261 }
262
263 return Ok(unset_result);
264 }
265 }
266
267 Ok(None)
268 }
269
270 pub fn unset_index(&mut self, name: &str, index: &str) -> Result<bool, error::Error> {
278 if let Some((_, var)) = self.get_mut(name) {
279 var.unset_index(index)
280 } else {
281 Ok(false)
282 }
283 }
284
285 fn try_unset_in_map(
286 map: &mut ShellVariableMap,
287 name: &str,
288 ) -> Result<Option<ShellVariable>, error::Error> {
289 match map.get(name).map(|v| v.is_readonly()) {
290 Some(true) => Err(error::ErrorKind::ReadonlyVariable.into()),
291 Some(false) => Ok(map.unset(name)),
292 None => Ok(None),
293 }
294 }
295
296 pub fn get_using_policy<N: AsRef<str>>(
304 &self,
305 name: N,
306 lookup_policy: EnvironmentLookup,
307 ) -> Option<&ShellVariable> {
308 let mut local_count = 0;
309 for (scope_type, var_map) in self.scopes.iter().rev() {
310 if matches!(scope_type, EnvironmentScope::Local) {
311 local_count += 1;
312 }
313
314 match lookup_policy {
315 EnvironmentLookup::Anywhere => (),
316 EnvironmentLookup::OnlyInGlobal => {
317 if !matches!(scope_type, EnvironmentScope::Global) {
318 continue;
319 }
320 }
321 EnvironmentLookup::OnlyInCurrentLocal => {
322 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
323 continue;
324 }
325 }
326 EnvironmentLookup::OnlyInLocal => {
327 if !matches!(scope_type, EnvironmentScope::Local) {
328 continue;
329 }
330 }
331 }
332
333 if let Some(var) = var_map.get(name.as_ref()) {
334 return Some(var);
335 }
336
337 if matches!(scope_type, EnvironmentScope::Local)
338 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
339 {
340 break;
341 }
342 }
343
344 None
345 }
346
347 pub fn get_mut_using_policy<N: AsRef<str>>(
355 &mut self,
356 name: N,
357 lookup_policy: EnvironmentLookup,
358 ) -> Option<&mut ShellVariable> {
359 let mut local_count = 0;
360 for (scope_type, var_map) in self.scopes.iter_mut().rev() {
361 if matches!(scope_type, EnvironmentScope::Local) {
362 local_count += 1;
363 }
364
365 match lookup_policy {
366 EnvironmentLookup::Anywhere => (),
367 EnvironmentLookup::OnlyInGlobal => {
368 if !matches!(scope_type, EnvironmentScope::Global) {
369 continue;
370 }
371 }
372 EnvironmentLookup::OnlyInCurrentLocal => {
373 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
374 continue;
375 }
376 }
377 EnvironmentLookup::OnlyInLocal => {
378 if !matches!(scope_type, EnvironmentScope::Local) {
379 continue;
380 }
381 }
382 }
383
384 if let Some(var) = var_map.get_mut(name.as_ref()) {
385 return Some(var);
386 }
387
388 if matches!(scope_type, EnvironmentScope::Local)
389 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
390 {
391 break;
392 }
393 }
394
395 None
396 }
397
398 pub fn update_or_add<N: Into<String>>(
408 &mut self,
409 name: N,
410 value: variables::ShellValueLiteral,
411 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
412 lookup_policy: EnvironmentLookup,
413 scope_if_creating: EnvironmentScope,
414 ) -> Result<(), error::Error> {
415 let name = name.into();
416
417 let auto_export = self.export_variables_on_modification;
418 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
419 var.assign(value, false)?;
420 if auto_export {
421 var.export();
422 }
423 updater(var)
424 } else {
425 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
426 var.assign(value, false)?;
427 if auto_export {
428 var.export();
429 }
430 updater(&mut var)?;
431
432 self.add(name, var, scope_if_creating)
433 }
434 }
435
436 pub fn update_or_add_array_element<N: Into<String>>(
447 &mut self,
448 name: N,
449 index: String,
450 value: String,
451 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
452 lookup_policy: EnvironmentLookup,
453 scope_if_creating: EnvironmentScope,
454 ) -> Result<(), error::Error> {
455 let name = name.into();
456
457 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
458 var.assign_at_index(index, value, false)?;
459 updater(var)
460 } else {
461 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
462 var.assign(
463 variables::ShellValueLiteral::Array(variables::ArrayLiteral(vec![(
464 Some(index),
465 value,
466 )])),
467 false,
468 )?;
469 updater(&mut var)?;
470
471 self.add(name, var, scope_if_creating)
472 }
473 }
474
475 pub fn add<N: Into<String>>(
483 &mut self,
484 name: N,
485 mut var: ShellVariable,
486 target_scope: EnvironmentScope,
487 ) -> Result<(), error::Error> {
488 if self.export_variables_on_modification {
489 var.export();
490 }
491
492 for (scope_type, map) in self.scopes.iter_mut().rev() {
493 if *scope_type == target_scope {
494 let prev_var = map.set(name, var);
495 if prev_var.is_none() {
496 self.entry_count += 1;
497 }
498
499 return Ok(());
500 }
501 }
502
503 Err(error::ErrorKind::MissingScope.into())
504 }
505
506 pub fn set_global<N: Into<String>>(
513 &mut self,
514 name: N,
515 var: ShellVariable,
516 ) -> Result<(), error::Error> {
517 self.add(name, var, EnvironmentScope::Global)
518 }
519}
520
521#[derive(Clone, Debug, Default)]
523pub struct ShellVariableMap {
524 variables: HashMap<String, ShellVariable>,
525}
526
527impl ShellVariableMap {
528 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
534 self.variables.iter()
535 }
536
537 pub fn get(&self, name: &str) -> Option<&ShellVariable> {
543 self.variables.get(name)
544 }
545
546 pub fn get_mut(&mut self, name: &str) -> Option<&mut ShellVariable> {
552 self.variables.get_mut(name)
553 }
554
555 pub fn unset(&mut self, name: &str) -> Option<ShellVariable> {
566 self.variables.remove(name)
567 }
568
569 pub fn set<N: Into<String>>(&mut self, name: N, var: ShellVariable) -> Option<ShellVariable> {
576 self.variables.insert(name.into(), var)
577 }
578}
579
580pub fn valid_variable_name(s: &str) -> bool {
582 let mut cs = s.chars();
583 match cs.next() {
584 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
585 cs.all(|c| c.is_ascii_alphanumeric() || c == '_')
586 }
587 Some(_) | None => false,
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594
595 #[test]
596 fn test_valid_variable_name() {
597 assert!(!valid_variable_name(""));
598 assert!(!valid_variable_name("1"));
599 assert!(!valid_variable_name(" a"));
600 assert!(!valid_variable_name(" "));
601
602 assert!(valid_variable_name("_"));
603 assert!(valid_variable_name("_a"));
604 assert!(valid_variable_name("_1"));
605 assert!(valid_variable_name("_a1"));
606 assert!(valid_variable_name("a"));
607 assert!(valid_variable_name("A"));
608 assert!(valid_variable_name("a1"));
609 assert!(valid_variable_name("A1"));
610 }
611}