Skip to content

Commit 0c845dd

Browse files
authored
Extract DockerFileParser to its own file (#2707)
1 parent 3027822 commit 0c845dd

File tree

3 files changed

+102
-96
lines changed

3 files changed

+102
-96
lines changed

jetbrains-core/src/software/aws/toolkits/jetbrains/services/ecs/execution/ImportFromDockerfile.kt

Lines changed: 3 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,34 @@
44
package software.aws.toolkits.jetbrains.services.ecs.execution
55

66
import com.intellij.docker.dockerFile.DockerFileType
7-
import com.intellij.docker.dockerFile.parser.psi.DockerPsiCommand
87
import com.intellij.openapi.fileChooser.FileChooser
98
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
109
import com.intellij.openapi.project.Project
1110
import com.intellij.openapi.project.guessProjectDir
1211
import com.intellij.openapi.ui.Messages
13-
import com.intellij.openapi.vfs.VirtualFile
14-
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileAddOrCopyCommand
15-
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileCmdCommand
16-
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileExposeCommand
17-
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileFromCommand
18-
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileWorkdirCommand
19-
import com.intellij.psi.PsiElement
20-
import com.intellij.psi.PsiManager
21-
import com.intellij.psi.impl.source.tree.LeafPsiElement
2212
import software.aws.toolkits.core.utils.tryOrNull
2313
import software.aws.toolkits.jetbrains.core.plugins.pluginIsInstalledAndEnabled
14+
import software.aws.toolkits.jetbrains.services.ecs.execution.docker.DockerfileParser
2415
import software.aws.toolkits.resources.message
2516
import java.awt.event.ActionEvent
2617
import java.awt.event.ActionListener
27-
import java.io.File
2818

2919
object DockerUtil {
3020
@JvmStatic
3121
fun dockerPluginAvailable() = pluginIsInstalledAndEnabled("Docker")
3222
}
3323

34-
class ImportFromDockerfile @JvmOverloads constructor(
24+
class ImportFromDockerfile(
3525
private val project: Project,
3626
private val view: PerContainerSettings,
37-
private val dockerfileParser: DockerfileParser = DockerfileParser(project)
3827
) : ActionListener {
3928
override fun actionPerformed(e: ActionEvent?) {
4029
val file = FileChooser.chooseFile(
4130
FileChooserDescriptorFactory.createSingleFileDescriptor(DockerFileType.DOCKER_FILE_TYPE),
4231
project,
4332
project.guessProjectDir()
4433
) ?: return
45-
val details = tryOrNull { dockerfileParser.parse(file) }
34+
val details = tryOrNull { DockerfileParser(project).parse(file) }
4635
if (details == null) {
4736
Messages.showWarningDialog(
4837
view.component,
@@ -57,83 +46,3 @@ class ImportFromDockerfile @JvmOverloads constructor(
5746
details.copyDirectives.map { ArtifactMapping(it.from, it.to) }.takeIf { it.isNotEmpty() }?.let { view.artifactMappingsTable.setValues(it) }
5847
}
5948
}
60-
61-
class DockerfileParser(private val project: Project) {
62-
fun parse(virtualFile: VirtualFile): DockerfileDetails? {
63-
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)!!
64-
val contextDirectory = virtualFile.parent.path
65-
66-
val lastFromCommand = psiFile.children.filterIsInstance<DockerFileFromCommand>().lastOrNull() ?: return null
67-
val commandsAfterLastFrom = psiFile.children.dropWhile { it != lastFromCommand }
68-
if (commandsAfterLastFrom.isEmpty()) {
69-
return null
70-
}
71-
72-
val command = commandsAfterLastFrom.filterIsInstance<DockerFileCmdCommand>().lastOrNull()?.text?.substringAfter("CMD ")
73-
val portMappings = commandsAfterLastFrom.filterIsInstance<DockerFileExposeCommand>().mapNotNull {
74-
it.listChildren().find { child -> (child as? LeafPsiElement)?.elementType?.toString() == "INTEGER_LITERAL" }?.text?.toIntOrNull()
75-
}
76-
77-
val copyDirectives = groupByWorkDir(commandsAfterLastFrom).flatMap { (workDir, commands) ->
78-
commands.filterIsInstance<DockerFileAddOrCopyCommand>()
79-
.filter { it.copyKeyword != null }
80-
.mapNotNull { cmd -> cmd.fileOrUrlList.takeIf { it.size == 2 }?.let { it.first().text to it.last().text } }
81-
.map { (rawLocal, rawRemote) ->
82-
val local = if (rawLocal.startsWith("/") || rawLocal.startsWith(File.separatorChar)) {
83-
rawLocal
84-
} else {
85-
"${contextDirectory.normalizeDirectory(true)}$rawLocal"
86-
}
87-
val remote = if (rawRemote.startsWith("/") || workDir == null) {
88-
rawRemote
89-
} else {
90-
"${workDir.normalizeDirectory()}$rawRemote"
91-
}
92-
CopyDirective(local, remote)
93-
}
94-
}
95-
96-
return DockerfileDetails(command, portMappings, copyDirectives)
97-
}
98-
99-
private fun groupByWorkDir(commands: List<PsiElement>): List<Pair<String?, List<DockerPsiCommand>>> {
100-
val list = mutableListOf<Pair<String?, List<DockerPsiCommand>>>()
101-
var workDir: String? = null
102-
val elements = mutableListOf<DockerPsiCommand>()
103-
commands.forEach {
104-
when (it) {
105-
is DockerFileWorkdirCommand -> {
106-
if (elements.isNotEmpty()) {
107-
list.add(workDir to elements.toList())
108-
elements.clear()
109-
}
110-
workDir = it.fileOrUrlList.first().text
111-
}
112-
is DockerPsiCommand -> elements.add(it)
113-
}
114-
}
115-
if (elements.isNotEmpty()) {
116-
list.add(workDir to elements.toList())
117-
}
118-
return list
119-
}
120-
121-
private fun PsiElement.listChildren(): List<PsiElement> {
122-
var child: PsiElement? = firstChild ?: return emptyList()
123-
val children = mutableListOf<PsiElement>()
124-
while (child != null) {
125-
children.add(child)
126-
child = child.nextSibling
127-
}
128-
return children.toList()
129-
}
130-
}
131-
132-
data class DockerfileDetails(val command: String?, val exposePorts: List<Int>, val copyDirectives: List<CopyDirective>)
133-
134-
data class CopyDirective(val from: String, val to: String)
135-
136-
fun String.normalizeDirectory(matchPlatform: Boolean = false): String {
137-
val ch = if (matchPlatform) File.separatorChar else '/'
138-
return "${trimEnd(ch)}$ch"
139-
}
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 software.aws.toolkits.jetbrains.services.ecs.execution.docker
5+
6+
import com.intellij.docker.dockerFile.parser.psi.DockerPsiCommand
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.vfs.VirtualFile
9+
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileAddOrCopyCommand
10+
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileCmdCommand
11+
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileExposeCommand
12+
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileFromCommand
13+
import com.intellij.plugins.docker.dockerFile.parser.psi.DockerFileWorkdirCommand
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)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package software.aws.toolkits.jetbrains.services.ecs.execution
4+
package software.aws.toolkits.jetbrains.services.ecs.execution.docker
55

66
import com.intellij.docker.dockerFile.DockerFileType
77
import com.intellij.docker.dockerFile.DockerLanguage
@@ -16,6 +16,7 @@ import org.junit.Before
1616
import org.junit.Rule
1717
import org.junit.Test
1818
import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule
19+
import java.io.File
1920

2021
class DockerfileParserTest {
2122

@@ -158,7 +159,7 @@ class DockerfileParserTest {
158159
COPY . .
159160
""".trimIndent()
160161
)
161-
val directory = file.parent.path.normalizeDirectory(matchPlatform = true)
162+
val directory = file.parent.path + File.separator
162163
runInEdtAndWait {
163164
assertThat(sut.parse(file)).isEqualTo(
164165
DockerfileDetails(

0 commit comments

Comments
 (0)