dockerfile_parser_rs/
file.rs

1use std::fs::File;
2use std::io::Write;
3use std::path::PathBuf;
4
5use crate::ParseResult;
6use crate::ast::Instruction;
7use crate::error::ParseError;
8use crate::parser::instructions::add;
9use crate::parser::instructions::arg;
10use crate::parser::instructions::cmd;
11use crate::parser::instructions::copy;
12use crate::parser::instructions::entrypoint;
13use crate::parser::instructions::env;
14use crate::parser::instructions::expose;
15use crate::parser::instructions::from;
16use crate::parser::instructions::label;
17use crate::parser::instructions::run;
18use crate::parser::instructions::shell;
19use crate::parser::instructions::user;
20use crate::parser::instructions::volume;
21use crate::parser::instructions::workdir;
22use crate::symbols::chars::HASHTAG;
23use crate::utils::read_lines;
24use crate::utils::split_instruction_and_arguments;
25
26/// This struct represents a Dockerfile instance.
27#[derive(Debug)]
28pub struct Dockerfile {
29    pub path: PathBuf,
30    pub instructions: Vec<Instruction>,
31}
32
33impl Dockerfile {
34    /// Creates a new `Dockerfile` instance for the given path and instructions.
35    ///
36    /// The actual file does not need to exist at this point.
37    ///
38    pub fn new(path: PathBuf, instructions: Vec<Instruction>) -> Self {
39        Dockerfile { path, instructions }
40    }
41
42    /// Creates an empty `Dockerfile` instance for the given path.
43    ///
44    /// The actual file does not need to exist at this point.
45    ///
46    pub fn empty(path: PathBuf) -> Self {
47        Dockerfile::new(path, Vec::new())
48    }
49
50    /// Parses the content of the Dockerfile and returns a populated `Dockerfile` instance.
51    ///
52    /// The file is read line by line, preserving empty lines and comments.
53    ///
54    /// # Example
55    ///
56    /// ```
57    /// use std::path::PathBuf;
58    ///
59    /// use dockerfile_parser_rs::Dockerfile;
60    /// use dockerfile_parser_rs::ParseResult;
61    ///
62    /// fn main() -> ParseResult<()> {
63    ///     let dockerfile = Dockerfile::from(PathBuf::from("./Dockerfile"))?;
64    ///     println!("{:#?}", dockerfile.instructions);
65    ///     Ok(())
66    /// }
67    /// ```
68    ///
69    pub fn from(path: PathBuf) -> ParseResult<Self> {
70        let mut dockerfile = Dockerfile::empty(path);
71        dockerfile.instructions = dockerfile.parse()?;
72        Ok(dockerfile)
73    }
74
75    /// Parses the content of the Dockerfile and returns a vector of `Instruction` items.
76    ///
77    /// The file is read line by line, preserving empty lines and comments.
78    ///
79    /// **The attributes of the `Dockerfile` instance are not modified by this method.**
80    ///
81    /// # Example
82    ///
83    /// ```
84    /// use std::path::PathBuf;
85    ///
86    /// use dockerfile_parser_rs::Dockerfile;
87    /// use dockerfile_parser_rs::ParseResult;
88    ///
89    /// fn main() -> ParseResult<()> {
90    ///     let dockerfile = Dockerfile::empty(PathBuf::from("./Dockerfile"));
91    ///     let instructions = dockerfile.parse()?;
92    ///     println!("{:#?}", instructions);
93    ///     Ok(())
94    /// }
95    /// ```
96    ///
97    pub fn parse(&self) -> ParseResult<Vec<Instruction>> {
98        let file = File::open(&self.path).map_err(|e| ParseError::FileError(e.to_string()))?;
99        let lines = read_lines(&file);
100
101        let mut instructions = Vec::new();
102
103        for line in lines {
104            // preserve empty lines
105            if line.is_empty() {
106                instructions.push(Instruction::Empty);
107            // preserve comments
108            } else if line.starts_with(HASHTAG) {
109                instructions.push(Instruction::Comment(line.to_owned()));
110            } else {
111                let (instruction, arguments) = split_instruction_and_arguments(&line)?;
112                let instruction = match instruction.as_str() {
113                    "ADD" => add::parse(arguments),
114                    "ARG" => arg::parse(arguments),
115                    "CMD" => cmd::parse(arguments),
116                    "COPY" => copy::parse(arguments),
117                    "ENTRYPOINT" => entrypoint::parse(arguments),
118                    "ENV" => env::parse(arguments),
119                    "EXPOSE" => expose::parse(arguments),
120                    "LABEL" => label::parse(arguments),
121                    "FROM" => from::parse(arguments),
122                    "RUN" => run::parse(arguments),
123                    "SHELL" => shell::parse(arguments),
124                    "USER" => user::parse(arguments),
125                    "VOLUME" => volume::parse(arguments),
126                    "WORKDIR" => workdir::parse(arguments),
127                    _ => return Err(ParseError::UnknownInstruction(instruction)),
128                };
129                match instruction {
130                    Ok(instruction) => instructions.push(instruction),
131                    Err(e) => {
132                        return Err(ParseError::SyntaxError(format!("{}: {}", line, e)));
133                    }
134                }
135            }
136        }
137        Ok(instructions)
138    }
139
140    /// Dumps the current instructions into the Dockerfile.
141    ///
142    /// If the file does not exist, it will be created.
143    /// If the file exists, it will be overwritten.
144    ///
145    pub fn dump(&self) -> std::io::Result<()> {
146        let mut file = File::create(&self.path)?;
147        for instruction in &self.instructions {
148            writeln!(file, "{}", instruction)?;
149        }
150        Ok(())
151    }
152}