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}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum Redir {
83 Write {
84 fd: u32,
85 target: Word,
86 append: bool,
87 },
88 Read {
89 fd: u32,
90 target: Word,
91 },
92 HereStr(Word),
93 HereDoc {
94 delimiter: String,
95 strip_tabs: bool,
96 },
97 DupFd {
98 src: u32,
99 dst: String,
100 },
101}
102
103pub use check::{command_verdict, is_safe_command, is_safe_pipeline};
104pub use parse::parse;
105
106impl Word {
107 pub fn eval(&self) -> String {
108 eval::eval_word(self)
109 }
110
111 pub fn literal(s: &str) -> Self {
112 Word(vec![WordPart::Lit(s.to_string())])
113 }
114
115 pub fn normalize(&self) -> Self {
116 let mut parts = Vec::new();
117 for part in &self.0 {
118 let part = match part {
119 WordPart::DQuote(inner) => WordPart::DQuote(inner.normalize()),
120 WordPart::CmdSub(s) => WordPart::CmdSub(s.normalize()),
121 other => other.clone(),
122 };
123 if let WordPart::Lit(s) = &part
124 && let Some(WordPart::Lit(prev)) = parts.last_mut()
125 {
126 prev.push_str(s);
127 continue;
128 }
129 parts.push(part);
130 }
131 Word(parts)
132 }
133}
134
135impl Script {
136 pub fn is_empty(&self) -> bool {
137 self.0.is_empty()
138 }
139
140 pub fn normalize(&self) -> Self {
141 Script(
142 self.0
143 .iter()
144 .map(|stmt| Stmt {
145 pipeline: stmt.pipeline.normalize(),
146 op: stmt.op,
147 })
148 .collect(),
149 )
150 }
151
152 pub fn normalize_as_body(&self) -> Self {
153 let mut s = self.normalize();
154 if let Some(last) = s.0.last_mut()
155 && last.op.is_none()
156 {
157 last.op = Some(ListOp::Semi);
158 }
159 s
160 }
161}
162
163impl Pipeline {
164 fn normalize(&self) -> Self {
165 Pipeline {
166 bang: self.bang,
167 commands: self.commands.iter().map(|c| c.normalize()).collect(),
168 }
169 }
170}
171
172impl Cmd {
173 fn normalize(&self) -> Self {
174 match self {
175 Cmd::Simple(s) => Cmd::Simple(s.normalize()),
176 Cmd::Subshell(s) => Cmd::Subshell(s.normalize()),
177 Cmd::For { var, items, body } => Cmd::For {
178 var: var.clone(),
179 items: items.iter().map(|w| w.normalize()).collect(),
180 body: body.normalize_as_body(),
181 },
182 Cmd::While { cond, body } => Cmd::While {
183 cond: cond.normalize_as_body(),
184 body: body.normalize_as_body(),
185 },
186 Cmd::Until { cond, body } => Cmd::Until {
187 cond: cond.normalize_as_body(),
188 body: body.normalize_as_body(),
189 },
190 Cmd::If { branches, else_body } => Cmd::If {
191 branches: branches
192 .iter()
193 .map(|b| Branch {
194 cond: b.cond.normalize_as_body(),
195 body: b.body.normalize_as_body(),
196 })
197 .collect(),
198 else_body: else_body.as_ref().map(|e| e.normalize_as_body()),
199 },
200 }
201 }
202}
203
204impl SimpleCmd {
205 fn normalize(&self) -> Self {
206 SimpleCmd {
207 env: self
208 .env
209 .iter()
210 .map(|(k, v)| (k.clone(), v.normalize()))
211 .collect(),
212 words: self.words.iter().map(|w| w.normalize()).collect(),
213 redirs: self
214 .redirs
215 .iter()
216 .map(|r| match r {
217 Redir::Write { fd, target, append } => Redir::Write {
218 fd: *fd,
219 target: target.normalize(),
220 append: *append,
221 },
222 Redir::Read { fd, target } => Redir::Read {
223 fd: *fd,
224 target: target.normalize(),
225 },
226 Redir::HereStr(w) => Redir::HereStr(w.normalize()),
227 Redir::HereDoc { .. } | Redir::DupFd { .. } => r.clone(),
228 })
229 .collect(),
230 }
231 }
232}