dockerfile_parser/instructions/
label.rs

1// (C) Copyright 2019-2020 Hewlett Packard Enterprise Development LP
2
3use std::convert::TryFrom;
4
5use crate::dockerfile_parser::Instruction;
6use crate::parser::{Pair, Rule};
7use crate::Span;
8use crate::util::*;
9use crate::error::*;
10
11use enquote::unquote;
12use snafu::ResultExt;
13
14/// A single label key/value pair.
15#[derive(Debug, PartialEq, Eq, Clone)]
16pub struct Label {
17  pub span: Span,
18  pub name: SpannedString,
19  pub value: SpannedString
20}
21
22impl Label {
23  pub fn new(span: Span, name: SpannedString, value: SpannedString) -> Label
24  {
25    Label {
26      span,
27      name,
28      value,
29    }
30  }
31
32  pub(crate) fn from_record(record: Pair) -> Result<Label> {
33    let span = Span::from_pair(&record);
34    let mut name = None;
35    let mut value = None;
36
37    for field in record.into_inner() {
38      match field.as_rule() {
39        Rule::label_name | Rule::label_single_name => name = Some(parse_string(&field)?),
40        Rule::label_quoted_name | Rule::label_single_quoted_name => {
41          // label seems to be uniquely able to span multiple lines when quoted
42          let v = unquote(&clean_escaped_breaks(field.as_str()))
43            .context(UnescapeError)?;
44
45          name = Some(SpannedString {
46            content: v,
47            span: Span::from_pair(&field),
48          });
49        },
50
51        Rule::label_value => value = Some(parse_string(&field)?),
52        Rule::label_quoted_value => {
53          let v = unquote(&clean_escaped_breaks(field.as_str()))
54            .context(UnescapeError)?;
55
56          value = Some(SpannedString {
57            content: v,
58            span: Span::from_pair(&field),
59          });
60        },
61        Rule::comment => continue,
62        _ => return Err(unexpected_token(field))
63      }
64    }
65
66    let name = name.ok_or_else(|| Error::GenericParseError {
67      message: "label name is required".into()
68    })?;
69
70    let value = value.ok_or_else(|| Error::GenericParseError {
71      message: "label value is required".into()
72    })?;
73
74    Ok(Label::new(span, name, value))
75  }
76}
77
78/// A Dockerfile [`LABEL` instruction][label].
79///
80/// A single `LABEL` instruction may set many labels.
81///
82/// [label]: https://docs.docker.com/engine/reference/builder/#label
83#[derive(Debug, PartialEq, Eq, Clone)]
84pub struct LabelInstruction {
85  pub span: Span,
86  pub labels: Vec<Label>,
87}
88
89impl LabelInstruction {
90  pub(crate) fn from_record(record: Pair) -> Result<LabelInstruction> {
91    let span = Span::from_pair(&record);
92    let mut labels = Vec::new();
93
94    for field in record.into_inner() {
95      match field.as_rule() {
96        Rule::label_pair => labels.push(Label::from_record(field)?),
97        Rule::label_single => labels.push(Label::from_record(field)?),
98        Rule::comment => continue,
99        _ => return Err(unexpected_token(field))
100      }
101    }
102
103    Ok(LabelInstruction {
104      span,
105      labels,
106    })
107  }
108}
109
110impl<'a> TryFrom<&'a Instruction> for &'a LabelInstruction {
111  type Error = Error;
112
113  fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
114    if let Instruction::Label(l) = instruction {
115      Ok(l)
116    } else {
117      Err(Error::ConversionError {
118        from: format!("{:?}", instruction),
119        to: "LabelInstruction".into()
120      })
121    }
122  }
123}
124
125#[cfg(test)]
126mod tests {
127  use indoc::indoc;
128  use pretty_assertions::assert_eq;
129
130  use super::*;
131  use crate::test_util::*;
132
133  #[test]
134  fn label_basic() -> Result<()> {
135    assert_eq!(
136      parse_single("label foo=bar", Rule::label)?,
137      LabelInstruction {
138        span: Span::new(0, 13),
139        labels: vec![
140          Label::new(
141            Span::new(6, 13),
142            SpannedString {
143              span: Span::new(6, 9),
144              content: "foo".to_string(),
145            }, SpannedString {
146              span: Span::new(10, 13),
147              content: "bar".to_string()
148            },
149          )
150        ]
151      }.into()
152    );
153
154    assert_eq!(
155      parse_single("label foo.bar=baz", Rule::label)?,
156      LabelInstruction {
157        span: Span::new(0, 17),
158        labels: vec![
159          Label::new(
160            Span::new(6, 17),
161            SpannedString {
162              span: Span::new(6, 13),
163              content: "foo.bar".to_string(),
164            },
165            SpannedString {
166              span: Span::new(14, 17),
167              content: "baz".to_string()
168            }
169          )
170        ]
171      }.into()
172    );
173
174    assert_eq!(
175      parse_single(r#"label "foo.bar"="baz qux""#, Rule::label)?,
176      LabelInstruction {
177        span: Span::new(0, 25),
178        labels: vec![
179          Label::new(
180            Span::new(6, 25),
181            SpannedString {
182              span: Span::new(6, 15),
183              content: "foo.bar".to_string(),
184            }, SpannedString {
185              span: Span::new(16, 25),
186              content: "baz qux".to_string(),
187            },
188          )
189        ]
190      }.into()
191    );
192
193    // this is undocumented but supported :(
194    assert_eq!(
195      parse_single(r#"label foo.bar baz"#, Rule::label)?,
196      LabelInstruction {
197        span: Span::new(0, 17),
198        labels: vec![
199          Label::new(
200            Span::new(5, 17),
201            SpannedString {
202              span: Span::new(6, 13),
203              content: "foo.bar".to_string(),
204            },
205            SpannedString {
206              span: Span::new(14, 17),
207              content: "baz".to_string(),
208            }
209          )
210        ]
211      }.into()
212    );
213    assert_eq!(
214      parse_single(r#"label "foo.bar" "baz qux""#, Rule::label)?,
215      LabelInstruction {
216        span: Span::new(0, 25),
217        labels: vec![
218          Label::new(
219            Span::new(5, 25),
220            SpannedString {
221              span: Span::new(6, 15),
222              content: "foo.bar".to_string(),
223            },
224            SpannedString {
225              span: Span::new(16, 25),
226              content: "baz qux".to_string(),
227            },
228          )
229        ]
230      }.into()
231    );
232
233    Ok(())
234  }
235
236  #[test]
237  fn label_multi() -> Result<()> {
238    assert_eq!(
239      parse_single(r#"label foo=bar baz="qux" "quux quuz"="corge grault""#, Rule::label)?,
240      LabelInstruction {
241        span: Span::new(0, 50),
242        labels: vec![
243          Label::new(
244            Span::new(6, 13),
245            SpannedString {
246              span: Span::new(6, 9),
247              content: "foo".to_string(),
248            },
249            SpannedString {
250              span: Span::new(10, 13),
251              content: "bar".to_string(),
252            },
253          ),
254          Label::new(
255            Span::new(14, 23),
256            SpannedString {
257              span: Span::new(14, 17),
258              content: "baz".to_string(),
259            },
260            SpannedString {
261              span: Span::new(18, 23),
262              content: "qux".to_string(),
263            },
264          ),
265          Label::new(
266            Span::new(24, 50),
267            SpannedString {
268              span: Span::new(24, 35),
269              content: "quux quuz".to_string(),
270            },
271            SpannedString {
272              span: Span::new(36, 50),
273              content: "corge grault".to_string(),
274            },
275          )
276        ]
277      }.into()
278    );
279
280    assert_eq!(
281      parse_single(
282        r#"label foo=bar \
283          baz="qux" \
284          "quux quuz"="corge grault""#,
285        Rule::label
286      )?,
287      LabelInstruction {
288        span: Span::new(0, 74),
289        labels: vec![
290          Label::new(
291            Span::new(6, 13),
292            SpannedString {
293              span: Span::new(6, 9),
294              content: "foo".to_string(),
295            },
296            SpannedString {
297              span: Span::new(10, 13),
298              content: "bar".to_string(),
299            },
300          ),
301          Label::new(
302            Span::new(26, 35),
303            SpannedString {
304              span: Span::new(26, 29),
305              content: "baz".to_string(),
306            },
307            SpannedString {
308              span: Span::new(30, 35),
309              content: "qux".to_string(),
310            },
311          ),
312          Label::new(
313            Span::new(48, 74),
314            SpannedString {
315              span: Span::new(48, 59),
316              content: "quux quuz".to_string(),
317            },
318            SpannedString {
319              span: Span::new(60, 74),
320              content: "corge grault".to_string(),
321            },
322          )
323        ]
324      }.into()
325    );
326
327    Ok(())
328  }
329
330  #[test]
331  fn label_multiline() -> Result<()> {
332    assert_eq!(
333      parse_single(r#"label "foo.bar"="baz\n qux""#, Rule::label)?,
334      LabelInstruction {
335        span: Span::new(0, 27),
336        labels: vec![
337          Label::new(
338            Span::new(6, 27),
339            SpannedString {
340              span: Span::new(6, 15),
341              content: "foo.bar".to_string(),
342            },
343            SpannedString {
344              span: Span::new(16, 27),
345              content: "baz\n qux".to_string(),
346            },
347          )
348        ]
349      }.into()
350    );
351
352    assert_eq!(
353      parse_single(r#"label "foo\nbar"="baz\n qux""#, Rule::label)?,
354      LabelInstruction {
355        span: Span::new(0, 28),
356        labels: vec![
357          Label::new(
358            Span::new(6, 28),
359            SpannedString {
360              span: Span::new(6, 16),
361              content: "foo\nbar".to_string(),
362            },
363            SpannedString {
364              span: Span::new(17, 28),
365              content: "baz\n qux".to_string(),
366            },
367          )
368        ]
369      }.into()
370    );
371
372    Ok(())
373  }
374
375  #[test]
376  fn label_multi_multiline() -> Result<()> {
377    assert_eq!(
378      parse_single(
379        r#"label foo=bar \
380          "lorem ipsum
381          dolor
382          "="sit
383          amet" \
384          baz=qux"#,
385        Rule::label
386      )?,
387      LabelInstruction {
388        span: Span::new(0, 107),
389        labels: vec![
390          Label::new(
391            Span::new(6, 13),
392            SpannedString {
393              span: Span::new(6, 9),
394              content: "foo".to_string(),
395            },
396            SpannedString {
397              span: Span::new(10, 13),
398              content: "bar".to_string(),
399            },
400          ),
401          Label::new(
402            Span::new(26, 87),
403            SpannedString {
404              span: Span::new(26, 66),
405              content: "lorem ipsum\n          dolor\n          ".to_string(),
406            },
407            SpannedString {
408              span: Span::new(67, 87),
409              content: "sit\n          amet".to_string(),
410            },
411          ),
412          Label::new(
413            Span::new(100, 107),
414            SpannedString {
415              span: Span::new(100, 103),
416              content: "baz".to_string(),
417            },
418            SpannedString {
419              span: Span::new(104, 107),
420              content: "qux".to_string(),
421            },
422          )
423        ]
424      }.into()
425    );
426
427    Ok(())
428  }
429
430  #[test]
431  fn label_multiline_improper_continuation() -> Result<()> {
432    // note: docker allows empty line continuations (but may print a warning)
433    assert_eq!(
434      parse_single(
435        indoc!(r#"
436          label foo=a \
437            bar=b \
438            baz=c \
439
440        "#),
441        Rule::label
442      )?.into_label().unwrap().labels,
443      vec![
444        Label::new(
445          Span::new(6, 11),
446          SpannedString {
447          span: Span::new(6, 9),
448            content: "foo".to_string(),
449          },
450          SpannedString {
451            span: Span::new(10, 11),
452            content: "a".to_string(),
453          },
454        ),
455        Label::new(
456          Span::new(16, 21),
457          SpannedString {
458            span: Span::new(16, 19),
459            content: "bar".to_string(),
460          },
461          SpannedString {
462            span: Span::new(20, 21),
463            content: "b".to_string(),
464          },
465        ),
466        Label::new(
467          Span::new(26, 31),
468          SpannedString {
469            span: Span::new(26, 29),
470            content: "baz".to_string(),
471          },
472          SpannedString {
473            span: Span::new(30, 31),
474            content: "c".to_string(),
475          },
476        ),
477      ]
478    );
479
480    Ok(())
481  }
482}