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}