dockerfile_parser/
splicer.rsuse std::convert::TryInto;
use std::fmt;
use crate::parser::Pair;
use crate::dockerfile_parser::Dockerfile;
#[derive(Debug)]
struct SpliceOffset {
position: usize,
offset: isize
}
#[derive(PartialEq, Eq, Clone, Ord, PartialOrd, Copy)]
pub struct Span {
pub start: usize,
pub end: usize
}
impl Span {
pub fn new(start: usize, end: usize) -> Span {
Span { start, end }
}
pub(crate) fn from_pair(record: &Pair) -> Span {
let pest_span = record.as_span();
Span {
start: pest_span.start(),
end: pest_span.end()
}
}
fn adjust_offsets(&self, offsets: &[SpliceOffset]) -> Span {
let mut start = self.start as isize;
let mut end = self.end as isize;
for splice in offsets {
if splice.position < start as usize {
start += splice.offset;
end += splice.offset;
} else if splice.position < end as usize {
end += splice.offset;
}
}
Span {
start: start.try_into().ok().unwrap_or(0),
end: end.try_into().ok().unwrap_or(0)
}
}
pub fn relative_span(&self, dockerfile: &Dockerfile) -> (usize, Span) {
let mut line_start_offset = 0;
let mut lines = 0;
for (i, c) in dockerfile.content.as_bytes().iter().enumerate() {
if i == self.start {
break;
}
if *c == b'\n' {
lines += 1;
line_start_offset = i + 1;
}
}
let start = self.start - line_start_offset;
let end = start + (self.end - self.start);
(lines, Span { start, end })
}
}
impl From<(usize, usize)> for Span {
fn from(tup: (usize, usize)) -> Span {
Span::new(tup.0, tup.1)
}
}
impl From<&Pair<'_>> for Span {
fn from(pair: &Pair<'_>) -> Self {
Span::from_pair(&pair)
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("")
.field(&self.start)
.field(&self.end)
.finish()
}
}
pub struct Splicer {
pub content: String,
splice_offsets: Vec<SpliceOffset>
}
impl Splicer {
pub(crate) fn from(dockerfile: &Dockerfile) -> Splicer {
Splicer {
content: dockerfile.content.clone(),
splice_offsets: Vec::new()
}
}
pub(crate) fn from_str(s: &str) -> Splicer {
Splicer {
content: s.to_string(),
splice_offsets: Vec::new()
}
}
pub fn splice(&mut self, span: &Span, replacement: &str) {
let span = span.adjust_offsets(&self.splice_offsets);
let prev_len = span.end - span.start;
let new_len = replacement.len();
let offset = new_len as isize - prev_len as isize;
self.splice_offsets.push(
SpliceOffset { position: span.start, offset }
);
let (beginning, rest) = self.content.split_at(span.start);
let (_, end) = rest.split_at(span.end - span.start);
self.content = format!("{}{}{}", beginning, replacement, end);
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use indoc::indoc;
use crate::*;
#[test]
fn test_relative_span() {
let d = Dockerfile::parse(indoc!(r#"
FROM alpine:3.10 as build
FROM alpine:3.10
RUN echo "hello world"
COPY --from=build /foo /bar
"#)).unwrap();
let first_from = TryInto::<&FromInstruction>::try_into(&d.instructions[0]).unwrap();
assert_eq!(
first_from.alias.as_ref().unwrap().span.relative_span(&d),
(0, (20, 25).into())
);
let copy = TryInto::<&CopyInstruction>::try_into(&d.instructions[3]).unwrap();
let len = copy.span.end - copy.span.start;
let content = &d.content[copy.span.start .. copy.span.end];
let (rel_line_index, rel_span) = copy.span.relative_span(&d);
let rel_len = rel_span.end - rel_span.start;
assert_eq!(len, rel_len);
let rel_line = d.content.lines().collect::<Vec<&str>>()[rel_line_index];
let rel_content = &rel_line[rel_span.start .. rel_span.end];
assert_eq!(rel_line, "COPY --from=build /foo /bar");
assert_eq!(content, rel_content);
assert_eq!(
copy.span.relative_span(&d),
(5, (0, 27).into())
);
assert_eq!(
copy.flags[0].span.relative_span(&d),
(5, (5, 17).into())
);
assert_eq!(
copy.flags[0].value.span.relative_span(&d),
(5, (12, 17).into())
);
}
}