Skip to content

Commit 12f801c

Browse files
committed
feat(docker): add Dockerfile parser and enhance context provider #306
- Introduced `DockerfileParser` class to parse and extract details from Dockerfiles. - Enhanced `DockerContextProvider` with additional imports and functionality for Docker-related operations.
1 parent 7819b5d commit 12f801c

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

exts/ext-container/src/233/main/kotlin/cc/unitmesh/container/provider/DockerContextProvider.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ import cc.unitmesh.devti.provider.context.ChatContextProvider
55
import cc.unitmesh.devti.provider.context.ChatCreationContext
66
import cc.unitmesh.devti.sketch.ui.patch.readText
77
import com.intellij.docker.DockerFileSearch
8+
import com.intellij.docker.dockerFile.DockerPsiFile
9+
import com.intellij.docker.dockerFile.parser.psi.DockerFileCmdCommand
10+
import com.intellij.docker.dockerFile.parser.psi.DockerFileExposeCommand
811
import com.intellij.docker.dockerFile.parser.psi.DockerFileFromCommand
912
import com.intellij.openapi.application.runReadAction
1013
import com.intellij.openapi.diagnostic.logger
1114
import com.intellij.openapi.project.Project
15+
import com.intellij.psi.PsiElement
1216
import com.intellij.psi.PsiManager
17+
import com.intellij.psi.impl.source.tree.LeafPsiElement
1318
import com.intellij.psi.util.PsiTreeUtil
1419

1520
class DockerContextProvider : ChatContextProvider {
@@ -59,4 +64,4 @@ class DockerContextProvider : ChatContextProvider {
5964
val text = "This project use Docker to run in server. Here is related info:\n$additionalCtx"
6065
return listOf(ChatContextItem(DockerContextProvider::class, text))
6166
}
62-
}
67+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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

Comments
 (0)