Skip to main content

nu_protocol/engine/
stack_out_dest.rs

1use crate::{OutDest, engine::Stack};
2use std::{
3    fs::File,
4    mem,
5    ops::{Deref, DerefMut},
6    sync::Arc,
7};
8
9#[derive(Debug, Clone)]
10pub enum Redirection {
11    /// A pipe redirection.
12    ///
13    /// This will only affect the last command of a block.
14    /// This is created by pipes and pipe redirections (`|`, `e>|`, `o+e>|`, etc.),
15    /// or set by the next command in the pipeline (e.g., `ignore` sets stdout to [`OutDest::Null`]).
16    Pipe(OutDest),
17    /// A file redirection.
18    ///
19    /// This will affect all commands in the block.
20    /// This is only created by file redirections (`o>`, `e>`, `o+e>`, etc.).
21    File(Arc<File>),
22}
23
24impl Redirection {
25    pub fn file(file: File) -> Self {
26        Self::File(Arc::new(file))
27    }
28}
29
30#[derive(Debug, Clone)]
31pub(crate) struct StackOutDest {
32    /// The stream to use for the next command's stdout.
33    pub pipe_stdout: Option<OutDest>,
34    /// The stream to use for the next command's stderr.
35    pub pipe_stderr: Option<OutDest>,
36    /// The stream used for the command stdout if `pipe_stdout` is `None`.
37    ///
38    /// This should only ever be `File` or `Inherit`.
39    pub stdout: OutDest,
40    /// The stream used for the command stderr if `pipe_stderr` is `None`.
41    ///
42    /// This should only ever be `File` or `Inherit`.
43    pub stderr: OutDest,
44    /// The previous stdout used before the current `stdout` was set.
45    ///
46    /// This is used only when evaluating arguments to commands,
47    /// since the arguments are lazily evaluated inside each command
48    /// after redirections have already been applied to the command/stack.
49    ///
50    /// This should only ever be `File` or `Inherit`.
51    pub parent_stdout: Option<OutDest>,
52    /// The previous stderr used before the current `stderr` was set.
53    ///
54    /// This is used only when evaluating arguments to commands,
55    /// since the arguments are lazily evaluated inside each command
56    /// after redirections have already been applied to the command/stack.
57    ///
58    /// This should only ever be `File` or `Inherit`.
59    pub parent_stderr: Option<OutDest>,
60    /// Stack of stdout destinations for active custom-command invocations.
61    ///
62    /// Each frame is the destination of that call's *return value*, pushed when
63    /// entering a custom command and popped when leaving. Intermediate
64    /// evaluation may temporarily set `pipe_stdout` to [`OutDest::Value`]
65    /// (e.g. `if (…)`), but this stack stays stable so commands like
66    /// `is-redirected` can answer "where does *this command* go?" from anywhere
67    /// in the body.
68    pub invocation_stdout: Vec<OutDest>,
69}
70
71impl StackOutDest {
72    pub(crate) fn new() -> Self {
73        Self {
74            pipe_stdout: Some(OutDest::Print),
75            pipe_stderr: Some(OutDest::Print),
76            stdout: OutDest::Inherit,
77            stderr: OutDest::Inherit,
78            parent_stdout: None,
79            parent_stderr: None,
80            invocation_stdout: Vec::new(),
81        }
82    }
83
84    /// Returns the [`OutDest`] to use for current command's stdout.
85    ///
86    /// This will be the pipe redirection if one is set,
87    /// otherwise it will be the current file redirection,
88    /// otherwise it will be the process's stdout indicated by [`OutDest::Inherit`].
89    pub(crate) fn stdout(&self) -> &OutDest {
90        self.pipe_stdout.as_ref().unwrap_or(&self.stdout)
91    }
92
93    /// Returns the [`OutDest`] to use for current command's stderr.
94    ///
95    /// This will be the pipe redirection if one is set,
96    /// otherwise it will be the current file redirection,
97    /// otherwise it will be the process's stderr indicated by [`OutDest::Inherit`].
98    pub(crate) fn stderr(&self) -> &OutDest {
99        self.pipe_stderr.as_ref().unwrap_or(&self.stderr)
100    }
101
102    fn push_stdout(&mut self, stdout: OutDest) -> Option<OutDest> {
103        let stdout = mem::replace(&mut self.stdout, stdout);
104        self.parent_stdout.replace(stdout)
105    }
106
107    fn push_stderr(&mut self, stderr: OutDest) -> Option<OutDest> {
108        let stderr = mem::replace(&mut self.stderr, stderr);
109        self.parent_stderr.replace(stderr)
110    }
111}
112
113pub struct StackIoGuard<'a> {
114    stack: &'a mut Stack,
115    old_pipe_stdout: Option<OutDest>,
116    old_pipe_stderr: Option<OutDest>,
117    old_parent_stdout: Option<OutDest>,
118    old_parent_stderr: Option<OutDest>,
119}
120
121impl<'a> StackIoGuard<'a> {
122    pub(crate) fn new(
123        stack: &'a mut Stack,
124        stdout: Option<Redirection>,
125        stderr: Option<Redirection>,
126    ) -> Self {
127        let out_dest = &mut stack.out_dest;
128
129        let (old_pipe_stdout, old_parent_stdout) = match stdout {
130            Some(Redirection::Pipe(stdout)) => {
131                let old = out_dest.pipe_stdout.replace(stdout);
132                (old, out_dest.parent_stdout.take())
133            }
134            Some(Redirection::File(file)) => {
135                let file = OutDest::from(file);
136                (
137                    out_dest.pipe_stdout.replace(file.clone()),
138                    out_dest.push_stdout(file),
139                )
140            }
141            None => (out_dest.pipe_stdout.take(), out_dest.parent_stdout.take()),
142        };
143
144        let (old_pipe_stderr, old_parent_stderr) = match stderr {
145            Some(Redirection::Pipe(stderr)) => {
146                let old = out_dest.pipe_stderr.replace(stderr);
147                (old, out_dest.parent_stderr.take())
148            }
149            Some(Redirection::File(file)) => (
150                out_dest.pipe_stderr.take(),
151                out_dest.push_stderr(file.into()),
152            ),
153            None => (out_dest.pipe_stderr.take(), out_dest.parent_stderr.take()),
154        };
155
156        StackIoGuard {
157            stack,
158            old_pipe_stdout,
159            old_parent_stdout,
160            old_pipe_stderr,
161            old_parent_stderr,
162        }
163    }
164}
165
166impl Deref for StackIoGuard<'_> {
167    type Target = Stack;
168
169    fn deref(&self) -> &Self::Target {
170        self.stack
171    }
172}
173
174impl DerefMut for StackIoGuard<'_> {
175    fn deref_mut(&mut self) -> &mut Self::Target {
176        self.stack
177    }
178}
179
180impl Drop for StackIoGuard<'_> {
181    fn drop(&mut self) {
182        self.out_dest.pipe_stdout = self.old_pipe_stdout.take();
183        self.out_dest.pipe_stderr = self.old_pipe_stderr.take();
184
185        let old_stdout = self.old_parent_stdout.take();
186        if let Some(stdout) = mem::replace(&mut self.out_dest.parent_stdout, old_stdout) {
187            self.out_dest.stdout = stdout;
188        }
189
190        let old_stderr = self.old_parent_stderr.take();
191        if let Some(stderr) = mem::replace(&mut self.out_dest.parent_stderr, old_stderr) {
192            self.out_dest.stderr = stderr;
193        }
194    }
195}
196
197pub struct StackCollectValueGuard<'a> {
198    stack: &'a mut Stack,
199    old_pipe_stdout: Option<OutDest>,
200    old_pipe_stderr: Option<OutDest>,
201}
202
203impl<'a> StackCollectValueGuard<'a> {
204    pub(crate) fn new(stack: &'a mut Stack) -> Self {
205        let old_pipe_stdout = stack.out_dest.pipe_stdout.replace(OutDest::Value);
206        let old_pipe_stderr = stack.out_dest.pipe_stderr.take();
207        Self {
208            stack,
209            old_pipe_stdout,
210            old_pipe_stderr,
211        }
212    }
213}
214
215impl Deref for StackCollectValueGuard<'_> {
216    type Target = Stack;
217
218    fn deref(&self) -> &Self::Target {
219        &*self.stack
220    }
221}
222
223impl DerefMut for StackCollectValueGuard<'_> {
224    fn deref_mut(&mut self) -> &mut Self::Target {
225        self.stack
226    }
227}
228
229impl Drop for StackCollectValueGuard<'_> {
230    fn drop(&mut self) {
231        self.out_dest.pipe_stdout = self.old_pipe_stdout.take();
232        self.out_dest.pipe_stderr = self.old_pipe_stderr.take();
233    }
234}
235
236pub struct StackCallArgGuard<'a> {
237    stack: &'a mut Stack,
238    old_pipe_stdout: Option<OutDest>,
239    old_pipe_stderr: Option<OutDest>,
240    old_stdout: Option<OutDest>,
241    old_stderr: Option<OutDest>,
242}
243
244impl<'a> StackCallArgGuard<'a> {
245    pub(crate) fn new(stack: &'a mut Stack) -> Self {
246        let old_pipe_stdout = stack.out_dest.pipe_stdout.replace(OutDest::Value);
247        let old_pipe_stderr = stack.out_dest.pipe_stderr.take();
248
249        let old_stdout = stack
250            .out_dest
251            .parent_stdout
252            .take()
253            .map(|stdout| mem::replace(&mut stack.out_dest.stdout, stdout));
254
255        let old_stderr = stack
256            .out_dest
257            .parent_stderr
258            .take()
259            .map(|stderr| mem::replace(&mut stack.out_dest.stderr, stderr));
260
261        Self {
262            stack,
263            old_pipe_stdout,
264            old_pipe_stderr,
265            old_stdout,
266            old_stderr,
267        }
268    }
269}
270
271impl Deref for StackCallArgGuard<'_> {
272    type Target = Stack;
273
274    fn deref(&self) -> &Self::Target {
275        &*self.stack
276    }
277}
278
279impl DerefMut for StackCallArgGuard<'_> {
280    fn deref_mut(&mut self) -> &mut Self::Target {
281        self.stack
282    }
283}
284
285impl Drop for StackCallArgGuard<'_> {
286    fn drop(&mut self) {
287        self.out_dest.pipe_stdout = self.old_pipe_stdout.take();
288        self.out_dest.pipe_stderr = self.old_pipe_stderr.take();
289        if let Some(stdout) = self.old_stdout.take() {
290            self.out_dest.push_stdout(stdout);
291        }
292        if let Some(stderr) = self.old_stderr.take() {
293            self.out_dest.push_stderr(stderr);
294        }
295    }
296}
297
298/// RAII wrapper that records a custom command's return-value [`OutDest`] on a [`Stack`].
299///
300/// Created by [`Stack::with_invocation_stdout`]. While this value is alive, the stack's
301/// `invocation_stdout` frame is the destination of the call currently being evaluated.
302/// Nested custom commands push additional frames; each drop pops exactly one.
303///
304/// # Ownership model
305///
306/// Unlike [`StackIoGuard`] / [`StackCollectValueGuard`] (which borrow a stack), this type
307/// **owns** the [`Stack`]. That matches how custom-command evaluation works: the callee
308/// stack is built with [`Stack::gather_captures`](crate::engine::Stack::gather_captures) as an
309/// owned value, then wrapped here for the duration of `eval_block`.
310///
311/// Derefs to [`Stack`] so existing evaluation code can treat it as a mutable stack.
312pub struct StackWithInvocation {
313    stack: Stack,
314}
315
316impl StackWithInvocation {
317    /// Push `dest` onto the stack's invocation frame list.
318    ///
319    /// Prefer [`Stack::with_invocation_stdout`] at call sites.
320    pub(crate) fn new(mut stack: Stack, dest: OutDest) -> Self {
321        stack.out_dest.invocation_stdout.push(dest);
322        Self { stack }
323    }
324}
325
326impl Deref for StackWithInvocation {
327    type Target = Stack;
328
329    fn deref(&self) -> &Self::Target {
330        &self.stack
331    }
332}
333
334impl DerefMut for StackWithInvocation {
335    fn deref_mut(&mut self) -> &mut Self::Target {
336        &mut self.stack
337    }
338}
339
340impl Drop for StackWithInvocation {
341    fn drop(&mut self) {
342        // Paired with the push in `new`. Nested wrappers pop only their own frame.
343        self.stack.out_dest.invocation_stdout.pop();
344    }
345}