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