1use std;
17use std::io::{Cursor, Write};
18use std::rc::Rc;
19
20use crate::build::Val;
21use crate::build::Val::Tuple;
22use crate::convert;
23use crate::convert::traits::{ConvertResult, Converter};
24use crate::error::BuildError;
25use crate::error::ErrorType;
26
27pub struct ExecConverter {}
28
29impl ExecConverter {
35 pub fn new() -> Self {
36 ExecConverter {}
37 }
38
39 #[allow(unused_assignments)]
40 fn write(&self, v: &Val, w: &mut dyn Write) -> ConvertResult {
41 if let &Tuple(ref fields) = v {
43 if fields.len() > 3 {
45 return Err(BuildError::new(
46 "Exec tuples must have no more than 3 fields",
47 ErrorType::TypeFail,
48 )
49 .to_boxed());
50 }
51 let mut env: Option<&Vec<(String, Rc<Val>)>> = None;
52 let mut command: Option<&str> = None;
53 let mut args: Option<&Vec<Rc<Val>>> = None;
54 for &(ref name, ref val) in fields.iter() {
55 if name == "command" {
57 if command.is_some() {
58 return Err(BuildError::new(
59 "There can only be one command field in an exec tuple",
60 ErrorType::TypeFail,
61 )
62 .to_boxed());
63 }
64 if let &Val::Str(ref s) = val.as_ref() {
65 command = Some(s);
66 continue;
67 }
68 return Err(BuildError::new(
69 "The command field of an exec tuple must be a string",
70 ErrorType::TypeFail,
71 )
72 .to_boxed());
73 }
74 if name == "env" {
76 if let &Val::Tuple(ref l) = val.as_ref() {
77 if env.is_some() {
78 return Err(BuildError::new(
79 "There can only be one env field in an exec tuple",
80 ErrorType::TypeFail,
81 )
82 .to_boxed());
83 }
84 env = Some(l);
85 continue;
86 }
87 return Err(BuildError::new(
88 "The env field of an exec tuple must be a list",
89 ErrorType::TypeFail,
90 )
91 .to_boxed());
92 }
93 if name == "args" {
95 if let &Val::List(ref l) = val.as_ref() {
96 if args.is_some() {
97 return Err(BuildError::new(
98 "There can only be one args field of an exec tuple",
99 ErrorType::TypeFail,
100 )
101 .to_boxed());
102 }
103 args = Some(l);
104 continue;
105 }
106 return Err(BuildError::new(
107 "The args field of an exec tuple must be a list",
108 ErrorType::TypeFail,
109 )
110 .to_boxed());
111 }
112 }
113 if command.is_none() {
114 return Err(BuildError::new(
115 "An exec tuple must have a command field",
116 ErrorType::TypeFail,
117 )
118 .to_boxed());
119 }
120 let mut script = Cursor::new(vec![]);
122 write!(script, "#!/usr/bin/env bash\n")?;
124 write!(script, "# Turn on unofficial Bash-Strict-Mode\n")?;
126 write!(script, "set -euo pipefail\n")?;
127 if let Some(env_list) = env {
129 for &(ref name, ref v) in env_list.iter() {
130 if let &Val::Str(ref s) = v.as_ref() {
132 write!(script, "{}=\"{}\"\n", name, s)?;
133 continue;
134 }
135 return Err(BuildError::new(
136 "The env fields of an exec tuple must contain only string values",
137 ErrorType::TypeFail,
138 )
139 .to_boxed());
140 }
141 }
142 write!(script, "\n")?;
143 let flag_converter = convert::flags::FlagConverter::new();
144 write!(script, "exec {} ", command.unwrap())?;
146 if let Some(arg_list) = args {
147 for v in arg_list.iter() {
148 match v.as_ref() {
150 &Val::Str(ref s) => {
151 write!(script, "{} ", s)?;
152 }
153 &Val::Tuple(_) => flag_converter.convert(v.clone(), &mut script)?,
154 _ => {
155 return Err(BuildError::new(
156 "Exec args must be a list of strings or tuples of strings.",
157 ErrorType::TypeFail,
158 )
159 .to_boxed());
160 }
161 }
162 }
163 }
164 script.set_position(0);
167 std::io::copy(&mut script, w)?;
168 return Ok(());
169 }
170
171 Err(BuildError::new("Exec outputs must be of type Tuple", ErrorType::TypeFail).to_boxed())
172 }
173}
174
175impl Converter for ExecConverter {
176 fn convert(&self, v: Rc<Val>, mut w: &mut dyn Write) -> ConvertResult {
177 self.write(&v, &mut w)
178 }
179
180 fn file_ext(&self) -> String {
181 String::from("sh")
182 }
183
184 fn description(&self) -> String {
185 "Convert ucg Vals into an bash script with \nenvironment variables set and command line arguments sent..".to_string()
186 }
187
188 #[allow(unused_must_use)]
189 fn help(&self) -> String {
190 include_str!("exec_help.txt").to_string()
191 }
192}
193
194#[cfg(test)]
195mod exec_test {
196 use std::cell::RefCell;
197
198 use super::*;
199 use crate::build::opcode::Environment;
200 use crate::build::FileBuilder;
201 use crate::convert::traits::Converter;
202
203 use std;
204 use std::io::Cursor;
205
206 #[test]
207 fn convert_just_command_test() {
208 let i_paths = Vec::new();
209 let out: Vec<u8> = Vec::new();
210 let err: Vec<u8> = Vec::new();
211 let env = RefCell::new(Environment::new(out, err));
212 let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, &env);
213 let conv = ExecConverter::new();
214 b.eval_string(
215 "let script = {
216 command = \"/bin/echo\",
217 };",
218 )
219 .unwrap();
220 let result = b.get_out_by_name("script").unwrap();
221 let mut expected = "#!/usr/bin/env bash\n".to_string();
222 expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
223 expected.push_str("set -euo pipefail\n\n");
224 expected.push_str("exec /bin/echo ");
225 let mut buf = Cursor::new(vec![]);
226 conv.convert(result, &mut buf).unwrap();
227 assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
228 }
229
230 #[test]
231 fn convert_command_with_env_test() {
232 let i_paths = Vec::new();
233 let out: Vec<u8> = Vec::new();
234 let err: Vec<u8> = Vec::new();
235 let env = RefCell::new(Environment::new(out, err));
236 let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, &env);
237 let conv = ExecConverter::new();
238 b.eval_string(
239 "let script = {
240 command = \"/bin/echo\",
241 env = {
242 foo = \"bar\",
243 quux = \"baz\",
244 },
245 };",
246 )
247 .unwrap();
248 let result = b.get_out_by_name("script").unwrap();
249 let mut expected = "#!/usr/bin/env bash\n".to_string();
250 expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
251 expected.push_str("set -euo pipefail\n");
252 expected.push_str("foo=\"bar\"\n");
253 expected.push_str("quux=\"baz\"\n");
254 expected.push_str("\n");
255 expected.push_str("exec /bin/echo ");
256 let mut buf = Cursor::new(vec![]);
257 conv.convert(result, &mut buf).unwrap();
258 assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
259 }
260
261 #[test]
262 fn convert_command_with_arg_test() {
263 let i_paths = Vec::new();
264 let out: Vec<u8> = Vec::new();
265 let err: Vec<u8> = Vec::new();
266 let env = RefCell::new(Environment::new(out, err));
267 let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, &env);
268 let conv = ExecConverter::new();
269 b.eval_string(
270 "let script = {
271 command = \"/bin/echo\",
272 env = {
273 foo = \"bar\",
274 quux = \"baz\",
275 },
276 args = [
277 \"subcommand\",
278 {flag1 = 1},
279 ],
280 };",
281 )
282 .unwrap();
283 let result = b.get_out_by_name("script").unwrap();
284 let mut expected = "#!/usr/bin/env bash\n".to_string();
285 expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
286 expected.push_str("set -euo pipefail\n");
287 expected.push_str("foo=\"bar\"\n");
288 expected.push_str("quux=\"baz\"\n");
289 expected.push_str("\n");
290 expected.push_str("exec /bin/echo subcommand --flag1 1 ");
291 let mut buf = Cursor::new(vec![]);
292 conv.convert(result, &mut buf).unwrap();
293 assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
294 }
295}