Skip to content

Commit

Permalink
Merge pull request #4 from HewlettPackard/stage-improvements
Browse files Browse the repository at this point in the history
Stage improvements
  • Loading branch information
timothyb89 authored Jun 24, 2020
2 parents 0bc4fdd + ab7238c commit ebc48f2
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 86 deletions.
5 changes: 4 additions & 1 deletion examples/stages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ fn wrap() -> Result<()> {

let dockerfile = Dockerfile::from_reader(f)?;
for stage in dockerfile.iter_stages() {
println!("stage #{}", stage.index);
println!(
"stage #{} (parent: {:?}, root: {:?})",
stage.index, stage.parent, stage.root
);

for ins in stage.instructions {
println!(" {:?}", ins);
Expand Down
92 changes: 8 additions & 84 deletions src/dockerfile_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use crate::error::*;
pub use crate::parser::*;
pub use crate::instructions::*;
pub use crate::splicer::*;
pub use crate::stage::*;

/// A single Dockerfile instruction.
///
Expand Down Expand Up @@ -179,83 +180,6 @@ fn parse_dockerfile(input: &str) -> Result<Dockerfile> {
})
}

/// A single stage in a [multi-stage build].
///
/// A stage begins with (and includes) a `FROM` instruction and continues until
/// (but does *not* include) the next `FROM` instruction, if any.
///
/// Stages have an index and an optional alias. Later `COPY --from=$index [...]`
/// instructions may copy files between unnamed build stages. The alias, if
/// defined in this stage's `FROM` instruction, may be used as well.
///
/// Note that instructions in a Dockerfile before the first `FROM` are not
/// included in the first stage's list of instructions.
///
/// [multi-stage build]: https://docs.docker.com/develop/develop-images/multistage-build/
pub struct Stage<'a> {
pub index: usize,
pub instructions: Vec<&'a Instruction>
}

/// Iterates over build stages within a Dockerfile.
pub struct StageIterator<'a> {
dockerfile: &'a Dockerfile,
stage_index: usize,
instruction_index: usize
}

impl<'a> Iterator for StageIterator<'a> {
type Item = Stage<'a>;

fn next(&mut self) -> Option<Stage<'a>> {
let mut instructions = Vec::new();

// instructions before the first FROM are not part of any stage and should
// be skipped
// to simplify things we generalize this and skip all instructions from
// `instruction_index` until the first FROM, regardless of whether or not
// this is the beginning of the entire Dockerfile
let mut preamble = true;

let mut iter = self.dockerfile.instructions.iter()
.skip(self.instruction_index)
.peekable();

while let Some(ins) = iter.next() {
self.instruction_index += 1;

// skip until the first FROM
if preamble {
if let Instruction::From(_) = ins {
preamble = false;
} else {
continue;
}
}

instructions.push(ins);

// this stage ends before the next FROM
if let Some(Instruction::From(_)) = iter.peek() {
break;
}
}

if instructions.is_empty() {
None
} else {
let stage = Stage {
index: self.stage_index,
instructions
};

self.stage_index += 1;

Some(stage)
}
}
}

impl Dockerfile {
/// Parses a Dockerfile from a string.
pub fn parse(input: &str) -> Result<Dockerfile> {
Expand All @@ -274,13 +198,13 @@ impl Dockerfile {
Dockerfile::parse(&buf)
}

/// Creates an iterator over stages within this Dockerfile.
pub fn iter_stages(&self) -> StageIterator {
StageIterator {
dockerfile: &self,
stage_index: 0,
instruction_index: 0
}
/// Returns a `Stages`, which splits this Dockerfile into its build stages.
pub fn stages(&self) -> Stages {
Stages::new(self)
}

pub fn iter_stages(&self) -> std::vec::IntoIter<Stage<'_>> {
self.stages().into_iter()
}

/// Creates a `Splicer` for this Dockerfile.
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ mod util;
mod image;
mod instructions;
mod splicer;
mod stage;
mod dockerfile_parser;

pub use image::*;
pub use error::*;
pub use parser::*;
pub use instructions::*;
pub use splicer::*;
pub use stage::*;
pub use crate::dockerfile_parser::*;

#[cfg(test)] mod test_util;
12 changes: 11 additions & 1 deletion src/splicer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// (C) Copyright 2019-2020 Hewlett Packard Enterprise Development LP

use std::convert::TryInto;
use std::fmt;

use crate::parser::Pair;
use crate::dockerfile_parser::Dockerfile;
Expand All @@ -13,7 +14,7 @@ struct SpliceOffset {
}

/// A byte-index tuple representing a span of characters in a string
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(PartialEq, Eq, Clone)]
pub struct Span {
pub start: usize,
pub end: usize
Expand Down Expand Up @@ -85,6 +86,15 @@ impl From<(usize, usize)> for Span {
}
}

impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("")
.field(&self.start)
.field(&self.end)
.finish()
}
}

/// A utility to repeatedly replace spans of text within a larger document.
///
/// Each subsequent call to `Splicer::splice(...)` rewrites the `content` buffer
Expand Down
Loading

0 comments on commit ebc48f2

Please sign in to comment.