1use 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#[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 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#[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 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 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}