Skip to main content

safe_chains/cst/
mod.rs

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