1pub(crate) mod check;
2#[cfg(test)]
3mod display;
4mod eval;
5mod parse;
6#[cfg(test)]
7mod proptests;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Script(pub Vec<Stmt>);
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Stmt {
14 pub pipeline: Pipeline,
15 pub op: Option<ListOp>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ListOp {
20 And,
21 Or,
22 Semi,
23 Amp,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Pipeline {
28 pub bang: bool,
29 pub commands: Vec<Cmd>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Cmd {
34 Simple(SimpleCmd),
35 Subshell(Script),
36 For {
37 var: String,
38 items: Vec<Word>,
39 body: Script,
40 },
41 While {
42 cond: Script,
43 body: Script,
44 },
45 Until {
46 cond: Script,
47 body: Script,
48 },
49 If {
50 branches: Vec<Branch>,
51 else_body: Option<Script>,
52 },
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Branch {
57 pub cond: Script,
58 pub body: Script,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct SimpleCmd {
63 pub env: Vec<(String, Word)>,
64 pub words: Vec<Word>,
65 pub redirs: Vec<Redir>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Word(pub Vec<WordPart>);
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum WordPart {
73 Lit(String),
74 Escape(char),
75 SQuote(String),
76 DQuote(Word),
77 CmdSub(Script),
78 Backtick(String),
79 Arith(String),
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum Redir {
84 Write {
85 fd: u32,
86 target: Word,
87 append: bool,
88 },
89 Read {
90 fd: u32,
91 target: Word,
92 },
93 HereStr(Word),
94 HereDoc {
95 delimiter: String,
96 strip_tabs: bool,
97 },
98 DupFd {
99 src: u32,
100 dst: String,
101 },
102}
103
104pub use check::{command_verdict, is_safe_command, is_safe_pipeline};
105pub use parse::parse;
106
107impl Word {
108 pub fn eval(&self) -> String {
109 eval::eval_word(self)
110 }
111
112 pub fn literal(s: &str) -> Self {
113 Word(vec![WordPart::Lit(s.to_string())])
114 }
115
116 pub fn normalize(&self) -> Self {
117 let mut parts = Vec::new();
118 for part in &self.0 {
119 let part = match part {
120 WordPart::DQuote(inner) => WordPart::DQuote(inner.normalize()),
121 WordPart::CmdSub(s) => WordPart::CmdSub(s.normalize()),
122 other => other.clone(),
123 };
124 if let WordPart::Lit(s) = &part
125 && let Some(WordPart::Lit(prev)) = parts.last_mut()
126 {
127 prev.push_str(s);
128 continue;
129 }
130 parts.push(part);
131 }
132 Word(parts)
133 }
134}
135
136impl Script {
137 pub fn is_empty(&self) -> bool {
138 self.0.is_empty()
139 }
140
141 pub fn normalize(&self) -> Self {
142 Script(
143 self.0
144 .iter()
145 .map(|stmt| Stmt {
146 pipeline: stmt.pipeline.normalize(),
147 op: stmt.op,
148 })
149 .collect(),
150 )
151 }
152
153 pub fn normalize_as_body(&self) -> Self {
154 let mut s = self.normalize();
155 if let Some(last) = s.0.last_mut()
156 && last.op.is_none()
157 {
158 last.op = Some(ListOp::Semi);
159 }
160 s
161 }
162}
163
164impl Pipeline {
165 fn normalize(&self) -> Self {
166 Pipeline {
167 bang: self.bang,
168 commands: self.commands.iter().map(|c| c.normalize()).collect(),
169 }
170 }
171}
172
173impl Cmd {
174 fn normalize(&self) -> Self {
175 match self {
176 Cmd::Simple(s) => Cmd::Simple(s.normalize()),
177 Cmd::Subshell(s) => Cmd::Subshell(s.normalize()),
178 Cmd::For { var, items, body } => Cmd::For {
179 var: var.clone(),
180 items: items.iter().map(|w| w.normalize()).collect(),
181 body: body.normalize_as_body(),
182 },
183 Cmd::While { cond, body } => Cmd::While {
184 cond: cond.normalize_as_body(),
185 body: body.normalize_as_body(),
186 },
187 Cmd::Until { cond, body } => Cmd::Until {
188 cond: cond.normalize_as_body(),
189 body: body.normalize_as_body(),
190 },
191 Cmd::If { branches, else_body } => Cmd::If {
192 branches: branches
193 .iter()
194 .map(|b| Branch {
195 cond: b.cond.normalize_as_body(),
196 body: b.body.normalize_as_body(),
197 })
198 .collect(),
199 else_body: else_body.as_ref().map(|e| e.normalize_as_body()),
200 },
201 }
202 }
203}
204
205impl SimpleCmd {
206 fn normalize(&self) -> Self {
207 SimpleCmd {
208 env: self
209 .env
210 .iter()
211 .map(|(k, v)| (k.clone(), v.normalize()))
212 .collect(),
213 words: self.words.iter().map(|w| w.normalize()).collect(),
214 redirs: self
215 .redirs
216 .iter()
217 .map(|r| match r {
218 Redir::Write { fd, target, append } => Redir::Write {
219 fd: *fd,
220 target: target.normalize(),
221 append: *append,
222 },
223 Redir::Read { fd, target } => Redir::Read {
224 fd: *fd,
225 target: target.normalize(),
226 },
227 Redir::HereStr(w) => Redir::HereStr(w.normalize()),
228 Redir::HereDoc { .. } | Redir::DupFd { .. } => r.clone(),
229 })
230 .collect(),
231 }
232 }
233}