nu_protocol/engine/
stack.rs

1use crate::{
2    Config, ENV_VARIABLE_ID, IntoValue, NU_VARIABLE_ID, OutDest, ShellError, Span, Value, VarId,
3    engine::{
4        ArgumentStack, DEFAULT_OVERLAY_NAME, EngineState, ErrorHandlerStack, Redirection,
5        StackCallArgGuard, StackCollectValueGuard, StackIoGuard, StackOutDest,
6    },
7    report_shell_warning,
8};
9use nu_utils::IgnoreCaseExt;
10use std::{
11    collections::{HashMap, HashSet},
12    fs::File,
13    path::{Component, MAIN_SEPARATOR},
14    sync::Arc,
15};
16
17/// Environment variables per overlay
18pub type EnvVars = HashMap<String, HashMap<String, Value>>;
19
20/// A runtime value stack used during evaluation
21///
22/// A note on implementation:
23///
24/// We previously set up the stack in a traditional way, where stack frames had parents which would
25/// represent other frames that you might return to when exiting a function.
26///
27/// While experimenting with blocks, we found that we needed to have closure captures of variables
28/// seen outside of the blocks, so that they blocks could be run in a way that was both thread-safe
29/// and followed the restrictions for closures applied to iterators. The end result left us with
30/// closure-captured single stack frames that blocks could see.
31///
32/// Blocks make up the only scope and stack definition abstraction in Nushell. As a result, we were
33/// creating closure captures at any point we wanted to have a Block value we could safely evaluate
34/// in any context. This meant that the parents were going largely unused, with captured variables
35/// taking their place. The end result is this, where we no longer have separate frames, but instead
36/// use the Stack as a way of representing the local and closure-captured state.
37#[derive(Debug, Clone)]
38pub struct Stack {
39    /// Variables
40    pub vars: Vec<(VarId, Value)>,
41    /// Environment variables arranged as a stack to be able to recover values from parent scopes
42    pub env_vars: Vec<Arc<EnvVars>>,
43    /// Tells which environment variables from engine state are hidden, per overlay.
44    pub env_hidden: Arc<HashMap<String, HashSet<String>>>,
45    /// List of active overlays
46    pub active_overlays: Vec<String>,
47    /// Argument stack for IR evaluation
48    pub arguments: ArgumentStack,
49    /// Error handler stack for IR evaluation
50    pub error_handlers: ErrorHandlerStack,
51    pub recursion_count: u64,
52    pub parent_stack: Option<Arc<Stack>>,
53    /// Variables that have been deleted (this is used to hide values from parent stack lookups)
54    pub parent_deletions: Vec<VarId>,
55    /// Locally updated config. Use [`.get_config()`](Self::get_config) to access correctly.
56    pub config: Option<Arc<Config>>,
57    pub(crate) out_dest: StackOutDest,
58}
59
60impl Default for Stack {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl Stack {
67    /// Create a new stack.
68    ///
69    /// stdout and stderr will be set to [`OutDest::Inherit`]. So, if the last command is an external command,
70    /// then its output will be forwarded to the terminal/stdio streams.
71    ///
72    /// Use [`Stack::collect_value`] afterwards if you need to evaluate an expression to a [`Value`]
73    /// (as opposed to a [`PipelineData`](crate::PipelineData)).
74    pub fn new() -> Self {
75        Self {
76            vars: Vec::new(),
77            env_vars: Vec::new(),
78            env_hidden: Arc::new(HashMap::new()),
79            active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
80            arguments: ArgumentStack::new(),
81            error_handlers: ErrorHandlerStack::new(),
82            recursion_count: 0,
83            parent_stack: None,
84            parent_deletions: vec![],
85            config: None,
86            out_dest: StackOutDest::new(),
87        }
88    }
89
90    /// Create a new child stack from a parent.
91    ///
92    /// Changes from this child can be merged back into the parent with
93    /// [`Stack::with_changes_from_child`]
94    pub fn with_parent(parent: Arc<Stack>) -> Stack {
95        Stack {
96            // here we are still cloning environment variable-related information
97            env_vars: parent.env_vars.clone(),
98            env_hidden: parent.env_hidden.clone(),
99            active_overlays: parent.active_overlays.clone(),
100            arguments: ArgumentStack::new(),
101            error_handlers: ErrorHandlerStack::new(),
102            recursion_count: parent.recursion_count,
103            vars: vec![],
104            parent_deletions: vec![],
105            config: parent.config.clone(),
106            out_dest: parent.out_dest.clone(),
107            parent_stack: Some(parent),
108        }
109    }
110
111    /// Take an [`Arc`] parent, and a child, and apply all the changes from a child back to the parent.
112    ///
113    /// Here it is assumed that `child` was created by a call to [`Stack::with_parent`] with `parent`.
114    ///
115    /// For this to be performant and not clone `parent`, `child` should be the only other
116    /// referencer of `parent`.
117    pub fn with_changes_from_child(parent: Arc<Stack>, child: Stack) -> Stack {
118        // we're going to drop the link to the parent stack on our new stack
119        // so that we can unwrap the Arc as a unique reference
120        drop(child.parent_stack);
121        let mut unique_stack = Arc::unwrap_or_clone(parent);
122
123        unique_stack
124            .vars
125            .retain(|(var, _)| !child.parent_deletions.contains(var));
126        for (var, value) in child.vars {
127            unique_stack.add_var(var, value);
128        }
129        unique_stack.env_vars = child.env_vars;
130        unique_stack.env_hidden = child.env_hidden;
131        unique_stack.active_overlays = child.active_overlays;
132        unique_stack.config = child.config;
133        unique_stack
134    }
135
136    pub fn with_env(
137        &mut self,
138        env_vars: &[Arc<EnvVars>],
139        env_hidden: &Arc<HashMap<String, HashSet<String>>>,
140    ) {
141        // Do not clone the environment if it hasn't changed
142        if self.env_vars.iter().any(|scope| !scope.is_empty()) {
143            env_vars.clone_into(&mut self.env_vars);
144        }
145
146        if !self.env_hidden.is_empty() {
147            self.env_hidden.clone_from(env_hidden);
148        }
149    }
150
151    /// Lookup a variable, returning None if it is not present
152    fn lookup_var(&self, var_id: VarId) -> Option<Value> {
153        for (id, val) in &self.vars {
154            if var_id == *id {
155                return Some(val.clone());
156            }
157        }
158
159        if let Some(stack) = &self.parent_stack {
160            if !self.parent_deletions.contains(&var_id) {
161                return stack.lookup_var(var_id);
162            }
163        }
164        None
165    }
166
167    /// Lookup a variable, erroring if it is not found
168    ///
169    /// The passed-in span will be used to tag the value
170    pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
171        match self.lookup_var(var_id) {
172            Some(v) => Ok(v.with_span(span)),
173            None => Err(ShellError::VariableNotFoundAtRuntime { span }),
174        }
175    }
176
177    /// Lookup a variable, erroring if it is not found
178    ///
179    /// While the passed-in span will be used for errors, the returned value
180    /// has the span from where it was originally defined
181    pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
182        match self.lookup_var(var_id) {
183            Some(v) => Ok(v),
184            None => {
185                if var_id == NU_VARIABLE_ID || var_id == ENV_VARIABLE_ID {
186                    return Err(ShellError::GenericError {
187                        error: "Built-in variables `$env` and `$nu` have no metadata".into(),
188                        msg: "no metadata available".into(),
189                        span: Some(span),
190                        help: None,
191                        inner: vec![],
192                    });
193                }
194                Err(ShellError::VariableNotFoundAtRuntime { span })
195            }
196        }
197    }
198
199    /// Get the local config if set, otherwise the config from the engine state.
200    ///
201    /// This is the canonical way to get [`Config`] when [`Stack`] is available.
202    pub fn get_config(&self, engine_state: &EngineState) -> Arc<Config> {
203        self.config
204            .clone()
205            .unwrap_or_else(|| engine_state.config.clone())
206    }
207
208    /// Update the local config with the config stored in the `config` environment variable. Run
209    /// this after assigning to `$env.config`.
210    ///
211    /// The config will be updated with successfully parsed values even if an error occurs.
212    pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> {
213        if let Some(value) = self.get_env_var(engine_state, "config") {
214            let old = self.get_config(engine_state);
215            let mut config = (*old).clone();
216            let result = config.update_from_value(&old, value);
217            // The config value is modified by the update, so we should add it again
218            self.add_env_var("config".into(), config.clone().into_value(value.span()));
219            self.config = Some(config.into());
220            if let Some(warning) = result? {
221                report_shell_warning(engine_state, &warning);
222            }
223        } else {
224            self.config = None;
225        }
226        Ok(())
227    }
228
229    pub fn add_var(&mut self, var_id: VarId, value: Value) {
230        //self.vars.insert(var_id, value);
231        for (id, val) in &mut self.vars {
232            if *id == var_id {
233                *val = value;
234                return;
235            }
236        }
237        self.vars.push((var_id, value));
238    }
239
240    pub fn remove_var(&mut self, var_id: VarId) {
241        for (idx, (id, _)) in self.vars.iter().enumerate() {
242            if *id == var_id {
243                self.vars.remove(idx);
244                break;
245            }
246        }
247        // even if we did have it in the original layer, we need to make sure to remove it here
248        // as well (since the previous update might have simply hid the parent value)
249        if self.parent_stack.is_some() {
250            self.parent_deletions.push(var_id);
251        }
252    }
253
254    pub fn add_env_var(&mut self, var: String, value: Value) {
255        if let Some(last_overlay) = self.active_overlays.last() {
256            if let Some(env_hidden) = Arc::make_mut(&mut self.env_hidden).get_mut(last_overlay) {
257                // if the env var was hidden, let's activate it again
258                env_hidden.remove(&var);
259            }
260
261            if let Some(scope) = self.env_vars.last_mut() {
262                let scope = Arc::make_mut(scope);
263                if let Some(env_vars) = scope.get_mut(last_overlay) {
264                    env_vars.insert(var, value);
265                } else {
266                    scope.insert(last_overlay.into(), [(var, value)].into_iter().collect());
267                }
268            } else {
269                self.env_vars.push(Arc::new(
270                    [(last_overlay.into(), [(var, value)].into_iter().collect())]
271                        .into_iter()
272                        .collect(),
273                ));
274            }
275        } else {
276            // TODO: Remove panic
277            panic!("internal error: no active overlay");
278        }
279    }
280
281    pub fn set_last_exit_code(&mut self, code: i32, span: Span) {
282        self.add_env_var("LAST_EXIT_CODE".into(), Value::int(code.into(), span));
283    }
284
285    pub fn set_last_error(&mut self, error: &ShellError) {
286        if let Some(code) = error.external_exit_code() {
287            self.set_last_exit_code(code.item, code.span);
288        } else if let Some(code) = error.exit_code() {
289            self.set_last_exit_code(code, Span::unknown());
290        }
291    }
292
293    pub fn last_overlay_name(&self) -> Result<String, ShellError> {
294        self.active_overlays
295            .last()
296            .cloned()
297            .ok_or_else(|| ShellError::NushellFailed {
298                msg: "No active overlay".into(),
299            })
300    }
301
302    pub fn captures_to_stack(&self, captures: Vec<(VarId, Value)>) -> Stack {
303        self.captures_to_stack_preserve_out_dest(captures)
304            .collect_value()
305    }
306
307    pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack {
308        let mut env_vars = self.env_vars.clone();
309        env_vars.push(Arc::new(HashMap::new()));
310
311        Stack {
312            vars: captures,
313            env_vars,
314            env_hidden: self.env_hidden.clone(),
315            active_overlays: self.active_overlays.clone(),
316            arguments: ArgumentStack::new(),
317            error_handlers: ErrorHandlerStack::new(),
318            recursion_count: self.recursion_count,
319            parent_stack: None,
320            parent_deletions: vec![],
321            config: self.config.clone(),
322            out_dest: self.out_dest.clone(),
323        }
324    }
325
326    pub fn gather_captures(&self, engine_state: &EngineState, captures: &[(VarId, Span)]) -> Stack {
327        let mut vars = Vec::with_capacity(captures.len());
328
329        let fake_span = Span::new(0, 0);
330
331        for (capture, _) in captures {
332            // Note: this assumes we have calculated captures correctly and that commands
333            // that take in a var decl will manually set this into scope when running the blocks
334            if let Ok(value) = self.get_var(*capture, fake_span) {
335                vars.push((*capture, value));
336            } else if let Some(const_val) = &engine_state.get_var(*capture).const_val {
337                vars.push((*capture, const_val.clone()));
338            }
339        }
340
341        let mut env_vars = self.env_vars.clone();
342        env_vars.push(Arc::new(HashMap::new()));
343
344        Stack {
345            vars,
346            env_vars,
347            env_hidden: self.env_hidden.clone(),
348            active_overlays: self.active_overlays.clone(),
349            arguments: ArgumentStack::new(),
350            error_handlers: ErrorHandlerStack::new(),
351            recursion_count: self.recursion_count,
352            parent_stack: None,
353            parent_deletions: vec![],
354            config: self.config.clone(),
355            out_dest: self.out_dest.clone(),
356        }
357    }
358
359    /// Flatten the env var scope frames into one frame
360    pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
361        let mut result = HashMap::new();
362
363        for active_overlay in self.active_overlays.iter() {
364            if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
365                result.extend(
366                    env_vars
367                        .iter()
368                        .filter(|(k, _)| {
369                            if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
370                                !env_hidden.contains(*k)
371                            } else {
372                                // nothing has been hidden in this overlay
373                                true
374                            }
375                        })
376                        .map(|(k, v)| (k.clone(), v.clone()))
377                        .collect::<HashMap<String, Value>>(),
378                );
379            }
380        }
381
382        result.extend(self.get_stack_env_vars());
383
384        result
385    }
386
387    /// Get flattened environment variables only from the stack
388    pub fn get_stack_env_vars(&self) -> HashMap<String, Value> {
389        let mut result = HashMap::new();
390
391        for scope in &self.env_vars {
392            for active_overlay in self.active_overlays.iter() {
393                if let Some(env_vars) = scope.get(active_overlay) {
394                    result.extend(env_vars.clone());
395                }
396            }
397        }
398
399        result
400    }
401
402    /// Get flattened environment variables only from the stack and one overlay
403    pub fn get_stack_overlay_env_vars(&self, overlay_name: &str) -> HashMap<String, Value> {
404        let mut result = HashMap::new();
405
406        for scope in &self.env_vars {
407            if let Some(active_overlay) = self.active_overlays.iter().find(|n| n == &overlay_name) {
408                if let Some(env_vars) = scope.get(active_overlay) {
409                    result.extend(env_vars.clone());
410                }
411            }
412        }
413
414        result
415    }
416
417    /// Get hidden envs, but without envs defined previously in `excluded_overlay_name`.
418    pub fn get_hidden_env_vars(
419        &self,
420        excluded_overlay_name: &str,
421        engine_state: &EngineState,
422    ) -> HashMap<String, Value> {
423        let mut result = HashMap::new();
424
425        for overlay_name in self.active_overlays.iter().rev() {
426            if overlay_name == excluded_overlay_name {
427                continue;
428            }
429            if let Some(env_names) = self.env_hidden.get(overlay_name) {
430                for n in env_names {
431                    if result.contains_key(n) {
432                        continue;
433                    }
434                    // get env value.
435                    if let Some(Some(v)) = engine_state
436                        .env_vars
437                        .get(overlay_name)
438                        .map(|env_vars| env_vars.get(n))
439                    {
440                        result.insert(n.to_string(), v.clone());
441                    }
442                }
443            }
444        }
445        result
446    }
447
448    /// Same as get_env_vars, but returns only the names as a HashSet
449    pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
450        let mut result = HashSet::new();
451
452        for active_overlay in self.active_overlays.iter() {
453            if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
454                result.extend(
455                    env_vars
456                        .keys()
457                        .filter(|k| {
458                            if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
459                                !env_hidden.contains(*k)
460                            } else {
461                                // nothing has been hidden in this overlay
462                                true
463                            }
464                        })
465                        .cloned()
466                        .collect::<HashSet<String>>(),
467                );
468            }
469        }
470
471        for scope in &self.env_vars {
472            for active_overlay in self.active_overlays.iter() {
473                if let Some(env_vars) = scope.get(active_overlay) {
474                    result.extend(env_vars.keys().cloned().collect::<HashSet<String>>());
475                }
476            }
477        }
478
479        result
480    }
481
482    pub fn get_env_var<'a>(
483        &'a self,
484        engine_state: &'a EngineState,
485        name: &str,
486    ) -> Option<&'a Value> {
487        for scope in self.env_vars.iter().rev() {
488            for active_overlay in self.active_overlays.iter().rev() {
489                if let Some(env_vars) = scope.get(active_overlay) {
490                    if let Some(v) = env_vars.get(name) {
491                        return Some(v);
492                    }
493                }
494            }
495        }
496
497        for active_overlay in self.active_overlays.iter().rev() {
498            let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
499                env_hidden.contains(name)
500            } else {
501                false
502            };
503
504            if !is_hidden {
505                if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
506                    if let Some(v) = env_vars.get(name) {
507                        return Some(v);
508                    }
509                }
510            }
511        }
512        None
513    }
514
515    // Case-Insensitive version of get_env_var
516    // Returns Some((name, value)) if found, None otherwise.
517    // When updating environment variables, make sure to use
518    // the same case (from the returned "name") as the original
519    // environment variable name.
520    pub fn get_env_var_insensitive<'a>(
521        &'a self,
522        engine_state: &'a EngineState,
523        name: &str,
524    ) -> Option<(&'a String, &'a Value)> {
525        for scope in self.env_vars.iter().rev() {
526            for active_overlay in self.active_overlays.iter().rev() {
527                if let Some(env_vars) = scope.get(active_overlay) {
528                    if let Some(v) = env_vars.iter().find(|(k, _)| k.eq_ignore_case(name)) {
529                        return Some((v.0, v.1));
530                    }
531                }
532            }
533        }
534
535        for active_overlay in self.active_overlays.iter().rev() {
536            let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
537                env_hidden.iter().any(|k| k.eq_ignore_case(name))
538            } else {
539                false
540            };
541
542            if !is_hidden {
543                if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
544                    if let Some(v) = env_vars.iter().find(|(k, _)| k.eq_ignore_case(name)) {
545                        return Some((v.0, v.1));
546                    }
547                }
548            }
549        }
550        None
551    }
552
553    pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool {
554        for scope in self.env_vars.iter().rev() {
555            for active_overlay in self.active_overlays.iter().rev() {
556                if let Some(env_vars) = scope.get(active_overlay) {
557                    if env_vars.contains_key(name) {
558                        return true;
559                    }
560                }
561            }
562        }
563
564        for active_overlay in self.active_overlays.iter().rev() {
565            let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
566                env_hidden.contains(name)
567            } else {
568                false
569            };
570
571            if !is_hidden {
572                if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
573                    if env_vars.contains_key(name) {
574                        return true;
575                    }
576                }
577            }
578        }
579
580        false
581    }
582
583    pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool {
584        for scope in self.env_vars.iter_mut().rev() {
585            let scope = Arc::make_mut(scope);
586            for active_overlay in self.active_overlays.iter().rev() {
587                if let Some(env_vars) = scope.get_mut(active_overlay) {
588                    if env_vars.remove(name).is_some() {
589                        return true;
590                    }
591                }
592            }
593        }
594
595        for active_overlay in self.active_overlays.iter().rev() {
596            if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
597                if env_vars.get(name).is_some() {
598                    let env_hidden = Arc::make_mut(&mut self.env_hidden);
599                    if let Some(env_hidden_in_overlay) = env_hidden.get_mut(active_overlay) {
600                        env_hidden_in_overlay.insert(name.into());
601                    } else {
602                        env_hidden
603                            .insert(active_overlay.into(), [name.into()].into_iter().collect());
604                    }
605
606                    return true;
607                }
608            }
609        }
610
611        false
612    }
613
614    pub fn has_env_overlay(&self, name: &str, engine_state: &EngineState) -> bool {
615        for scope in self.env_vars.iter().rev() {
616            if scope.contains_key(name) {
617                return true;
618            }
619        }
620
621        engine_state.env_vars.contains_key(name)
622    }
623
624    pub fn is_overlay_active(&self, name: &str) -> bool {
625        self.active_overlays.iter().any(|n| n == name)
626    }
627
628    pub fn add_overlay(&mut self, name: String) {
629        self.active_overlays.retain(|o| o != &name);
630        self.active_overlays.push(name);
631    }
632
633    pub fn remove_overlay(&mut self, name: &str) {
634        self.active_overlays.retain(|o| o != name);
635    }
636
637    /// Returns the [`OutDest`] to use for the current command's stdout.
638    ///
639    /// This will be the pipe redirection if one is set,
640    /// otherwise it will be the current file redirection,
641    /// otherwise it will be the process's stdout indicated by [`OutDest::Inherit`].
642    pub fn stdout(&self) -> &OutDest {
643        self.out_dest.stdout()
644    }
645
646    /// Returns the [`OutDest`] to use for the current command's stderr.
647    ///
648    /// This will be the pipe redirection if one is set,
649    /// otherwise it will be the current file redirection,
650    /// otherwise it will be the process's stderr indicated by [`OutDest::Inherit`].
651    pub fn stderr(&self) -> &OutDest {
652        self.out_dest.stderr()
653    }
654
655    /// Returns the [`OutDest`] of the pipe redirection applied to the current command's stdout.
656    pub fn pipe_stdout(&self) -> Option<&OutDest> {
657        self.out_dest.pipe_stdout.as_ref()
658    }
659
660    /// Returns the [`OutDest`] of the pipe redirection applied to the current command's stderr.
661    pub fn pipe_stderr(&self) -> Option<&OutDest> {
662        self.out_dest.pipe_stderr.as_ref()
663    }
664
665    /// Temporarily set the pipe stdout redirection to [`OutDest::Value`].
666    ///
667    /// This is used before evaluating an expression into a `Value`.
668    pub fn start_collect_value(&mut self) -> StackCollectValueGuard {
669        StackCollectValueGuard::new(self)
670    }
671
672    /// Temporarily use the output redirections in the parent scope.
673    ///
674    /// This is used before evaluating an argument to a call.
675    pub fn use_call_arg_out_dest(&mut self) -> StackCallArgGuard {
676        StackCallArgGuard::new(self)
677    }
678
679    /// Temporarily apply redirections to stdout and/or stderr.
680    pub fn push_redirection(
681        &mut self,
682        stdout: Option<Redirection>,
683        stderr: Option<Redirection>,
684    ) -> StackIoGuard {
685        StackIoGuard::new(self, stdout, stderr)
686    }
687
688    /// Mark stdout for the last command as [`OutDest::Value`].
689    ///
690    /// This will irreversibly alter the output redirections, and so it only makes sense to use this on an owned `Stack`
691    /// (which is why this function does not take `&mut self`).
692    ///
693    /// See [`Stack::start_collect_value`] which can temporarily set stdout as [`OutDest::Value`] for a mutable `Stack` reference.
694    pub fn collect_value(mut self) -> Self {
695        self.out_dest.pipe_stdout = Some(OutDest::Value);
696        self.out_dest.pipe_stderr = None;
697        self
698    }
699
700    /// Clears any pipe and file redirections and resets stdout and stderr to [`OutDest::Inherit`].
701    ///
702    /// This will irreversibly reset the output redirections, and so it only makes sense to use this on an owned `Stack`
703    /// (which is why this function does not take `&mut self`).
704    pub fn reset_out_dest(mut self) -> Self {
705        self.out_dest = StackOutDest::new();
706        self
707    }
708
709    /// Clears any pipe redirections, keeping the current stdout and stderr.
710    ///
711    /// This will irreversibly reset some of the output redirections, and so it only makes sense to use this on an owned `Stack`
712    /// (which is why this function does not take `&mut self`).
713    pub fn reset_pipes(mut self) -> Self {
714        self.out_dest.pipe_stdout = None;
715        self.out_dest.pipe_stderr = None;
716        self
717    }
718
719    /// Replaces the default stdout of the stack with a given file.
720    ///
721    /// This method configures the default stdout to redirect to a specified file.
722    /// It is primarily useful for applications using `nu` as a language, where the stdout of
723    /// external commands that are not explicitly piped can be redirected to a file.
724    ///
725    /// # Using Pipes
726    ///
727    /// For use in third-party applications pipes might be very useful as they allow using the
728    /// stdout of external commands for different uses.
729    /// For example the [`os_pipe`](https://docs.rs/os_pipe) crate provides a elegant way to to
730    /// access the stdout.
731    ///
732    /// ```
733    /// # use std::{fs::File, io::{self, Read}, thread, error};
734    /// # use nu_protocol::engine::Stack;
735    /// #
736    /// let (mut reader, writer) = os_pipe::pipe().unwrap();
737    /// // Use a thread to avoid blocking the execution of the called command.
738    /// let reader = thread::spawn(move || {
739    ///     let mut buf: Vec<u8> = Vec::new();
740    ///     reader.read_to_end(&mut buf)?;
741    ///     Ok::<_, io::Error>(buf)
742    /// });
743    ///
744    /// #[cfg(windows)]
745    /// let file = std::os::windows::io::OwnedHandle::from(writer).into();
746    /// #[cfg(unix)]
747    /// let file = std::os::unix::io::OwnedFd::from(writer).into();
748    ///
749    /// let stack = Stack::new().stdout_file(file);
750    ///
751    /// // Execute some nu code.
752    ///
753    /// drop(stack); // drop the stack so that the writer will be dropped too
754    /// let buf = reader.join().unwrap().unwrap();
755    /// // Do with your buffer whatever you want.
756    /// ```
757    pub fn stdout_file(mut self, file: File) -> Self {
758        self.out_dest.stdout = OutDest::File(Arc::new(file));
759        self
760    }
761
762    /// Replaces the default stderr of the stack with a given file.
763    ///
764    /// For more info, see [`stdout_file`](Self::stdout_file).
765    pub fn stderr_file(mut self, file: File) -> Self {
766        self.out_dest.stderr = OutDest::File(Arc::new(file));
767        self
768    }
769
770    /// Set the PWD environment variable to `path`.
771    ///
772    /// This method accepts `path` with trailing slashes, but they're removed
773    /// before writing the value into PWD.
774    pub fn set_cwd(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), ShellError> {
775        // Helper function to create a simple generic error.
776        // Its messages are not especially helpful, but these errors don't occur often, so it's probably fine.
777        fn error(msg: &str) -> Result<(), ShellError> {
778            Err(ShellError::GenericError {
779                error: msg.into(),
780                msg: "".into(),
781                span: None,
782                help: None,
783                inner: vec![],
784            })
785        }
786
787        let path = path.as_ref();
788
789        if !path.is_absolute() {
790            if matches!(path.components().next(), Some(Component::Prefix(_))) {
791                return Err(ShellError::GenericError {
792                    error: "Cannot set $env.PWD to a prefix-only path".to_string(),
793                    msg: "".into(),
794                    span: None,
795                    help: Some(format!(
796                        "Try to use {}{MAIN_SEPARATOR} instead",
797                        path.display()
798                    )),
799                    inner: vec![],
800                });
801            }
802
803            error("Cannot set $env.PWD to a non-absolute path")
804        } else if !path.exists() {
805            error("Cannot set $env.PWD to a non-existent directory")
806        } else if !path.is_dir() {
807            error("Cannot set $env.PWD to a non-directory")
808        } else {
809            // Strip trailing slashes, if any.
810            let path = nu_path::strip_trailing_slash(path);
811            let value = Value::string(path.to_string_lossy(), Span::unknown());
812            self.add_env_var("PWD".into(), value);
813            Ok(())
814        }
815    }
816}
817
818#[cfg(test)]
819mod test {
820    use std::sync::Arc;
821
822    use crate::{Span, Value, VarId, engine::EngineState};
823
824    use super::Stack;
825
826    #[test]
827    fn test_children_see_inner_values() {
828        let mut original = Stack::new();
829        original.add_var(VarId::new(0), Value::test_string("hello"));
830
831        let cloned = Stack::with_parent(Arc::new(original));
832        assert_eq!(
833            cloned.get_var(VarId::new(0), Span::test_data()),
834            Ok(Value::test_string("hello"))
835        );
836    }
837
838    #[test]
839    fn test_children_dont_see_deleted_values() {
840        let mut original = Stack::new();
841        original.add_var(VarId::new(0), Value::test_string("hello"));
842
843        let mut cloned = Stack::with_parent(Arc::new(original));
844        cloned.remove_var(VarId::new(0));
845
846        assert_eq!(
847            cloned.get_var(VarId::new(0), Span::test_data()),
848            Err(crate::ShellError::VariableNotFoundAtRuntime {
849                span: Span::test_data()
850            })
851        );
852    }
853
854    #[test]
855    fn test_children_changes_override_parent() {
856        let mut original = Stack::new();
857        original.add_var(VarId::new(0), Value::test_string("hello"));
858
859        let mut cloned = Stack::with_parent(Arc::new(original));
860        cloned.add_var(VarId::new(0), Value::test_string("there"));
861        assert_eq!(
862            cloned.get_var(VarId::new(0), Span::test_data()),
863            Ok(Value::test_string("there"))
864        );
865
866        cloned.remove_var(VarId::new(0));
867        // the underlying value shouldn't magically re-appear
868        assert_eq!(
869            cloned.get_var(VarId::new(0), Span::test_data()),
870            Err(crate::ShellError::VariableNotFoundAtRuntime {
871                span: Span::test_data()
872            })
873        );
874    }
875    #[test]
876    fn test_children_changes_persist_in_offspring() {
877        let mut original = Stack::new();
878        original.add_var(VarId::new(0), Value::test_string("hello"));
879
880        let mut cloned = Stack::with_parent(Arc::new(original));
881        cloned.add_var(VarId::new(1), Value::test_string("there"));
882
883        cloned.remove_var(VarId::new(0));
884        let cloned = Stack::with_parent(Arc::new(cloned));
885
886        assert_eq!(
887            cloned.get_var(VarId::new(0), Span::test_data()),
888            Err(crate::ShellError::VariableNotFoundAtRuntime {
889                span: Span::test_data()
890            })
891        );
892
893        assert_eq!(
894            cloned.get_var(VarId::new(1), Span::test_data()),
895            Ok(Value::test_string("there"))
896        );
897    }
898
899    #[test]
900    fn test_merging_children_back_to_parent() {
901        let mut original = Stack::new();
902        let engine_state = EngineState::new();
903        original.add_var(VarId::new(0), Value::test_string("hello"));
904
905        let original_arc = Arc::new(original);
906        let mut cloned = Stack::with_parent(original_arc.clone());
907        cloned.add_var(VarId::new(1), Value::test_string("there"));
908
909        cloned.remove_var(VarId::new(0));
910
911        cloned.add_env_var(
912            "ADDED_IN_CHILD".to_string(),
913            Value::test_string("New Env Var"),
914        );
915
916        let original = Stack::with_changes_from_child(original_arc, cloned);
917
918        assert_eq!(
919            original.get_var(VarId::new(0), Span::test_data()),
920            Err(crate::ShellError::VariableNotFoundAtRuntime {
921                span: Span::test_data()
922            })
923        );
924
925        assert_eq!(
926            original.get_var(VarId::new(1), Span::test_data()),
927            Ok(Value::test_string("there"))
928        );
929
930        assert_eq!(
931            original
932                .get_env_var(&engine_state, "ADDED_IN_CHILD")
933                .cloned(),
934            Some(Value::test_string("New Env Var")),
935        );
936    }
937}