1mod budget;
2pub(crate) mod check;
3mod display;
4pub(crate) mod eval;
5mod explain;
6mod parse;
7#[cfg(test)]
8mod proptests;
9mod reserved;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Script(pub Vec<Stmt>);
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Stmt {
16 pub pipeline: Pipeline,
17 pub op: Option<ListOp>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ListOp {
22 And,
23 Or,
24 Semi,
25 Amp,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Pipeline {
30 pub bang: bool,
31 pub commands: Vec<Cmd>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum Cmd {
36 Simple(SimpleCmd),
37 Subshell {
38 body: Script,
39 redirs: Vec<Redir>,
40 },
41 BraceGroup {
42 body: Script,
43 redirs: Vec<Redir>,
44 },
45 For {
46 var: String,
47 items: Vec<Word>,
48 body: Script,
49 redirs: Vec<Redir>,
50 },
51 While {
52 cond: Script,
53 body: Script,
54 redirs: Vec<Redir>,
55 },
56 Until {
57 cond: Script,
58 body: Script,
59 redirs: Vec<Redir>,
60 },
61 If {
62 branches: Vec<Branch>,
63 else_body: Option<Script>,
64 redirs: Vec<Redir>,
65 },
66 DoubleBracket {
67 words: Vec<Word>,
68 redirs: Vec<Redir>,
69 },
70 Case {
74 subject: Word,
75 arms: Vec<CaseArm>,
76 redirs: Vec<Redir>,
77 },
78 FunctionDef {
82 name: String,
83 body: Script,
84 },
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Branch {
89 pub cond: Script,
90 pub body: Script,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CaseArm {
97 pub patterns: Vec<Word>,
98 pub body: Script,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct SimpleCmd {
103 pub env: Vec<(String, Word)>,
104 pub words: Vec<Word>,
105 pub redirs: Vec<Redir>,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Word(pub Vec<WordPart>);
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum WordPart {
113 Lit(String),
114 Escape(char),
115 SQuote(String),
116 DQuote(Word),
117 CmdSub(Script),
118 ProcSub(Script),
119 Backtick(String),
120 Arith(Word),
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum WriteMode {
133 Truncate,
135 Append,
137 TruncateBoth,
141 AppendBoth,
143 Clobber,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum Redir {
149 Write {
150 fd: u32,
151 target: Word,
152 mode: WriteMode,
153 },
154 Read {
155 fd: u32,
156 target: Word,
157 },
158 ReadWrite {
162 fd: u32,
163 target: Word,
164 },
165 HereStr(Word),
166 HereDoc {
167 delimiter: String,
168 strip_tabs: bool,
169 body: Word,
174 },
175 DupFd {
176 src: u32,
177 dst: String,
178 },
179}
180
181pub use check::{command_verdict, is_safe_command, is_safe_pipeline};
182pub(crate) use explain::denied_inner_words;
183pub use explain::{Explanation, SegmentReport, explain, explain_with_coverage};
184pub use parse::parse;
185
186impl Word {
187 pub fn eval(&self) -> String {
188 eval::eval_word(self)
189 }
190
191 pub fn expand(&self) -> Vec<String> {
195 eval::expand_word(self)
196 }
197
198 pub fn literal(s: &str) -> Self {
199 Word(vec![WordPart::Lit(s.to_string())])
200 }
201
202 pub fn normalize(&self) -> Self {
203 let mut parts = Vec::new();
204 for part in &self.0 {
205 let part = match part {
206 WordPart::DQuote(inner) => WordPart::DQuote(inner.normalize()),
207 WordPart::CmdSub(s) => WordPart::CmdSub(s.normalize()),
208 WordPart::ProcSub(s) => WordPart::ProcSub(s.normalize()),
209 other => other.clone(),
210 };
211 if let WordPart::Lit(s) = &part
212 && let Some(WordPart::Lit(prev)) = parts.last_mut()
213 {
214 prev.push_str(s);
215 continue;
216 }
217 parts.push(part);
218 }
219 Word(parts)
220 }
221}
222
223impl Script {
224 pub fn is_empty(&self) -> bool {
225 self.0.is_empty()
226 }
227
228 pub fn normalize(&self) -> Self {
229 Script(self.0.iter().map(|stmt| Stmt { pipeline: stmt.pipeline.normalize(), op: stmt.op }).collect())
230 }
231
232 pub fn normalize_as_body(&self) -> Self {
233 let mut s = self.normalize();
234 if let Some(last) = s.0.last_mut()
235 && last.op.is_none()
236 {
237 last.op = Some(ListOp::Semi);
238 }
239 s
240 }
241}
242
243impl Pipeline {
244 fn normalize(&self) -> Self {
245 Pipeline { bang: self.bang, commands: self.commands.iter().map(|c| c.normalize()).collect() }
246 }
247}
248
249impl Cmd {
250 fn normalize(&self) -> Self {
251 match self {
252 Cmd::Simple(s) => Cmd::Simple(s.normalize()),
253 Cmd::Subshell { body, redirs } => Cmd::Subshell { body: body.normalize(), redirs: normalize_redirs(redirs) },
254 Cmd::BraceGroup { body, redirs } => Cmd::BraceGroup { body: body.normalize_as_body(), redirs: normalize_redirs(redirs) },
255 Cmd::For { var, items, body, redirs } => Cmd::For {
256 var: var.clone(),
257 items: items.iter().map(|w| w.normalize()).collect(),
258 body: body.normalize_as_body(),
259 redirs: normalize_redirs(redirs),
260 },
261 Cmd::While { cond, body, redirs } => {
262 Cmd::While { cond: cond.normalize_as_body(), body: body.normalize_as_body(), redirs: normalize_redirs(redirs) }
263 }
264 Cmd::Until { cond, body, redirs } => {
265 Cmd::Until { cond: cond.normalize_as_body(), body: body.normalize_as_body(), redirs: normalize_redirs(redirs) }
266 }
267 Cmd::If { branches, else_body, redirs } => Cmd::If {
268 branches: branches
269 .iter()
270 .map(|b| Branch { cond: b.cond.normalize_as_body(), body: b.body.normalize_as_body() })
271 .collect(),
272 else_body: else_body.as_ref().map(|e| e.normalize_as_body()),
273 redirs: normalize_redirs(redirs),
274 },
275 Cmd::DoubleBracket { words, redirs } => {
276 Cmd::DoubleBracket { words: words.iter().map(|w| w.normalize()).collect(), redirs: normalize_redirs(redirs) }
277 }
278 Cmd::Case { subject, arms, redirs } => Cmd::Case {
279 subject: subject.normalize(),
280 arms: arms
281 .iter()
282 .map(|a| CaseArm { patterns: a.patterns.iter().map(|w| w.normalize()).collect(), body: a.body.normalize_as_body() })
283 .collect(),
284 redirs: normalize_redirs(redirs),
285 },
286 Cmd::FunctionDef { name, body } => Cmd::FunctionDef { name: name.clone(), body: body.normalize_as_body() },
287 }
288 }
289}
290
291impl SimpleCmd {
292 fn normalize(&self) -> Self {
293 SimpleCmd {
294 env: self.env.iter().map(|(k, v)| (k.clone(), v.normalize())).collect(),
295 words: self.words.iter().map(|w| w.normalize()).collect(),
296 redirs: normalize_redirs(&self.redirs),
297 }
298 }
299}
300
301fn normalize_redirs(redirs: &[Redir]) -> Vec<Redir> {
302 redirs
303 .iter()
304 .map(|r| match r {
305 Redir::Write { fd, target, mode } => Redir::Write { fd: *fd, target: target.normalize(), mode: *mode },
306 Redir::Read { fd, target } => Redir::Read { fd: *fd, target: target.normalize() },
307 Redir::ReadWrite { fd, target } => Redir::ReadWrite { fd: *fd, target: target.normalize() },
308 Redir::HereStr(w) => Redir::HereStr(w.normalize()),
309 Redir::HereDoc { .. } | Redir::DupFd { .. } => r.clone(),
310 })
311 .collect()
312}