1use crate::{DockerfileParser, ast::*, language::DockerfileLanguage, lexer::token_type::DockerfileTokenType, parser::element_type::DockerfileElementType};
2use oak_core::{Builder, BuilderCache, GreenNode, OakDiagnostics, OakError, Parser, RedNode, RedTree, SourceText, TextEdit, builder::BuildOutput, source::Source};
3
4#[derive(Clone, Copy)]
6pub struct DockerfileBuilder<'config> {
7 config: &'config DockerfileLanguage,
9}
10
11impl<'config> DockerfileBuilder<'config> {
12 pub fn new(config: &'config DockerfileLanguage) -> Self {
14 Self { config }
15 }
16}
17
18impl<'config> Builder<DockerfileLanguage> for DockerfileBuilder<'config> {
19 fn build<'a, S: Source + ?Sized>(&self, source: &S, edits: &[TextEdit], cache: &'a mut impl BuilderCache<DockerfileLanguage>) -> BuildOutput<DockerfileLanguage> {
21 let parser = DockerfileParser::new(self.config);
22
23 let parse_result = parser.parse(source, edits, cache);
24
25 match parse_result.result {
26 Ok(green_tree) => {
27 let source_text = SourceText::new(source.get_text_in((0..source.length()).into()).into_owned());
28 match self.build_root(green_tree, &source_text) {
29 Ok(ast_root) => OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
30 Err(build_error) => {
31 let mut diagnostics = parse_result.diagnostics;
32 diagnostics.push(build_error.clone());
33 OakDiagnostics { result: Err(build_error), diagnostics }
34 }
35 }
36 }
37 Err(parse_error) => OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
38 }
39 }
40}
41
42impl<'config> DockerfileBuilder<'config> {
43 pub(crate) fn build_root<'a>(&self, green_tree: &'a GreenNode<'a, DockerfileLanguage>, source: &SourceText) -> Result<DockerfileRoot, OakError> {
45 let root_node = RedNode::new(green_tree, 0);
46 let mut instructions = Vec::new();
47
48 for child in root_node.children() {
49 if let RedTree::Node(n) = child {
50 match n.green.kind {
51 DockerfileElementType::From => instructions.push(self.build_from(n, source)?),
52 DockerfileElementType::Run => instructions.push(self.build_run(n, source)?),
53 DockerfileElementType::Copy => instructions.push(self.build_copy(n, source)?),
54 DockerfileElementType::Add => instructions.push(self.build_add(n, source)?),
55 DockerfileElementType::Workdir => instructions.push(self.build_workdir(n, source)?),
56 DockerfileElementType::Expose => instructions.push(self.build_expose(n, source)?),
57 DockerfileElementType::Env => instructions.push(self.build_env(n, source)?),
58 DockerfileElementType::Cmd => instructions.push(self.build_cmd(n, source)?),
59 DockerfileElementType::Entrypoint => instructions.push(self.build_entrypoint(n, source)?),
60 DockerfileElementType::Volume => instructions.push(self.build_volume(n, source)?),
61 DockerfileElementType::User => instructions.push(self.build_user(n, source)?),
62 DockerfileElementType::Label => instructions.push(self.build_label(n, source)?),
63 DockerfileElementType::Arg => instructions.push(self.build_arg(n, source)?),
64 DockerfileElementType::Stopsignal => instructions.push(self.build_stopsignal(n, source)?),
65 DockerfileElementType::Healthcheck => instructions.push(self.build_healthcheck(n, source)?),
66 DockerfileElementType::Shell => instructions.push(self.build_shell(n, source)?),
67 DockerfileElementType::Onbuild => instructions.push(self.build_onbuild(n, source)?),
68 _ => {}
69 }
70 }
71 }
72
73 Ok(DockerfileRoot { instructions })
74 }
75
76 fn build_from(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
77 let mut image = String::new();
78 let mut children = node.children();
79
80 children.next();
82
83 for child in children {
84 if let RedTree::Leaf(t) = child {
85 if t.kind != DockerfileTokenType::Whitespace {
86 image.push_str(source.get_text_in(t.span()).as_ref());
87 }
88 }
89 }
90
91 Ok(Instruction::From { image: image.trim().to_string(), tag: None, span: node.span() })
92 }
93
94 fn build_run(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
95 let mut command = String::new();
96 let mut children = node.children();
97
98 children.next();
100
101 for child in children {
102 if let RedTree::Leaf(t) = child {
103 if t.kind != DockerfileTokenType::Whitespace {
104 command.push_str(source.get_text_in(t.span()).as_ref());
105 }
106 }
107 }
108
109 Ok(Instruction::Run { command: command.trim().to_string(), span: node.span() })
110 }
111
112 fn build_copy(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
113 let mut args = Vec::new();
114 let mut children = node.children();
115
116 children.next();
118
119 for child in children {
120 if let RedTree::Leaf(t) = child {
121 if t.kind != DockerfileTokenType::Whitespace {
122 args.push(source.get_text_in(t.span()).to_string());
123 }
124 }
125 }
126
127 Ok(Instruction::Copy { src: args.get(0).cloned().unwrap_or_default(), dest: args.get(1).cloned().unwrap_or_default(), span: node.span() })
128 }
129
130 fn build_add(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
131 let mut args = Vec::new();
132 let mut children = node.children();
133
134 children.next();
136
137 for child in children {
138 if let RedTree::Leaf(t) = child {
139 if t.kind != DockerfileTokenType::Whitespace {
140 args.push(source.get_text_in(t.span()).to_string());
141 }
142 }
143 }
144
145 Ok(Instruction::Add { src: args.get(0).cloned().unwrap_or_default(), dest: args.get(1).cloned().unwrap_or_default(), span: node.span() })
146 }
147
148 fn build_workdir(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
149 let mut path = String::new();
150 let mut children = node.children();
151 children.next(); for child in children {
153 if let RedTree::Leaf(t) = child {
154 if t.kind != DockerfileTokenType::Whitespace {
155 path.push_str(source.get_text_in(t.span()).as_ref());
156 }
157 }
158 }
159 Ok(Instruction::Workdir { path: path.trim().to_string(), span: node.span() })
160 }
161
162 fn build_expose(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
163 let mut port = String::new();
164 let mut children = node.children();
165 children.next(); for child in children {
167 if let RedTree::Leaf(t) = child {
168 if t.kind != DockerfileTokenType::Whitespace {
169 port.push_str(source.get_text_in(t.span()).as_ref());
170 }
171 }
172 }
173 Ok(Instruction::Expose { port: port.trim().to_string(), span: node.span() })
174 }
175
176 fn build_env(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
177 let mut key = String::new();
178 let mut value = String::new();
179 let mut children = node.children();
180 children.next(); let mut parts = Vec::new();
183 for child in children {
184 if let RedTree::Leaf(t) = child {
185 if t.kind != DockerfileTokenType::Whitespace {
186 parts.push(source.get_text_in(t.span()).to_string());
187 }
188 }
189 }
190
191 if parts.len() >= 2 {
192 key = parts[0].clone();
193 value = parts[1..].join(" ");
194 }
195 else if !parts.is_empty() {
196 key = parts[0].clone();
197 }
198
199 Ok(Instruction::Env { key, value, span: node.span() })
200 }
201
202 fn build_cmd(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
203 let mut command = String::new();
204 let mut children = node.children();
205 children.next(); for child in children {
207 if let RedTree::Leaf(t) = child {
208 if t.kind != DockerfileTokenType::Whitespace {
209 command.push_str(source.get_text_in(t.span()).as_ref());
210 }
211 }
212 }
213 Ok(Instruction::Cmd { command: command.trim().to_string(), span: node.span() })
214 }
215
216 fn build_entrypoint(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
217 let mut command = String::new();
218 let mut children = node.children();
219 children.next(); for child in children {
221 if let RedTree::Leaf(t) = child {
222 if t.kind != DockerfileTokenType::Whitespace {
223 command.push_str(source.get_text_in(t.span()).as_ref());
224 }
225 }
226 }
227 Ok(Instruction::Entrypoint { command: command.trim().to_string(), span: node.span() })
228 }
229
230 fn build_volume(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
231 let mut path = String::new();
232 let mut children = node.children();
233 children.next(); for child in children {
235 if let RedTree::Leaf(t) = child {
236 if t.kind != DockerfileTokenType::Whitespace {
237 path.push_str(source.get_text_in(t.span()).as_ref());
238 }
239 }
240 }
241 Ok(Instruction::Volume { path: path.trim().to_string(), span: node.span() })
242 }
243
244 fn build_user(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
245 let mut user = String::new();
246 let mut children = node.children();
247 children.next(); for child in children {
249 if let RedTree::Leaf(t) = child {
250 if t.kind != DockerfileTokenType::Whitespace {
251 user.push_str(source.get_text_in(t.span()).as_ref());
252 }
253 }
254 }
255 Ok(Instruction::User { user: user.trim().to_string(), span: node.span() })
256 }
257
258 fn build_label(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
259 let mut key = String::new();
260 let mut value = String::new();
261 let mut children = node.children();
262 children.next(); let mut parts = Vec::new();
265 for child in children {
266 if let RedTree::Leaf(t) = child {
267 if t.kind != DockerfileTokenType::Whitespace {
268 parts.push(source.get_text_in(t.span()).to_string());
269 }
270 }
271 }
272
273 if parts.len() >= 2 {
274 key = parts[0].clone();
275 value = parts[1..].join(" ");
276 }
277 else if !parts.is_empty() {
278 key = parts[0].clone();
279 }
280
281 Ok(Instruction::Label { key, value, span: node.span() })
282 }
283
284 fn build_arg(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
285 let mut name = String::new();
286 let mut default = None;
287 let mut children = node.children();
288 children.next(); let mut parts = Vec::new();
291 for child in children {
292 if let RedTree::Leaf(t) = child {
293 if t.kind != DockerfileTokenType::Whitespace {
294 parts.push(source.get_text_in(t.span()).to_string());
295 }
296 }
297 }
298
299 if !parts.is_empty() {
300 name = parts[0].clone();
301 if parts.len() > 1 {
302 default = Some(parts[1..].join(" "));
303 }
304 }
305
306 Ok(Instruction::Arg { name, default, span: node.span() })
307 }
308
309 fn build_stopsignal(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
310 let mut signal = String::new();
311 let mut children = node.children();
312 children.next(); for child in children {
314 if let RedTree::Leaf(t) = child {
315 if t.kind != DockerfileTokenType::Whitespace {
316 signal.push_str(source.get_text_in(t.span()).as_ref());
317 }
318 }
319 }
320 Ok(Instruction::Stopsignal { signal: signal.trim().to_string(), span: node.span() })
321 }
322
323 fn build_healthcheck(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
324 let mut command = String::new();
325 let mut children = node.children();
326 children.next(); for child in children {
328 if let RedTree::Leaf(t) = child {
329 if t.kind != DockerfileTokenType::Whitespace {
330 command.push_str(source.get_text_in(t.span()).as_ref());
331 }
332 }
333 }
334 Ok(Instruction::Healthcheck { command: command.trim().to_string(), span: node.span() })
335 }
336
337 fn build_shell(&self, node: RedNode<DockerfileLanguage>, source: &SourceText) -> Result<Instruction, OakError> {
338 let mut shell = String::new();
339 let mut children = node.children();
340 children.next(); for child in children {
342 if let RedTree::Leaf(t) = child {
343 if t.kind != DockerfileTokenType::Whitespace {
344 shell.push_str(source.get_text_in(t.span()).as_ref());
345 }
346 }
347 }
348 Ok(Instruction::Shell { shell: shell.trim().to_string(), span: node.span() })
349 }
350
351 fn build_onbuild(&self, node: RedNode<DockerfileLanguage>, _source: &SourceText) -> Result<Instruction, OakError> {
352 Ok(Instruction::Onbuild { instruction: Box::new(Instruction::Run { command: "ONBUILD placeholder".to_string(), span: node.span() }), span: node.span() })
354 }
355}
356
357fn text(source: &SourceText, span: core::range::Range<usize>) -> String {
358 source.get_text_in(span).to_string()
359}