Add options param to external dependencies resolver API
This commit is contained in:
committed by
Ilya Chernikov
parent
83087291df
commit
e45e491718
+2
@@ -55,6 +55,7 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
|
||||
|
||||
override suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
|
||||
@@ -76,6 +77,7 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
|
||||
|
||||
override fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<Boolean> {
|
||||
val url = repositoryCoordinates.toRepositoryUrlOrNull()
|
||||
|
||||
+4
-2
@@ -26,6 +26,7 @@ class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenc
|
||||
|
||||
override fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<Boolean> {
|
||||
var success = false
|
||||
@@ -34,7 +35,7 @@ class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenc
|
||||
|
||||
for (resolver in resolvers) {
|
||||
if (resolver.acceptsRepository(repositoryCoordinates)) {
|
||||
when (val resolveResult = resolver.addRepository(repositoryCoordinates, sourceCodeLocation)) {
|
||||
when (val resolveResult = resolver.addRepository(repositoryCoordinates, options, sourceCodeLocation)) {
|
||||
is ResultWithDiagnostics.Success -> {
|
||||
success = true
|
||||
repositoryAdded = repositoryAdded || resolveResult.value
|
||||
@@ -57,13 +58,14 @@ class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenc
|
||||
|
||||
override suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
val reports = mutableListOf<ScriptDiagnostic>()
|
||||
|
||||
for (resolver in resolvers) {
|
||||
if (resolver.acceptsArtifact(artifactCoordinates)) {
|
||||
when (val resolveResult = resolver.resolve(artifactCoordinates, sourceCodeLocation)) {
|
||||
when (val resolveResult = resolver.resolve(artifactCoordinates, options, sourceCodeLocation)) {
|
||||
is ResultWithDiagnostics.Failure -> reports.addAll(resolveResult.reports)
|
||||
else -> return resolveResult
|
||||
}
|
||||
|
||||
+16
-4
@@ -6,24 +6,34 @@
|
||||
package kotlin.script.experimental.dependencies
|
||||
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
import kotlin.script.experimental.api.valueOrNull
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver.Options
|
||||
|
||||
open class RepositoryCoordinates(val string: String)
|
||||
|
||||
interface ExternalDependenciesResolver {
|
||||
interface Options {
|
||||
object Empty : Options {
|
||||
override fun value(name: String): String? = null
|
||||
override fun flag(name: String): Boolean? = null
|
||||
}
|
||||
|
||||
fun value(name: String): String?
|
||||
fun flag(name: String): Boolean?
|
||||
}
|
||||
|
||||
fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean
|
||||
fun acceptsArtifact(artifactCoordinates: String): Boolean
|
||||
|
||||
suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: Options = Options.Empty,
|
||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||
): ResultWithDiagnostics<List<File>>
|
||||
|
||||
fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: Options = Options.Empty,
|
||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||
): ResultWithDiagnostics<Boolean>
|
||||
}
|
||||
@@ -33,5 +43,7 @@ fun ExternalDependenciesResolver.acceptsRepository(repositoryCoordinates: String
|
||||
|
||||
fun ExternalDependenciesResolver.addRepository(
|
||||
repositoryCoordinates: String,
|
||||
options: Options = Options.Empty,
|
||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||
) = addRepository(RepositoryCoordinates(repositoryCoordinates), sourceCodeLocation)
|
||||
) = addRepository(RepositoryCoordinates(repositoryCoordinates), options, sourceCodeLocation)
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ class FileSystemDependenciesResolver(vararg paths: File) : ExternalDependenciesR
|
||||
|
||||
override fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<Boolean> {
|
||||
if (!acceptsRepository(repositoryCoordinates)) return false.asSuccess()
|
||||
@@ -36,6 +37,7 @@ class FileSystemDependenciesResolver(vararg paths: File) : ExternalDependenciesR
|
||||
|
||||
override suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
if (!acceptsArtifact(artifactCoordinates)) throw IllegalArgumentException("Path is invalid")
|
||||
|
||||
+14
-5
@@ -8,6 +8,7 @@ package kotlin.script.experimental.dependencies
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.util.filterByAnnotationType
|
||||
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
|
||||
|
||||
/**
|
||||
* A common annotation that could be used in a script to denote a dependency
|
||||
@@ -17,7 +18,7 @@ import kotlin.script.experimental.util.filterByAnnotationType
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Repeatable
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class DependsOn(vararg val artifactsCoordinates: String)
|
||||
annotation class DependsOn(vararg val artifactsCoordinates: String, val options: Array<String> = [])
|
||||
|
||||
/**
|
||||
* A common annotation that could be used in a script to denote a repository for an ExternalDependenciesResolver
|
||||
@@ -27,7 +28,7 @@ annotation class DependsOn(vararg val artifactsCoordinates: String)
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Repeatable
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Repository(vararg val repositoriesCoordinates: String)
|
||||
annotation class Repository(vararg val repositoriesCoordinates: String, val options: Array<String> = [])
|
||||
|
||||
/**
|
||||
* An extension function that configures repositories and resolves artifacts denoted by the [Repository] and [DependsOn] annotations
|
||||
@@ -39,8 +40,11 @@ suspend fun ExternalDependenciesResolver.resolveFromScriptSourceAnnotations(
|
||||
annotations.forEach { (annotation, locationWithId) ->
|
||||
when (annotation) {
|
||||
is Repository -> {
|
||||
val options = SimpleExternalDependenciesResolverOptionsParser(*annotation.options, locationWithId = locationWithId)
|
||||
.valueOr { return it }
|
||||
|
||||
for (coordinates in annotation.repositoriesCoordinates) {
|
||||
val added = addRepository(coordinates, locationWithId)
|
||||
val added = addRepository(coordinates, options, locationWithId)
|
||||
.also { reports.addAll(it.reports) }
|
||||
.valueOr { return it }
|
||||
|
||||
@@ -58,8 +62,13 @@ suspend fun ExternalDependenciesResolver.resolveFromScriptSourceAnnotations(
|
||||
|
||||
return reports + annotations.filterByAnnotationType<DependsOn>()
|
||||
.flatMapSuccess { (annotation, locationWithId) ->
|
||||
annotation.artifactsCoordinates.asIterable().flatMapSuccess { artifactCoordinates ->
|
||||
resolve(artifactCoordinates, locationWithId)
|
||||
SimpleExternalDependenciesResolverOptionsParser(
|
||||
*annotation.options,
|
||||
locationWithId = locationWithId
|
||||
).onSuccess { options ->
|
||||
annotation.artifactsCoordinates.asIterable().flatMapSuccess { artifactCoordinates ->
|
||||
resolve(artifactCoordinates, options, locationWithId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.dependencies.impl
|
||||
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
|
||||
|
||||
private val nameRegex = Regex("^[^\\S\\r\\n]*([a-zA-Z][a-zA-Z0-9-_]*)\\b")
|
||||
private val valueRegex = Regex("^[^\\S\\r\\n]*([a-zA-Z0-9-_]+)\\b")
|
||||
private val equalsRegex = Regex("^[^\\S\\r\\n]*=")
|
||||
|
||||
/**
|
||||
* Simple lightweight options parser for external dependency resolvers.
|
||||
*
|
||||
* This parser expects the input to be a series of equality statements:
|
||||
* `foo=Foo bar=Bar`
|
||||
*
|
||||
* And additionally supports flags without any equality statement:
|
||||
* `foo bar`
|
||||
*/
|
||||
internal object SimpleExternalDependenciesResolverOptionsParser {
|
||||
private sealed class Token {
|
||||
data class Name(val name: String) : Token()
|
||||
data class Value(val value: String) : Token()
|
||||
object Equals
|
||||
}
|
||||
|
||||
private class Scanner(text: String) {
|
||||
private var consumed = ""
|
||||
|
||||
var remaining = text
|
||||
private set
|
||||
|
||||
private fun take(regex: Regex) = regex
|
||||
.find(remaining)
|
||||
?.also { match ->
|
||||
consumed += match.value
|
||||
remaining = remaining.removePrefix(match.value)
|
||||
}
|
||||
|
||||
fun takeName(): Token.Name? = take(nameRegex)?.let { Token.Name(it.groups[1]!!.value) }
|
||||
fun takeValue(): Token.Value? = take(valueRegex)?.let { Token.Value(it.groups[1]!!.value) }
|
||||
fun takeEquals(): Token.Equals? = take(equalsRegex)?.let { Token.Equals }
|
||||
|
||||
fun hasFinished(): Boolean = remaining.isBlank()
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
vararg options: String,
|
||||
locationWithId: SourceCode.LocationWithId? = null
|
||||
): ResultWithDiagnostics<ExternalDependenciesResolver.Options> {
|
||||
|
||||
val map = mutableMapOf<String, String>()
|
||||
|
||||
for (option in options) {
|
||||
val scanner = Scanner(option)
|
||||
|
||||
while (!scanner.hasFinished()) {
|
||||
val name = scanner.takeName()?.name ?: return makeFailureResult(
|
||||
"Failed to parse options from annotation. Expected a valid option name but received:\n${scanner.remaining}",
|
||||
locationWithId
|
||||
)
|
||||
|
||||
if (scanner.takeEquals() != null) {
|
||||
// TODO: Consider supporting string literals
|
||||
val value = scanner.takeValue()?.value ?: return makeFailureResult(
|
||||
"Failed to parse options from annotation. Expected a valid option value but received:\n${scanner.remaining}",
|
||||
locationWithId
|
||||
)
|
||||
|
||||
map.tryToAddOption(name, value)?.let { return it }
|
||||
} else {
|
||||
map.tryToAddOption(name, "true")?.let { return it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return makeExternalDependenciesResolverOptions(map).asSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <K, V> MutableMap<K, V>.tryToAddOption(
|
||||
key: K,
|
||||
value: V,
|
||||
locationWithId: SourceCode.LocationWithId? = null
|
||||
): ResultWithDiagnostics.Failure? = when (val previousValue = this[key]) {
|
||||
null, value -> {
|
||||
this[key] = value
|
||||
null
|
||||
}
|
||||
else -> makeFailureResult("Conflicting values for option $key: $previousValue and $value", locationWithId)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.dependencies.impl
|
||||
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
|
||||
|
||||
fun makeExternalDependenciesResolverOptions(map: Map<String, String>): ExternalDependenciesResolver.Options =
|
||||
MapExternalDependenciesResolverOptions(map)
|
||||
|
||||
suspend fun ExternalDependenciesResolver.resolve(
|
||||
artifactCoordinates: String,
|
||||
options: Map<String, String>,
|
||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||
): ResultWithDiagnostics<List<File>> = resolve(artifactCoordinates, makeExternalDependenciesResolverOptions(options), sourceCodeLocation)
|
||||
|
||||
private class MapExternalDependenciesResolverOptions(
|
||||
private val map: Map<String, String>
|
||||
) : ExternalDependenciesResolver.Options {
|
||||
|
||||
override fun value(name: String): String? = map[name]
|
||||
|
||||
override fun flag(name: String): Boolean? = map[name]?.let { value ->
|
||||
when (value.toLowerCase()) {
|
||||
"1", "true", "t", "yes", "y" -> true
|
||||
"0", "false", "f", "no", "n" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.test
|
||||
|
||||
import junit.framework.TestCase
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.valueOrThrow
|
||||
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
|
||||
import kotlin.script.experimental.dependencies.impl.makeExternalDependenciesResolverOptions
|
||||
|
||||
class ResolverOptionsTest : TestCase() {
|
||||
fun testValueInMapAppearsIfPresent() {
|
||||
val map = mapOf("option" to "value")
|
||||
val options = makeExternalDependenciesResolverOptions(map)
|
||||
|
||||
assertEquals(options.value("option"), "value")
|
||||
}
|
||||
|
||||
fun testFlagInMapAppearsIfPresent() {
|
||||
val map = mapOf("option" to "true")
|
||||
val options = makeExternalDependenciesResolverOptions(map)
|
||||
|
||||
assertEquals(options.value("option"), "true")
|
||||
assertEquals(options.flag("option"), true)
|
||||
}
|
||||
|
||||
fun testValueInMapDoesNotAppearsIfPresent() {
|
||||
val options = makeExternalDependenciesResolverOptions(emptyMap())
|
||||
assertNull(options.value("option"))
|
||||
}
|
||||
|
||||
fun testFlagInMapDoesNotAppearsIfPresent() {
|
||||
val options = makeExternalDependenciesResolverOptions(emptyMap())
|
||||
assertNull(options.flag("option"))
|
||||
}
|
||||
|
||||
fun testParserReturnsSingleValue() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 = hello").valueOrThrow()
|
||||
assertEquals(options.value("option1"), "hello")
|
||||
}
|
||||
|
||||
fun testParserReturnsMultipleValue() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 = hello option2 = 42").valueOrThrow()
|
||||
assertEquals(options.value("option1"), "hello")
|
||||
assertEquals(options.value("option2"), "42")
|
||||
}
|
||||
|
||||
|
||||
fun testParserReturnsSingleFlag() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 = hello").valueOrThrow()
|
||||
assertEquals(options.value("option1"), "hello")
|
||||
}
|
||||
|
||||
fun testParserReturnsMultipleFlags() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 option2=false option3").valueOrThrow()
|
||||
assertEquals(options.flag("option1"), true)
|
||||
assertEquals(options.flag("option2"), false)
|
||||
assertEquals(options.flag("option3"), true)
|
||||
}
|
||||
|
||||
fun testParserReturnsMixOfValuesAndFlags() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 = hello option2 option3=world option4 option5 = false").valueOrThrow()
|
||||
assertEquals(options.value("option1"), "hello")
|
||||
assertEquals(options.flag("option2"), true)
|
||||
assertEquals(options.value("option3"), "world")
|
||||
assertEquals(options.flag("option4"), true)
|
||||
assertEquals(options.flag("option5"), false)
|
||||
}
|
||||
|
||||
fun testParserReportsClashWithConflictingOptions() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
when (val result = parser("option1 = hello option1 = world")) {
|
||||
is ResultWithDiagnostics.Success -> fail("Managed to parse options despite conflicting options: ${result.value}")
|
||||
is ResultWithDiagnostics.Failure -> {
|
||||
assertEquals(result.reports.count(), 1)
|
||||
assertEquals(result.reports.first().message, "Conflicting values for option option1: hello and world")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testParserDoesNotClashWithTheSameOptionTwice() {
|
||||
val parser = SimpleExternalDependenciesResolverOptionsParser
|
||||
val options = parser("option1 = hello option1 = hello").valueOrThrow()
|
||||
assertEquals(options.value("option1"), "hello")
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@ class ResolversTest : ResolversTestBase() {
|
||||
|
||||
override suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
if (!acceptsArtifact(artifactCoordinates)) throw Exception("Path is invalid")
|
||||
@@ -97,6 +98,7 @@ class ResolversTest : ResolversTestBase() {
|
||||
|
||||
override fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<Boolean> {
|
||||
if (!acceptsRepository(repositoryCoordinates)) return false.asSuccess()
|
||||
|
||||
@@ -38,6 +38,7 @@ class IvyResolver : ExternalDependenciesResolver {
|
||||
|
||||
override suspend fun resolve(
|
||||
artifactCoordinates: String,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
|
||||
@@ -135,6 +136,7 @@ class IvyResolver : ExternalDependenciesResolver {
|
||||
|
||||
override fun addRepository(
|
||||
repositoryCoordinates: RepositoryCoordinates,
|
||||
options: ExternalDependenciesResolver.Options,
|
||||
sourceCodeLocation: SourceCode.LocationWithId?
|
||||
): ResultWithDiagnostics<Boolean> {
|
||||
val url = repositoryCoordinates.toRepositoryUrlOrNull()
|
||||
|
||||
Reference in New Issue
Block a user