1use std::convert::TryFrom;
4
5use crate::dockerfile_parser::Instruction;
6use crate::Span;
7use crate::error::*;
8use crate::parser::{Pair, Rule};
9use crate::util::*;
10
11use enquote::unquote;
12use snafu::ResultExt;
13
14#[derive(Debug, PartialEq, Eq, Clone)]
16pub struct EnvVar {
17 pub span: Span,
18 pub key: SpannedString,
19 pub value: BreakableString,
20}
21
22impl EnvVar {
23 pub fn new(span: Span, key: SpannedString, value: impl Into<BreakableString>) -> Self {
24 EnvVar {
25 span,
26 key: key,
27 value: value.into(),
28 }
29 }
30}
31
32#[derive(Debug, PartialEq, Eq, Clone)]
36pub struct EnvInstruction {
37 pub span: Span,
38 pub vars: Vec<EnvVar>
39}
40
41fn parse_env_pair(record: Pair) -> Result<EnvVar> {
43 let span = Span::from_pair(&record);
44 let mut key = None;
45 let mut value = None;
46
47 for field in record.into_inner() {
48 match field.as_rule() {
49 Rule::env_name => key = Some(parse_string(&field)?),
50 Rule::env_pair_value => {
51 value = Some(
52 BreakableString::new(&field).add_string(&field, field.as_str())
53 );
54 },
55 Rule::env_pair_quoted_value => {
56 let v = unquote(field.as_str()).context(UnescapeError)?;
57
58 value = Some(
59 BreakableString::new(&field).add_string(&field, v)
60 );
61 },
62 _ => return Err(unexpected_token(field))
63 }
64 }
65
66 let key = key.ok_or_else(|| Error::GenericParseError {
67 message: "env pair requires a key".into()
68 })?;
69
70 let value = value.ok_or_else(|| Error::GenericParseError {
71 message: "env pair requires a value".into()
72 })?;
73
74 Ok(EnvVar {
75 span,
76 key,
77 value,
78 })
79}
80
81impl EnvInstruction {
82 pub(crate) fn from_record(record: Pair) -> Result<EnvInstruction> {
83 let span = Span::from_pair(&record);
84 let field = record.into_inner().next().unwrap();
85
86 match field.as_rule() {
87 Rule::env_single => EnvInstruction::from_single_record(span, field),
88 Rule::env_pairs => EnvInstruction::from_pairs_record(span, field),
89 _ => Err(unexpected_token(field)),
90 }
91 }
92
93 fn from_pairs_record(span: Span, record: Pair) -> Result<EnvInstruction> {
94 let mut vars = Vec::new();
95
96 for field in record.into_inner() {
97 match field.as_rule() {
98 Rule::env_pair => vars.push(parse_env_pair(field)?),
99 Rule::comment => continue,
100 _ => return Err(unexpected_token(field))
101 }
102 }
103
104 Ok(EnvInstruction {
105 span,
106 vars,
107 })
108 }
109
110 fn from_single_record(span: Span, record: Pair) -> Result<EnvInstruction> {
111 let mut key = None;
112 let mut value = None;
113
114 for field in record.into_inner() {
115 match field.as_rule() {
116 Rule::env_name => key = Some(parse_string(&field)?),
117 Rule::env_single_value => value = Some(parse_any_breakable(field)?),
118 Rule::env_single_quoted_value => {
119 let v = unquote(field.as_str()).context(UnescapeError)?;
120
121 value = Some(
122 BreakableString::new(&field).add_string(&field, v)
123 );
124 },
125 Rule::comment => continue,
126 _ => return Err(unexpected_token(field))
127 }
128 }
129
130 let key = key.ok_or_else(|| Error::GenericParseError {
131 message: "env requires a key".into()
132 })?;
133
134 let value = value.ok_or_else(|| Error::GenericParseError {
135 message: "env requires a value".into()
136 })?;
137
138 Ok(EnvInstruction {
139 span,
140 vars: vec![EnvVar {
141 span: Span::new(key.span.start, value.span.end),
142 key,
143 value,
144 }],
145 })
146 }
147}
148
149impl<'a> TryFrom<&'a Instruction> for &'a EnvInstruction {
150 type Error = Error;
151
152 fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
153 if let Instruction::Env(e) = instruction {
154 Ok(e)
155 } else {
156 Err(Error::ConversionError {
157 from: format!("{:?}", instruction),
158 to: "EnvInstruction".into()
159 })
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use indoc::indoc;
167 use pretty_assertions::assert_eq;
168
169 use super::*;
170 use crate::Dockerfile;
171 use crate::test_util::*;
172
173 #[test]
174 fn env() -> Result<()> {
175 assert_eq!(
176 parse_single(r#"env foo=bar"#, Rule::env)?.into_env().unwrap(),
177 EnvInstruction {
178 span: Span::new(0, 11),
179 vars: vec![EnvVar::new(
180 Span::new(4, 11),
181 SpannedString {
182 span: Span::new(4, 7),
183 content: "foo".to_string(),
184 },
185 ((8, 11), "bar"),
186 )],
187 }
188 );
189
190 assert_eq!(
191 parse_single(r#"env FOO_BAR="baz""#, Rule::env)?,
192 EnvInstruction {
193 span: Span::new(0, 17),
194 vars: vec![EnvVar::new(
195 Span::new(4, 17),
196 SpannedString {
197 span: Span::new(4, 11),
198 content: "FOO_BAR".to_string(),
199 },
200 ((12, 17), "baz"),
201 )],
202 }.into()
203 );
204
205 assert_eq!(
206 parse_single(r#"env FOO_BAR "baz""#, Rule::env)?,
207 EnvInstruction {
208 span: Span::new(0, 17),
209 vars: vec![EnvVar::new(
210 Span::new(4, 17),
211 SpannedString {
212 span: Span::new(4, 11),
213 content: "FOO_BAR".to_string(),
214 },
215 ((12, 17), "baz")),
216 ],
217 }.into()
218 );
219
220 assert_eq!(
221 parse_single(r#"env foo="bar\"baz""#, Rule::env)?,
222 EnvInstruction {
223 span: Span::new(0, 18),
224 vars: vec![EnvVar::new(
225 Span::new(4, 18),
226 SpannedString {
227 span: Span::new(4, 7),
228 content: "foo".to_string(),
229 },
230 ((8, 18), "bar\"baz"),
231 )],
232 }.into()
233 );
234
235 assert_eq!(
236 parse_single(r#"env foo='bar'"#, Rule::env)?,
237 EnvInstruction {
238 span: Span::new(0, 13),
239 vars: vec![EnvVar::new(
240 Span::new(4, 13),
241 SpannedString {
242 span: Span::new(4, 7),
243 content: "foo".to_string(),
244 },
245 ((8, 13), "bar"),
246 )],
247 }.into()
248 );
249
250 assert_eq!(
251 parse_single(r#"env foo='bar\'baz'"#, Rule::env)?,
252 EnvInstruction {
253 span: Span::new(0, 18),
254 vars: vec![EnvVar::new(
255 Span::new(4, 18),
256 SpannedString {
257 span: Span::new(4, 7),
258 content: "foo".to_string(),
259 },
260 ((8, 18), "bar'baz"),
261 )],
262 }.into()
263 );
264
265 assert_eq!(
266 parse_single(r#"env foo="123" bar='456' baz=789"#, Rule::env)?,
267 EnvInstruction {
268 span: Span::new(0, 31),
269 vars: vec![
270 EnvVar::new(
271 Span::new(4, 13),
272 SpannedString {
273 span: Span::new(4, 7),
274 content: "foo".to_string(),
275 },
276 ((8, 13), "123")
277 ),
278 EnvVar::new(
279 Span::new(14, 23),
280 SpannedString {
281 span: Span::new(14, 17),
282 content: "bar".to_string(),
283 },
284 ((18, 23), "456")
285 ),
286 EnvVar::new(
287 Span::new(24, 31),
288 SpannedString {
289 span: Span::new(24, 27),
290 content: "baz".to_string(),
291 },
292 ((28, 31), "789")
293 ),
294 ],
295 }.into()
296 );
297
298 assert!(Dockerfile::parse(r#"env foo="bar"bar"#).is_err());
299 assert!(Dockerfile::parse(r#"env foo='bar'bar"#).is_err());
300
301 Ok(())
302 }
303
304 #[test]
305 fn test_multiline_pairs() -> Result<()> {
306 assert_eq!(
308 parse_single(
309 indoc!(r#"
310 env foo=a \
311 bar=b \
312 baz=c \
313
314 "#),
315 Rule::env
316 )?.into_env().unwrap().vars,
317 vec![
318 EnvVar::new(
319 Span::new(4, 9),
320 SpannedString {
321 span: Span::new(4, 7),
322 content: "foo".to_string(),
323 },
324 ((8, 9), "a")
325 ),
326 EnvVar::new(
327 Span::new(14, 19),
328 SpannedString {
329 span: Span::new(14, 17),
330 content: "bar".to_string(),
331 },
332 ((18, 19), "b")
333 ),
334 EnvVar::new(
335 Span::new(24, 29),
336 SpannedString {
337 span: Span::new(24, 27),
338 content: "baz".to_string(),
339 },
340 ((28, 29), "c")
341 )
342 ]
343 );
344
345 Ok(())
346 }
347
348 #[test]
349 fn test_multiline_single_env() -> Result<()> {
350 assert_eq!(
351 parse_single(
352 indoc!(r#"
353 env foo Lorem ipsum dolor sit amet, \
354 consectetur adipiscing elit, \
355 sed do eiusmod tempor incididunt ut \
356 labore et dolore magna aliqua.
357 "#),
358 Rule::env
359 )?.into_env().unwrap().vars,
360 vec![
361 EnvVar::new(
362 Span::new(4, 143),
363 SpannedString {
364 span: Span::new(4, 7),
365 content: "foo".to_string(),
366 },
367 BreakableString::new((8, 143))
368 .add_string((8, 36), "Lorem ipsum dolor sit amet, ")
369 .add_string((38, 69), " consectetur adipiscing elit, ")
370 .add_string((71, 109), " sed do eiusmod tempor incididunt ut ")
371 .add_string((111, 143), " labore et dolore magna aliqua.")
372 )
373 ]
374 );
375
376 assert_eq!(
379 parse_single(
380 indoc!(r#"
381 env \
382 foo \
383 Lorem ipsum dolor sit amet, \
384 consectetur adipiscing elit
385 "#),
386 Rule::env
387 )?.into_env().unwrap().vars,
388 vec![
389 EnvVar::new(
390 Span::new(8, 75),
391 SpannedString {
392 span: Span::new(8, 11),
393 content: "foo".to_string(),
394 },
395 BreakableString::new((16, 75))
396 .add_string((16, 44), "Lorem ipsum dolor sit amet, ")
397 .add_string((46, 75), " consectetur adipiscing elit")
398 )
399 ]
400 );
401
402 assert_eq!(
403 parse_single(
404 indoc!(r#"
405 env \
406 foo \
407 # bar
408 Lorem ipsum dolor sit amet, \
409 # baz
410 consectetur adipiscing elit
411 "#),
412 Rule::env
413 )?.into_env().unwrap().vars,
414 vec![
415 EnvVar::new(
416 Span::new(8, 91),
417 SpannedString {
418 span: Span::new(8, 11),
419 content: "foo".to_string(),
420 },
421 BreakableString::new((24, 91))
422 .add_string((24, 52), "Lorem ipsum dolor sit amet, ")
423 .add_comment((56, 61), "# baz")
424 .add_string((62, 91), " consectetur adipiscing elit")
425 )
426 ]
427 );
428
429 Ok(())
430 }
431}