use std::fmt::{self, Display};
use instruction::Instruction;
pub mod instruction;
pub mod instruction_builder;
#[derive(Debug, Default, Clone)]
pub struct Dockerfile {
instructions: Vec<Instruction>,
}
impl Dockerfile {
pub fn new() -> Self {
Self::default()
}
pub fn push<T: Into<Instruction>>(&mut self, instruction: T) -> &mut Self {
self.instructions.push(instruction.into());
self
}
pub fn push_any<T: Into<String>>(&mut self, instruction: T) -> &mut Self {
self.instructions.push(Instruction::ANY(instruction.into()));
self
}
pub fn append<T: Into<Instruction>>(&mut self, instructions: Vec<T>) -> &mut Self {
for i in instructions {
self.instructions.push(i.into());
}
self
}
pub fn append_any<T: Into<String>>(&mut self, instructions: Vec<T>) -> &mut Self {
for i in instructions {
self.instructions.push(Instruction::ANY(i.into()));
}
self
}
pub fn syntax<T: Into<String>>(&mut self, syntax: T) -> &mut Self {
self.push_any(format!("# syntax={}", syntax.into()));
self
}
pub fn escape<T: Into<String>>(&mut self, escape: T) -> &mut Self {
self.push_any(format!("# escape={}", escape.into()));
self
}
pub fn comment<T: Into<String>>(&mut self, comment: T) -> &mut Self {
self.push_any(format!("# {}", comment.into()));
self
}
pub fn into_inner(self) -> Vec<Instruction> {
self.instructions
}
}
impl Display for Dockerfile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let instructions = self
.instructions
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>();
write!(f, "{}", instructions.join("\n"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
instruction::{EXPOSE, FROM, RUN},
instruction_builder::{ExposeBuilder, PortProtocol},
};
use expect_test::expect;
#[test]
fn quick_start() {
let mut dockerfile = Dockerfile::new();
dockerfile
.push(RUN::from("echo $HOME"))
.push(EXPOSE::from("80/tcp"))
.push_any("# Just adding a comment");
let expected = expect![[r#"
RUN echo $HOME
EXPOSE 80/tcp
# Just adding a comment"#]];
expected.assert_eq(&dockerfile.to_string());
}
#[test]
fn build_dockerfile() {
let expose = EXPOSE::from("80/tcp");
let expose_from_builder = ExposeBuilder::builder()
.port(80)
.protocol(PortProtocol::Tcp)
.build()
.unwrap();
assert_eq!(expose, expose_from_builder);
let mut dockerfile = Dockerfile::new();
dockerfile.push(expose_from_builder);
let expected = expect!["EXPOSE 80/tcp"];
expected.assert_eq(&dockerfile.to_string());
}
#[test]
fn append_instructions() {
let comments = vec!["# syntax=docker/dockerfile:1", "# escape=`"];
let instruction_vec = vec![
Instruction::FROM(FROM::from("cargo-chef AS chef")),
Instruction::RUN(RUN::from("cargo run")),
];
let mut dockerfile = Dockerfile::new();
dockerfile.append_any(comments).append(instruction_vec);
let expected = expect![[r#"
# syntax=docker/dockerfile:1
# escape=`
FROM cargo-chef AS chef
RUN cargo run"#]];
expected.assert_eq(&dockerfile.to_string());
}
}