dockerfile_parser_rs/
lib.rs1mod ast;
2mod error;
3mod parser;
4mod symbols;
5mod utils;
6
7use std::fs::File;
8use std::io::Write;
9use std::path::PathBuf;
10
11pub use crate::ast::Instruction;
13pub use crate::error::ParseError;
14
15use crate::parser::instructions::add;
16use crate::parser::instructions::arg;
17use crate::parser::instructions::cmd;
18use crate::parser::instructions::copy;
19use crate::parser::instructions::entrypoint;
20use crate::parser::instructions::env;
21use crate::parser::instructions::expose;
22use crate::parser::instructions::from;
23use crate::parser::instructions::label;
24use crate::parser::instructions::run;
25use crate::parser::instructions::user;
26use crate::parser::instructions::volume;
27use crate::parser::instructions::workdir;
28use crate::symbols::chars::HASHTAG;
29use crate::utils::read_lines;
30
31fn tokenize_line(line: &str) -> anyhow::Result<(String, Vec<String>)> {
32 let regex = regex::Regex::new(r"^(?P<instruction>[A-Z]+)\s*(?P<arguments>.*)").unwrap();
34
35 let captures = regex
36 .captures(line)
37 .ok_or_else(|| ParseError::SyntaxError(line.to_string()))?;
38
39 let instruction = captures
40 .name("instruction")
41 .ok_or_else(|| ParseError::SyntaxError(line.to_string()))?
42 .as_str();
43
44 let arguments = captures
45 .name("arguments")
46 .ok_or_else(|| ParseError::SyntaxError(line.to_string()))?
47 .as_str();
48
49 Ok((
50 instruction.to_string(),
51 arguments
52 .split_whitespace()
53 .map(|s| s.to_string())
54 .collect(),
55 ))
56}
57
58#[derive(Debug)]
59pub struct Dockerfile {
60 pub path: PathBuf,
61 pub instructions: Vec<Instruction>,
62}
63
64impl Dockerfile {
65 pub fn new(path: PathBuf) -> Self {
66 Dockerfile {
67 path,
68 instructions: Vec::new(),
69 }
70 }
71
72 pub fn from(path: PathBuf) -> anyhow::Result<Self> {
73 let mut dockerfile = Dockerfile::new(path);
74 dockerfile.instructions = dockerfile.parse()?;
75 Ok(dockerfile)
76 }
77
78 pub fn parse(&self) -> anyhow::Result<Vec<Instruction>> {
79 let lines = read_lines(&self.path);
80 let mut instructions = Vec::new();
81
82 for line in lines {
83 if line.is_empty() {
85 instructions.push(Instruction::Empty);
86 } else if line.starts_with(HASHTAG) {
88 instructions.push(Instruction::Comment(line.to_string()));
89 } else {
90 let (instruction, arguments) = tokenize_line(&line)?;
91 let instruction = match instruction.as_str() {
92 "ADD" => add::parse(arguments),
93 "ARG" => arg::parse(arguments),
94 "CMD" => cmd::parse(arguments),
95 "COPY" => copy::parse(arguments),
96 "ENTRYPOINT" => entrypoint::parse(arguments),
97 "ENV" => env::parse(arguments),
98 "EXPOSE" => expose::parse(arguments),
99 "LABEL" => label::parse(arguments),
100 "FROM" => from::parse(arguments),
101 "RUN" => run::parse(arguments),
102 "USER" => user::parse(arguments),
103 "VOLUME" => volume::parse(arguments),
104 "WORKDIR" => workdir::parse(arguments),
105 _ => return Err(ParseError::UnknownInstruction(instruction))?,
106 }?;
107 instructions.push(instruction);
108 }
109 }
110 Ok(instructions)
111 }
112
113 pub fn dump(&self) -> anyhow::Result<()> {
114 let mut file = File::create(&self.path)?;
115 for instruction in &self.instructions {
116 writeln!(file, "{}", instruction)?;
117 }
118 Ok(())
119 }
120}