Skip to main content

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