dockerfile_parser/instructions/
arg.rs

1// (C) Copyright 2019-2020 Hewlett Packard Enterprise Development LP
2
3use std::convert::TryFrom;
4
5use crate::dockerfile_parser::Instruction;
6use crate::SpannedString;
7use crate::error::*;
8use crate::parse_string;
9use crate::parser::{Pair, Rule};
10use crate::splicer::Span;
11
12/// A Dockerfile [`ARG` instruction][arg].
13///
14/// [arg]: https://docs.docker.com/engine/reference/builder/#arg
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ArgInstruction {
17  pub span: Span,
18
19  /// The argument key
20  pub name: SpannedString,
21
22  /// An optional argument value.
23  ///
24  /// This may be unset when passing arguments through to later stages in a
25  /// [multi-stage build][build].
26  ///
27  /// [build]: https://docs.docker.com/develop/develop-images/multistage-build/
28  pub value: Option<SpannedString>,
29}
30
31impl ArgInstruction {
32  pub(crate) fn from_record(record: Pair) -> Result<ArgInstruction> {
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::arg_name => name = Some(parse_string(&field)?),
40        Rule::arg_quoted_value => value = Some(parse_string(&field)?),
41        Rule::arg_value => value = Some(parse_string(&field)?),
42        Rule::comment => continue,
43        _ => return Err(unexpected_token(field))
44      }
45    }
46
47    let name = match name {
48      Some(name) => name,
49      _ => return Err(Error::GenericParseError {
50        message: "arg name is required".into()
51      })
52    };
53
54    Ok(ArgInstruction {
55      span,
56      name,
57      value,
58    })
59  }
60}
61
62impl<'a> TryFrom<&'a Instruction> for &'a ArgInstruction {
63 type Error = Error;
64
65 fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
66   if let Instruction::Arg(a) = instruction {
67     Ok(a)
68   } else {
69     Err(Error::ConversionError {
70       from: format!("{:?}", instruction),
71       to: "ArgInstruction".into()
72     })
73   }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79  use pretty_assertions::assert_eq;
80
81  use super::*;
82  use crate::Dockerfile;
83  use crate::test_util::*;
84
85  #[test]
86  fn arg_strings() -> Result<()> {
87    assert_eq!(
88      parse_single(r#"arg foo=bar"#, Rule::arg)?,
89      ArgInstruction {
90        span: Span::new(0, 11),
91        name: SpannedString {
92          span: Span::new(4, 7),
93          content: "foo".into(),
94        },
95        value: Some(SpannedString {
96          span: Span::new(8, 11),
97          content: "bar".into(),
98        }),
99      }.into()
100    );
101
102    assert_eq!(
103      parse_single(r#"arg foo="bar""#, Rule::arg)?,
104      ArgInstruction {
105        span: Span::new(0, 13),
106        name: SpannedString {
107          span: Span::new(4, 7),
108          content: "foo".into(),
109        },
110        value: Some(SpannedString {
111          span: Span::new(8, 13),
112          content: "bar".into(),
113        }),
114      }.into()
115    );
116
117    assert_eq!(
118      parse_single(r#"arg foo='bar'"#, Rule::arg)?,
119      ArgInstruction {
120        span: Span::new(0, 13),
121        name: SpannedString {
122          span: Span::new(4, 7),
123          content: "foo".into(),
124        },
125        value: Some(SpannedString {
126          span: Span::new(8, 13),
127          content: "bar".into(),
128        }),
129      }.into()
130    );
131
132    assert!(Dockerfile::parse(r#"arg foo="bar"bar"#).is_err());
133    assert!(Dockerfile::parse(r#"arg foo='bar'bar"#).is_err());
134
135    Ok(())
136  }
137}