diff --git a/src/main/kotlin/kscript/app/ResolveIncludes.kt b/src/main/kotlin/kscript/app/ResolveIncludes.kt index a66da801..b9dff195 100644 --- a/src/main/kotlin/kscript/app/ResolveIncludes.kt +++ b/src/main/kotlin/kscript/app/ResolveIncludes.kt @@ -11,7 +11,7 @@ import java.net.URL */ const val PACKAGE_STATEMENT_PREFIX = "package " -const val IMPORT_STATMENT_PREFIX = "import " // todo make more solid by using operator including regex +const val IMPORT_STATEMENT_PREFIX = "import " // todo make more solid by using operator including regex data class IncludeResult(val scriptFile: File, val includes: List = emptyList()) @@ -25,30 +25,35 @@ fun resolveIncludes(template: File, includeContext: URI = template.parentFile.to } val includes = emptyList().toMutableList() + val includeLines = emptySet().toMutableSet() // resolve as long as it takes. YAGNI but we do because we can! while (script.any { isIncludeDirective(it) }) { - script = script.flatMap { - if (isIncludeDirective(it)) { - val include = extractIncludeTarget(it) + script = script.flatMap { line -> + if (isIncludeDirective(line)) { + val include = extractIncludeTarget(line) val includeURL = when { isUrl(include) -> URL(include) include.startsWith("/") -> File(include).toURI().toURL() else -> includeContext.resolve(URI(include.removePrefix("./"))).toURL() } + if (includeLines.contains(includeURL.path)) { + emptyList() + } else { + includes.add(includeURL) + includeLines.add(includeURL.path) - includes.add(includeURL) - - try { + try { includeURL.readText().lines() - } catch (e: FileNotFoundException) { + } catch (e: FileNotFoundException) { errorMsg("Failed to resolve //INCLUDE '${include}'") System.err.println(e.message?.lines()!!.map { it.prependIndent("[kscript] [ERROR] ") }) quit(1) + } } } else { - listOf(it) + listOf(line) } }.let { script.copy(it) } } diff --git a/src/main/kotlin/kscript/app/Script.kt b/src/main/kotlin/kscript/app/Script.kt index 2c72febd..51e6f0a9 100644 --- a/src/main/kotlin/kscript/app/Script.kt +++ b/src/main/kotlin/kscript/app/Script.kt @@ -34,7 +34,7 @@ data class Script(val lines: List, val extension: String = "kts") : Iter val annotations = emptySet().toMutableSet() stripShebang().forEach { - if (it.startsWith(IMPORT_STATMENT_PREFIX)) { + if (it.startsWith(IMPORT_STATEMENT_PREFIX)) { imports.add(it) } else if (isKscriptAnnotation(it)) { annotations.add(it) diff --git a/src/test/kotlin/Tests.kt b/src/test/kotlin/Tests.kt index 4402476f..2bb5bdf6 100644 --- a/src/test/kotlin/Tests.kt +++ b/src/test/kotlin/Tests.kt @@ -168,4 +168,14 @@ class Tests { result.includes.filter { it.protocol == "file" }.map { File(it.toURI()).name } shouldBe List(4) { "include_${it + 1}.kt" } result.includes.filter { it.protocol != "file" }.size shouldBe 1 } -} \ No newline at end of file + + @Test + fun `test include detection - should not include dependency twice`() { + val result = resolveIncludes(File("test/resources/includes/dup_include/dup_include.kts")) + + result.includes.map { File(it.toURI()).name } shouldBe listOf( + "dup_include_1.kt", + "dup_include_2.kt" + ) + } +} diff --git a/test/resources/includes/dup_include/dup_include.kts b/test/resources/includes/dup_include/dup_include.kts new file mode 100644 index 00000000..ecfec8b2 --- /dev/null +++ b/test/resources/includes/dup_include/dup_include.kts @@ -0,0 +1,7 @@ +#!/usr/bin/env kscript + + +@file:Include("dup_include_1.kt") +@file:Include("dup_include_2.kt") + +dup_include_2() diff --git a/test/resources/includes/dup_include/dup_include_1.kt b/test/resources/includes/dup_include/dup_include_1.kt new file mode 100644 index 00000000..f29669a5 --- /dev/null +++ b/test/resources/includes/dup_include/dup_include_1.kt @@ -0,0 +1,3 @@ + +@file:Include("dup_include_2.kt") + diff --git a/test/resources/includes/dup_include/dup_include_2.kt b/test/resources/includes/dup_include/dup_include_2.kt new file mode 100644 index 00000000..9b7cabc3 --- /dev/null +++ b/test/resources/includes/dup_include/dup_include_2.kt @@ -0,0 +1 @@ +fun dup_include_2() = println("dup_include")