1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::collections::hash_map;
6
7use crate::Shell;
8use crate::error;
9use crate::extensions;
10use crate::variables::{self, ShellValue, ShellValueUnsetType, ShellVariable};
11
12#[derive(Clone, Copy)]
14pub enum EnvironmentLookup {
15 Anywhere,
17 OnlyInGlobal,
19 OnlyInCurrentLocal,
21 OnlyInLocal,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum EnvironmentScope {
29 Local,
31 Global,
33 Command,
35}
36
37impl std::fmt::Display for EnvironmentScope {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Local => write!(f, "local"),
41 Self::Global => write!(f, "global"),
42 Self::Command => write!(f, "command"),
43 }
44 }
45}
46
47pub(crate) struct ScopeGuard<'a, SE: extensions::ShellExtensions> {
49 scope_type: EnvironmentScope,
50 shell: &'a mut crate::Shell<SE>,
51 detached: bool,
52}
53
54impl<'a, SE: extensions::ShellExtensions> ScopeGuard<'a, SE> {
55 pub fn new(shell: &'a mut crate::Shell<SE>, scope_type: EnvironmentScope) -> Self {
62 shell.env_mut().push_scope(scope_type);
63 Self {
64 scope_type,
65 shell,
66 detached: false,
67 }
68 }
69
70 pub const fn shell(&mut self) -> &mut crate::Shell<SE> {
72 self.shell
73 }
74
75 pub const fn detach(&mut self) {
77 self.detached = true;
78 }
79}
80
81impl<SE: extensions::ShellExtensions> Drop for ScopeGuard<'_, SE> {
82 fn drop(&mut self) {
83 if !self.detached {
84 let _ = self.shell.env_mut().pop_scope(self.scope_type);
85 }
86 }
87}
88
89#[derive(Clone, Debug)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub struct ShellEnvironment {
93 scopes: Vec<(EnvironmentScope, ShellVariableMap)>,
95 export_variables_on_modification: bool,
97 entry_count: usize,
99}
100
101impl Default for ShellEnvironment {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl ShellEnvironment {
108 pub fn new() -> Self {
110 Self {
111 scopes: vec![(EnvironmentScope::Global, ShellVariableMap::default())],
112 export_variables_on_modification: false,
113 entry_count: 0,
114 }
115 }
116
117 pub fn push_scope(&mut self, scope_type: EnvironmentScope) {
123 self.scopes.push((scope_type, ShellVariableMap::default()));
124 }
125
126 pub fn pop_scope(&mut self, expected_scope_type: EnvironmentScope) -> Result<(), error::Error> {
132 match self.scopes.pop() {
134 Some((actual_scope_type, _)) if actual_scope_type == expected_scope_type => Ok(()),
135 Some((actual_scope_type, _)) => Err(error::ErrorKind::UnexpectedScopeType {
136 expected: expected_scope_type,
137 actual: actual_scope_type,
138 }
139 .into()),
140 None => Err(error::ErrorKind::MissingScope.into()),
141 }
142 }
143
144 pub fn iter_exported(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
150 let mut visible_vars: HashMap<&String, &ShellVariable> =
153 HashMap::with_capacity(self.entry_count);
154
155 for (_, var_map) in self.scopes.iter().rev() {
156 for (name, var) in var_map.iter().filter(|(_, v)| v.is_exported()) {
157 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
159 entry.insert(var);
160 }
161 }
162 }
163
164 visible_vars.into_iter()
165 }
166
167 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
169 self.iter_using_policy(EnvironmentLookup::Anywhere)
170 }
171
172 pub fn iter_using_policy(
179 &self,
180 lookup_policy: EnvironmentLookup,
181 ) -> impl Iterator<Item = (&String, &ShellVariable)> {
182 let mut visible_vars: HashMap<&String, &ShellVariable> =
185 HashMap::with_capacity(self.entry_count);
186
187 let mut local_count = 0;
188 for (scope_type, var_map) in self.scopes.iter().rev() {
189 if matches!(scope_type, EnvironmentScope::Local) {
190 local_count += 1;
191 }
192
193 match lookup_policy {
194 EnvironmentLookup::Anywhere => (),
195 EnvironmentLookup::OnlyInGlobal => {
196 if !matches!(scope_type, EnvironmentScope::Global) {
197 continue;
198 }
199 }
200 EnvironmentLookup::OnlyInCurrentLocal => {
201 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
202 continue;
203 }
204 }
205 EnvironmentLookup::OnlyInLocal => {
206 if !matches!(scope_type, EnvironmentScope::Local) {
207 continue;
208 }
209 }
210 }
211
212 for (name, var) in var_map.iter() {
213 if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
215 entry.insert(var);
216 }
217 }
218
219 if matches!(scope_type, EnvironmentScope::Local)
220 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
221 {
222 break;
223 }
224 }
225
226 visible_vars.into_iter()
227 }
228
229 pub fn get<S: AsRef<str>>(&self, name: S) -> Option<(EnvironmentScope, &ShellVariable)> {
236 for (scope_type, map) in self.scopes.iter().rev() {
238 if let Some(var) = map.get(name.as_ref()) {
239 return Some((*scope_type, var));
240 }
241 }
242
243 None
244 }
245
246 pub fn get_mut<S: AsRef<str>>(
253 &mut self,
254 name: S,
255 ) -> Option<(EnvironmentScope, &mut ShellVariable)> {
256 for (scope_type, map) in self.scopes.iter_mut().rev() {
258 if let Some(var) = map.get_mut(name.as_ref()) {
259 return Some((*scope_type, var));
260 }
261 }
262
263 None
264 }
265
266 pub fn get_str<S: AsRef<str>, SE: extensions::ShellExtensions>(
274 &self,
275 name: S,
276 shell: &Shell<SE>,
277 ) -> Option<Cow<'_, str>> {
278 self.get(name.as_ref())
279 .map(|(_, v)| v.value().to_cow_str(shell))
280 }
281
282 pub fn is_set<S: AsRef<str>>(&self, name: S) -> bool {
288 if let Some((_, var)) = self.get(name) {
289 !matches!(var.value(), ShellValue::Unset(_))
290 } else {
291 false
292 }
293 }
294
295 pub fn unset(&mut self, name: &str) -> Result<Option<ShellVariable>, error::Error> {
306 let mut local_count = 0;
307 for (scope_type, map) in self.scopes.iter_mut().rev() {
308 if matches!(scope_type, EnvironmentScope::Local) {
309 local_count += 1;
310 }
311
312 let unset_result = Self::try_unset_in_map(map, name)?;
313
314 if unset_result.is_some() {
315 if matches!(scope_type, EnvironmentScope::Local) && local_count == 1 {
318 map.set(
319 name,
320 ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped)),
321 );
322 } else if self.entry_count > 0 {
323 self.entry_count -= 1;
325 }
326
327 return Ok(unset_result);
328 }
329 }
330
331 Ok(None)
332 }
333
334 pub fn unset_index(&mut self, name: &str, index: &str) -> Result<bool, error::Error> {
342 if let Some((_, var)) = self.get_mut(name) {
343 var.unset_index(index)
344 } else {
345 Ok(false)
346 }
347 }
348
349 fn try_unset_in_map(
350 map: &mut ShellVariableMap,
351 name: &str,
352 ) -> Result<Option<ShellVariable>, error::Error> {
353 match map.get(name).map(|v| v.is_readonly()) {
354 Some(true) => Err(error::ErrorKind::ReadonlyVariable.into()),
355 Some(false) => Ok(map.unset(name)),
356 None => Ok(None),
357 }
358 }
359
360 pub fn get_using_policy<N: AsRef<str>>(
368 &self,
369 name: N,
370 lookup_policy: EnvironmentLookup,
371 ) -> Option<&ShellVariable> {
372 let mut local_count = 0;
373 for (scope_type, var_map) in self.scopes.iter().rev() {
374 if matches!(scope_type, EnvironmentScope::Local) {
375 local_count += 1;
376 }
377
378 match lookup_policy {
379 EnvironmentLookup::Anywhere => (),
380 EnvironmentLookup::OnlyInGlobal => {
381 if !matches!(scope_type, EnvironmentScope::Global) {
382 continue;
383 }
384 }
385 EnvironmentLookup::OnlyInCurrentLocal => {
386 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
387 continue;
388 }
389 }
390 EnvironmentLookup::OnlyInLocal => {
391 if !matches!(scope_type, EnvironmentScope::Local) {
392 continue;
393 }
394 }
395 }
396
397 if let Some(var) = var_map.get(name.as_ref()) {
398 return Some(var);
399 }
400
401 if matches!(scope_type, EnvironmentScope::Local)
402 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
403 {
404 break;
405 }
406 }
407
408 None
409 }
410
411 pub fn get_mut_using_policy<N: AsRef<str>>(
419 &mut self,
420 name: N,
421 lookup_policy: EnvironmentLookup,
422 ) -> Option<&mut ShellVariable> {
423 let mut local_count = 0;
424 for (scope_type, var_map) in self.scopes.iter_mut().rev() {
425 if matches!(scope_type, EnvironmentScope::Local) {
426 local_count += 1;
427 }
428
429 match lookup_policy {
430 EnvironmentLookup::Anywhere => (),
431 EnvironmentLookup::OnlyInGlobal => {
432 if !matches!(scope_type, EnvironmentScope::Global) {
433 continue;
434 }
435 }
436 EnvironmentLookup::OnlyInCurrentLocal => {
437 if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
438 continue;
439 }
440 }
441 EnvironmentLookup::OnlyInLocal => {
442 if !matches!(scope_type, EnvironmentScope::Local) {
443 continue;
444 }
445 }
446 }
447
448 if let Some(var) = var_map.get_mut(name.as_ref()) {
449 return Some(var);
450 }
451
452 if matches!(scope_type, EnvironmentScope::Local)
453 && matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
454 {
455 break;
456 }
457 }
458
459 None
460 }
461
462 pub fn update_or_add<N: Into<String>>(
472 &mut self,
473 name: N,
474 value: variables::ShellValueLiteral,
475 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
476 lookup_policy: EnvironmentLookup,
477 scope_if_creating: EnvironmentScope,
478 ) -> Result<(), error::Error> {
479 let name = name.into();
480
481 let auto_export = self.export_variables_on_modification;
482 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
483 var.assign(value, false)?;
484 if auto_export {
485 var.export();
486 }
487 updater(var)
488 } else {
489 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
490 var.assign(value, false)?;
491 if auto_export {
492 var.export();
493 }
494 updater(&mut var)?;
495
496 self.add(name, var, scope_if_creating)
497 }
498 }
499
500 pub fn update_or_add_array_element<N: Into<String>>(
511 &mut self,
512 name: N,
513 index: String,
514 value: String,
515 updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
516 lookup_policy: EnvironmentLookup,
517 scope_if_creating: EnvironmentScope,
518 ) -> Result<(), error::Error> {
519 let name = name.into();
520
521 if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
522 var.assign_at_index(index, value, false)?;
523 updater(var)
524 } else {
525 let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
526 var.assign(
527 variables::ShellValueLiteral::Array(variables::ArrayLiteral(vec![(
528 Some(index),
529 value,
530 )])),
531 false,
532 )?;
533 updater(&mut var)?;
534
535 self.add(name, var, scope_if_creating)
536 }
537 }
538
539 pub fn add<N: Into<String>>(
547 &mut self,
548 name: N,
549 mut var: ShellVariable,
550 target_scope: EnvironmentScope,
551 ) -> Result<(), error::Error> {
552 if self.export_variables_on_modification {
553 var.export();
554 }
555
556 for (scope_type, map) in self.scopes.iter_mut().rev() {
557 if *scope_type == target_scope {
558 let prev_var = map.set(name, var);
559 if prev_var.is_none() {
560 self.entry_count += 1;
561 }
562
563 return Ok(());
564 }
565 }
566
567 Err(error::ErrorKind::MissingScopeForNewVariable.into())
568 }
569
570 pub fn set_global<N: Into<String>>(
577 &mut self,
578 name: N,
579 var: ShellVariable,
580 ) -> Result<(), error::Error> {
581 self.add(name, var, EnvironmentScope::Global)
582 }
583}
584
585#[derive(Clone, Debug, Default)]
587#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
588pub struct ShellVariableMap {
589 variables: HashMap<String, ShellVariable>,
590}
591
592impl ShellVariableMap {
593 pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
599 self.variables.iter()
600 }
601
602 pub fn get(&self, name: &str) -> Option<&ShellVariable> {
608 self.variables.get(name)
609 }
610
611 pub fn get_mut(&mut self, name: &str) -> Option<&mut ShellVariable> {
617 self.variables.get_mut(name)
618 }
619
620 pub fn unset(&mut self, name: &str) -> Option<ShellVariable> {
631 self.variables.remove(name)
632 }
633
634 pub fn set<N: Into<String>>(&mut self, name: N, var: ShellVariable) -> Option<ShellVariable> {
641 self.variables.insert(name.into(), var)
642 }
643}
644
645pub fn valid_variable_name(s: &str) -> bool {
647 let mut cs = s.chars();
648 match cs.next() {
649 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
650 cs.all(|c| c.is_ascii_alphanumeric() || c == '_')
651 }
652 Some(_) | None => false,
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659
660 #[test]
661 fn test_valid_variable_name() {
662 assert!(!valid_variable_name(""));
663 assert!(!valid_variable_name("1"));
664 assert!(!valid_variable_name(" a"));
665 assert!(!valid_variable_name(" "));
666
667 assert!(valid_variable_name("_"));
668 assert!(valid_variable_name("_a"));
669 assert!(valid_variable_name("_1"));
670 assert!(valid_variable_name("_a1"));
671 assert!(valid_variable_name("a"));
672 assert!(valid_variable_name("A"));
673 assert!(valid_variable_name("a1"));
674 assert!(valid_variable_name("A1"));
675 }
676}