nu_protocol/engine/
stack.rs

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