1
+ // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ package cc.unitmesh.container.workspace
5
+
6
+ import com.intellij.docker.dockerFile.parser.psi.DockerFileAddOrCopyCommand
7
+ import com.intellij.docker.dockerFile.parser.psi.DockerFileCmdCommand
8
+ import com.intellij.docker.dockerFile.parser.psi.DockerFileExposeCommand
9
+ import com.intellij.docker.dockerFile.parser.psi.DockerFileFromCommand
10
+ import com.intellij.docker.dockerFile.parser.psi.DockerFileWorkdirCommand
11
+ import com.intellij.docker.dockerFile.parser.psi.DockerPsiCommand
12
+ import com.intellij.openapi.project.Project
13
+ import com.intellij.openapi.vfs.VirtualFile
14
+ import com.intellij.psi.PsiElement
15
+ import com.intellij.psi.PsiManager
16
+ import com.intellij.psi.impl.source.tree.LeafPsiElement
17
+ import java.io.File
18
+
19
+ class DockerfileParser (private val project : Project ) {
20
+ fun parse (virtualFile : VirtualFile ): DockerfileDetails ? {
21
+ val psiFile = PsiManager .getInstance(project).findFile(virtualFile)!!
22
+ val contextDirectory = virtualFile.parent.path
23
+
24
+ val lastFromCommand = psiFile.children.filterIsInstance<DockerFileFromCommand >().lastOrNull() ? : return null
25
+ val commandsAfterLastFrom = psiFile.children.dropWhile { it != lastFromCommand }
26
+ if (commandsAfterLastFrom.isEmpty()) {
27
+ return null
28
+ }
29
+
30
+ val command = commandsAfterLastFrom.filterIsInstance<DockerFileCmdCommand >().lastOrNull()?.text?.substringAfter(" CMD " )
31
+ val portMappings = commandsAfterLastFrom.filterIsInstance<DockerFileExposeCommand >().mapNotNull {
32
+ it.listChildren().find { child -> (child as ? LeafPsiElement )?.elementType?.toString() == " INTEGER_LITERAL" }?.text?.toIntOrNull()
33
+ }
34
+
35
+ val copyDirectives = groupByWorkDir(commandsAfterLastFrom).flatMap { (workDir, commands) ->
36
+ commands.filterIsInstance<DockerFileAddOrCopyCommand >()
37
+ .filter { it.copyKeyword != null }
38
+ .mapNotNull { cmd -> cmd.fileOrUrlList.takeIf { it.size == 2 }?.let { it.first().text to it.last().text } }
39
+ .map { (rawLocal, rawRemote) ->
40
+ val local = if (rawLocal.startsWith(" /" ) || rawLocal.startsWith(File .separatorChar)) {
41
+ rawLocal
42
+ } else {
43
+ " ${contextDirectory.normalizeDirectory(true )}$rawLocal "
44
+ }
45
+ val remote = if (rawRemote.startsWith(" /" ) || workDir == null ) {
46
+ rawRemote
47
+ } else {
48
+ " ${workDir.normalizeDirectory()}$rawRemote "
49
+ }
50
+ CopyDirective (local, remote)
51
+ }
52
+ }
53
+
54
+ return DockerfileDetails (command, portMappings, copyDirectives)
55
+ }
56
+
57
+ private fun String.normalizeDirectory (matchPlatform : Boolean = false): String {
58
+ val ch = if (matchPlatform) File .separatorChar else ' /'
59
+ return " ${trimEnd(ch)}$ch "
60
+ }
61
+
62
+ private fun groupByWorkDir (commands : List <PsiElement >): List <Pair <String ?, List <DockerPsiCommand >>> {
63
+ val list = mutableListOf<Pair <String ?, List <DockerPsiCommand >>>()
64
+ var workDir: String? = null
65
+ val elements = mutableListOf<DockerPsiCommand >()
66
+ commands.forEach {
67
+ when (it) {
68
+ is DockerFileWorkdirCommand -> {
69
+ if (elements.isNotEmpty()) {
70
+ list.add(workDir to elements.toList())
71
+ elements.clear()
72
+ }
73
+ workDir = it.fileOrUrlList.first().text
74
+ }
75
+ is DockerPsiCommand -> elements.add(it)
76
+ }
77
+ }
78
+ if (elements.isNotEmpty()) {
79
+ list.add(workDir to elements.toList())
80
+ }
81
+ return list
82
+ }
83
+
84
+ private fun PsiElement.listChildren (): List <PsiElement > {
85
+ var child: PsiElement ? = firstChild ? : return emptyList()
86
+ val children = mutableListOf<PsiElement >()
87
+ while (child != null ) {
88
+ children.add(child)
89
+ child = child.nextSibling
90
+ }
91
+ return children.toList()
92
+ }
93
+ }
94
+
95
+ data class DockerfileDetails (val command : String? , val exposePorts : List <Int >, val copyDirectives : List <CopyDirective >)
96
+ data class CopyDirective (val from : String , val to : String )
0 commit comments