dockerfile_parser/
splicer.rs1use std::convert::TryInto;
4use std::fmt;
5
6use crate::parser::Pair;
7use crate::dockerfile_parser::Dockerfile;
8
9#[derive(Debug)]
11struct SpliceOffset {
12 position: usize,
13 offset: isize
14}
15
16#[derive(PartialEq, Eq, Clone, Ord, PartialOrd, Copy)]
18pub struct Span {
19 pub start: usize,
20 pub end: usize
21}
22
23impl Span {
24 pub fn new(start: usize, end: usize) -> Span {
25 Span { start, end }
26 }
27
28 pub(crate) fn from_pair(record: &Pair) -> Span {
29 let pest_span = record.as_span();
30
31 Span {
32 start: pest_span.start(),
33 end: pest_span.end()
34 }
35 }
36
37 fn adjust_offsets(&self, offsets: &[SpliceOffset]) -> Span {
38 let mut start = self.start as isize;
39 let mut end = self.end as isize;
40
41 for splice in offsets {
42 if splice.position < start as usize {
43 start += splice.offset;
44 end += splice.offset;
45 } else if splice.position < end as usize {
46 end += splice.offset;
47 }
48 }
49
50 Span {
51 start: start.try_into().ok().unwrap_or(0),
52 end: end.try_into().ok().unwrap_or(0)
53 }
54 }
55
56 pub fn relative_span(&self, dockerfile: &Dockerfile) -> (usize, Span) {
63 let mut line_start_offset = 0;
64 let mut lines = 0;
65 for (i, c) in dockerfile.content.as_bytes().iter().enumerate() {
66 if i == self.start {
67 break;
68 }
69
70 if *c == b'\n' {
71 lines += 1;
72 line_start_offset = i + 1;
73 }
74 }
75
76 let start = self.start - line_start_offset;
77 let end = start + (self.end - self.start);
78
79 (lines, Span { start, end })
80 }
81}
82
83impl From<(usize, usize)> for Span {
84 fn from(tup: (usize, usize)) -> Span {
85 Span::new(tup.0, tup.1)
86 }
87}
88
89impl From<&Pair<'_>> for Span {
90 fn from(pair: &Pair<'_>) -> Self {
91 Span::from_pair(&pair)
92 }
93}
94
95impl fmt::Debug for Span {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.debug_tuple("")
98 .field(&self.start)
99 .field(&self.end)
100 .finish()
101 }
102}
103
104pub struct Splicer {
133 pub content: String,
135
136 splice_offsets: Vec<SpliceOffset>
137}
138
139impl Splicer {
140 pub(crate) fn from(dockerfile: &Dockerfile) -> Splicer {
142 Splicer {
143 content: dockerfile.content.clone(),
144 splice_offsets: Vec::new()
145 }
146 }
147
148 pub(crate) fn from_str(s: &str) -> Splicer {
149 Splicer {
150 content: s.to_string(),
151 splice_offsets: Vec::new()
152 }
153 }
154
155 pub fn splice(&mut self, span: &Span, replacement: &str) {
164 let span = span.adjust_offsets(&self.splice_offsets);
165
166 let prev_len = span.end - span.start;
168 let new_len = replacement.len();
169 let offset = new_len as isize - prev_len as isize;
170 self.splice_offsets.push(
171 SpliceOffset { position: span.start, offset }
172 );
173
174 let (beginning, rest) = self.content.split_at(span.start);
176 let (_, end) = rest.split_at(span.end - span.start);
177 self.content = format!("{}{}{}", beginning, replacement, end);
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use std::convert::TryInto;
184 use indoc::indoc;
185 use crate::*;
186
187 #[test]
188 fn test_relative_span() {
189 let d = Dockerfile::parse(indoc!(r#"
190 FROM alpine:3.10 as build
191 FROM alpine:3.10
192
193 RUN echo "hello world"
194
195 COPY --from=build /foo /bar
196 "#)).unwrap();
197
198 let first_from = TryInto::<&FromInstruction>::try_into(&d.instructions[0]).unwrap();
199 assert_eq!(
200 first_from.alias.as_ref().unwrap().span.relative_span(&d),
201 (0, (20, 25).into())
202 );
203
204 let copy = TryInto::<&CopyInstruction>::try_into(&d.instructions[3]).unwrap();
205
206 let len = copy.span.end - copy.span.start;
207 let content = &d.content[copy.span.start .. copy.span.end];
208
209 let (rel_line_index, rel_span) = copy.span.relative_span(&d);
210 let rel_len = rel_span.end - rel_span.start;
211 assert_eq!(len, rel_len);
212
213 let rel_line = d.content.lines().collect::<Vec<&str>>()[rel_line_index];
214 let rel_content = &rel_line[rel_span.start .. rel_span.end];
215 assert_eq!(rel_line, "COPY --from=build /foo /bar");
216 assert_eq!(content, rel_content);
217
218 assert_eq!(
220 copy.span.relative_span(&d),
221 (5, (0, 27).into())
222 );
223
224 assert_eq!(
226 copy.flags[0].span.relative_span(&d),
227 (5, (5, 17).into())
228 );
229
230 assert_eq!(
232 copy.flags[0].value.span.relative_span(&d),
233 (5, (12, 17).into())
234 );
235 }
236}