nu_protocol/ast/
pipeline.rs

1use crate::{OutDest, Span, VarId, ast::Expression, engine::StateWorkingSet};
2use serde::{Deserialize, Serialize};
3use std::fmt::Display;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
6pub enum RedirectionSource {
7    Stdout,
8    Stderr,
9    StdoutAndStderr,
10}
11
12impl Display for RedirectionSource {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        f.write_str(match self {
15            RedirectionSource::Stdout => "stdout",
16            RedirectionSource::Stderr => "stderr",
17            RedirectionSource::StdoutAndStderr => "stdout and stderr",
18        })
19    }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub enum RedirectionTarget {
24    File {
25        expr: Expression,
26        append: bool,
27        span: Span,
28    },
29    Pipe {
30        span: Span,
31    },
32}
33
34impl RedirectionTarget {
35    pub fn span(&self) -> Span {
36        match self {
37            RedirectionTarget::File { span, .. } | RedirectionTarget::Pipe { span } => *span,
38        }
39    }
40
41    pub fn expr(&self) -> Option<&Expression> {
42        match self {
43            RedirectionTarget::File { expr, .. } => Some(expr),
44            RedirectionTarget::Pipe { .. } => None,
45        }
46    }
47
48    pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
49        self.expr().is_some_and(|e| e.has_in_variable(working_set))
50    }
51
52    pub fn replace_span(
53        &mut self,
54        working_set: &mut StateWorkingSet,
55        replaced: Span,
56        new_span: Span,
57    ) {
58        match self {
59            RedirectionTarget::File { expr, .. } => {
60                expr.replace_span(working_set, replaced, new_span)
61            }
62            RedirectionTarget::Pipe { .. } => {}
63        }
64    }
65
66    pub fn replace_in_variable(
67        &mut self,
68        working_set: &mut StateWorkingSet<'_>,
69        new_var_id: VarId,
70    ) {
71        match self {
72            RedirectionTarget::File { expr, .. } => {
73                expr.replace_in_variable(working_set, new_var_id)
74            }
75            RedirectionTarget::Pipe { .. } => {}
76        }
77    }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub enum PipelineRedirection {
82    Single {
83        source: RedirectionSource,
84        target: RedirectionTarget,
85    },
86    Separate {
87        out: RedirectionTarget,
88        err: RedirectionTarget,
89    },
90}
91impl PipelineRedirection {
92    pub fn replace_in_variable(
93        &mut self,
94        working_set: &mut StateWorkingSet<'_>,
95        new_var_id: VarId,
96    ) {
97        match self {
98            PipelineRedirection::Single { source: _, target } => {
99                target.replace_in_variable(working_set, new_var_id)
100            }
101            PipelineRedirection::Separate { out, err } => {
102                out.replace_in_variable(working_set, new_var_id);
103                err.replace_in_variable(working_set, new_var_id);
104            }
105        }
106    }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct PipelineElement {
111    pub pipe: Option<Span>,
112    pub expr: Expression,
113    pub redirection: Option<PipelineRedirection>,
114}
115
116impl PipelineElement {
117    pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
118        self.expr.has_in_variable(working_set)
119            || self.redirection.as_ref().is_some_and(|r| match r {
120                PipelineRedirection::Single { target, .. } => target.has_in_variable(working_set),
121                PipelineRedirection::Separate { out, err } => {
122                    out.has_in_variable(working_set) || err.has_in_variable(working_set)
123                }
124            })
125    }
126
127    pub fn replace_span(
128        &mut self,
129        working_set: &mut StateWorkingSet,
130        replaced: Span,
131        new_span: Span,
132    ) {
133        self.expr.replace_span(working_set, replaced, new_span);
134        if let Some(expr) = self.redirection.as_mut() {
135            match expr {
136                PipelineRedirection::Single { target, .. } => {
137                    target.replace_span(working_set, replaced, new_span)
138                }
139                PipelineRedirection::Separate { out, err } => {
140                    out.replace_span(working_set, replaced, new_span);
141                    err.replace_span(working_set, replaced, new_span);
142                }
143            }
144        }
145    }
146
147    pub fn pipe_redirection(
148        &self,
149        working_set: &StateWorkingSet,
150    ) -> (Option<OutDest>, Option<OutDest>) {
151        self.expr.expr.pipe_redirection(working_set)
152    }
153
154    pub fn replace_in_variable(
155        &mut self,
156        working_set: &mut StateWorkingSet<'_>,
157        new_var_id: VarId,
158    ) {
159        self.expr.replace_in_variable(working_set, new_var_id);
160        if let Some(redirection) = &mut self.redirection {
161            redirection.replace_in_variable(working_set, new_var_id);
162        }
163    }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct Pipeline {
168    pub elements: Vec<PipelineElement>,
169}
170
171impl Default for Pipeline {
172    fn default() -> Self {
173        Self::new()
174    }
175}
176
177impl Pipeline {
178    pub fn new() -> Self {
179        Self { elements: vec![] }
180    }
181
182    pub fn from_vec(expressions: Vec<Expression>) -> Pipeline {
183        Self {
184            elements: expressions
185                .into_iter()
186                .enumerate()
187                .map(|(idx, expr)| PipelineElement {
188                    pipe: if idx == 0 { None } else { Some(expr.span) },
189                    expr,
190                    redirection: None,
191                })
192                .collect(),
193        }
194    }
195
196    pub fn len(&self) -> usize {
197        self.elements.len()
198    }
199
200    pub fn is_empty(&self) -> bool {
201        self.elements.is_empty()
202    }
203
204    pub fn pipe_redirection(
205        &self,
206        working_set: &StateWorkingSet,
207    ) -> (Option<OutDest>, Option<OutDest>) {
208        if let Some(first) = self.elements.first() {
209            first.pipe_redirection(working_set)
210        } else {
211            (None, None)
212        }
213    }
214}