dockerfile_parser/instructions/
copy.rs1use std::convert::TryFrom;
4
5use snafu::ensure;
6
7use crate::dockerfile_parser::Instruction;
8use crate::parser::{Pair, Rule};
9use crate::{Span, parse_string};
10use crate::SpannedString;
11use crate::error::*;
12
13#[derive(Debug, PartialEq, Eq, Clone)]
17pub struct CopyFlag {
18 pub span: Span,
19 pub name: SpannedString,
20 pub value: SpannedString,
21}
22
23impl CopyFlag {
24 fn from_record(record: Pair) -> Result<CopyFlag> {
25 let span = Span::from_pair(&record);
26 let mut name = None;
27 let mut value = None;
28
29 for field in record.into_inner() {
30 match field.as_rule() {
31 Rule::copy_flag_name => name = Some(parse_string(&field)?),
32 Rule::copy_flag_value => value = Some(parse_string(&field)?),
33 _ => return Err(unexpected_token(field))
34 }
35 }
36
37 let name = name.ok_or_else(|| Error::GenericParseError {
38 message: "copy flags require a key".into(),
39 })?;
40
41 let value = value.ok_or_else(|| Error::GenericParseError {
42 message: "copy flags require a value".into()
43 })?;
44
45 Ok(CopyFlag {
46 span, name, value
47 })
48 }
49}
50
51#[derive(Debug, PartialEq, Eq, Clone)]
55pub struct CopyInstruction {
56 pub span: Span,
57 pub flags: Vec<CopyFlag>,
58 pub sources: Vec<SpannedString>,
59 pub destination: SpannedString
60}
61
62impl CopyInstruction {
63 pub(crate) fn from_record(record: Pair) -> Result<CopyInstruction> {
64 let span = Span::from_pair(&record);
65 let mut flags = Vec::new();
66 let mut paths = Vec::new();
67
68 for field in record.into_inner() {
69 match field.as_rule() {
70 Rule::copy_flag => flags.push(CopyFlag::from_record(field)?),
71 Rule::copy_pathspec => paths.push(parse_string(&field)?),
72 Rule::comment => continue,
73 _ => return Err(unexpected_token(field))
74 }
75 }
76
77 ensure!(
78 paths.len() >= 2,
79 GenericParseError {
80 message: "copy requires at least one source and a destination"
81 }
82 );
83
84 let destination = paths.pop().unwrap();
86
87 Ok(CopyInstruction {
88 span,
89 flags,
90 sources: paths,
91 destination
92 })
93 }
94}
95
96impl<'a> TryFrom<&'a Instruction> for &'a CopyInstruction {
97 type Error = Error;
98
99 fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
100 if let Instruction::Copy(c) = instruction {
101 Ok(c)
102 } else {
103 Err(Error::ConversionError {
104 from: format!("{:?}", instruction),
105 to: "CopyInstruction".into()
106 })
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use indoc::indoc;
114 use pretty_assertions::assert_eq;
115
116 use super::*;
117 use crate::test_util::*;
118
119 #[test]
120 fn copy_basic() -> Result<()> {
121 assert_eq!(
122 parse_single("copy foo bar", Rule::copy)?,
123 CopyInstruction {
124 span: Span { start: 0, end: 12 },
125 flags: vec![],
126 sources: vec![SpannedString {
127 span: Span::new(5, 8),
128 content: "foo".to_string()
129 }],
130 destination: SpannedString {
131 span: Span::new(9, 12),
132 content: "bar".to_string()
133 },
134 }.into()
135 );
136
137 Ok(())
138 }
139
140 #[test]
141 fn copy_multiple_sources() -> Result<()> {
142 assert_eq!(
143 parse_single("copy foo bar baz qux", Rule::copy)?,
144 CopyInstruction {
145 span: Span { start: 0, end: 20 },
146 flags: vec![],
147 sources: vec![SpannedString {
148 span: Span::new(5, 8),
149 content: "foo".to_string(),
150 }, SpannedString {
151 span: Span::new(9, 12),
152 content: "bar".to_string()
153 }, SpannedString {
154 span: Span::new(13, 16),
155 content: "baz".to_string()
156 }],
157 destination: SpannedString {
158 span: Span::new(17, 20),
159 content: "qux".to_string()
160 },
161 }.into()
162 );
163
164 Ok(())
165 }
166
167 #[test]
168 fn copy_multiline() -> Result<()> {
169 assert_eq!(
171 parse_single("copy foo \\\nbar", Rule::copy)?,
172 CopyInstruction {
173 span: Span { start: 0, end: 14 },
174 flags: vec![],
175 sources: vec![SpannedString {
176 span: Span::new(5, 8),
177 content: "foo".to_string(),
178 }],
179 destination: SpannedString {
180 span: Span::new(11, 14),
181 content: "bar".to_string(),
182 },
183 }.into()
184 );
185
186 assert_eq!(
188 parse_single("copy foo\nbar", Rule::copy).is_err(),
189 true
190 );
191
192 Ok(())
193 }
194
195 #[test]
196 fn copy_flags() -> Result<()> {
197 assert_eq!(
198 parse_single(
199 "copy --from=alpine:3.10 /usr/lib/libssl.so.1.1 /tmp/",
200 Rule::copy
201 )?,
202 CopyInstruction {
203 span: Span { start: 0, end: 52 },
204 flags: vec![
205 CopyFlag {
206 span: Span { start: 5, end: 23 },
207 name: SpannedString {
208 content: "from".into(),
209 span: Span { start: 7, end: 11 },
210 },
211 value: SpannedString {
212 content: "alpine:3.10".into(),
213 span: Span { start: 12, end: 23 },
214 }
215 }
216 ],
217 sources: vec![SpannedString {
218 span: Span::new(24, 46),
219 content: "/usr/lib/libssl.so.1.1".to_string(),
220 }],
221 destination: SpannedString {
222 span: Span::new(47, 52),
223 content: "/tmp/".into(),
224 }
225 }.into()
226 );
227
228 Ok(())
229 }
230
231 #[test]
232 fn copy_comments() -> Result<()> {
233 assert_eq!(
234 parse_single(
235 indoc!(r#"
236 copy \
237 --from=alpine:3.10 \
238
239 # hello
240
241 /usr/lib/libssl.so.1.1 \
242 # world
243 /tmp/
244 "#),
245 Rule::copy
246 )?.into_copy().unwrap(),
247 CopyInstruction {
248 span: Span { start: 0, end: 86 },
249 flags: vec![
250 CopyFlag {
251 span: Span { start: 9, end: 27 },
252 name: SpannedString {
253 span: Span { start: 11, end: 15 },
254 content: "from".into(),
255 },
256 value: SpannedString {
257 span: Span { start: 16, end: 27 },
258 content: "alpine:3.10".into(),
259 },
260 }
261 ],
262 sources: vec![SpannedString {
263 span: Span::new(44, 66),
264 content: "/usr/lib/libssl.so.1.1".to_string(),
265 }],
266 destination: SpannedString {
267 span: Span::new(81, 86),
268 content: "/tmp/".into(),
269 },
270 }.into()
271 );
272
273 Ok(())
274 }
275}