Skip to content

Commit f8ff3c3

Browse files
committed
Add default resolution info detection heuristics
1 parent 42c0eef commit f8ff3c3

File tree

10 files changed

+89
-22
lines changed

10 files changed

+89
-22
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,15 @@ Other options are resolving library descriptor from a local file or from remote
143143
```
144144
// Load library from file
145145
%use mylib@file[/home/user/lib.json]
146+
// Load library from file: kernel will guess it's a file actually
147+
%use @/home/user/libs/lib.json
148+
// Or use another approach: specify a directory and file name without
149+
// extension (it should be JSON in such case) before it
150+
%use lib@/home/user/libs
146151
// Load library descriptor from a remote URL
147152
%use herlib@url[https://site.com/lib.json]
153+
// If your URL responds with 200(OK), you may skip `url[]` part:
154+
%use @https://site.com/lib.json
148155
// You may omit library name for file and URL resolution:
149156
%use @file[lib.json]
150157
```

src/main/kotlin/org/jetbrains/kotlin/jupyter/libraries/LibraryFactory.kt

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import java.net.URL
77
import java.nio.file.Paths
88

99
class LibraryFactory(
10-
var defaultResolutionInfo: LibraryResolutionInfo,
10+
val resolutionInfoProvider: ResolutionInfoProvider,
1111
private val parsers: Map<String, LibraryResolutionInfoParser> = defaultParsers,
1212
) {
1313
fun parseReferenceWithArgs(str: String): Pair<LibraryReference, List<Variable>> {
@@ -25,16 +25,16 @@ class LibraryFactory(
2525

2626
private fun parseResolutionInfo(string: String): LibraryResolutionInfo {
2727
// In case of empty string after `@`: %use lib@
28-
if(string.isBlank()) return defaultResolutionInfo
28+
if(string.isBlank()) return resolutionInfoProvider.get()
2929

3030
val (type, vars) = parseCall(string, Brackets.SQUARE)
31-
val parser = parsers[type] ?: return LibraryResolutionInfo.getInfoByRef(type)
31+
val parser = parsers[type] ?: return resolutionInfoProvider.get(type)
3232
return parser.getInfo(vars)
3333
}
3434

3535
private fun parseReference(string: String): LibraryReference {
3636
val sepIndex = string.indexOf('@')
37-
if (sepIndex == -1) return LibraryReference(defaultResolutionInfo, string)
37+
if (sepIndex == -1) return LibraryReference(resolutionInfoProvider.get(), string)
3838

3939
val nameString = string.substring(0, sepIndex)
4040
val infoString = string.substring(sepIndex + 1)
@@ -43,8 +43,6 @@ class LibraryFactory(
4343
}
4444

4545
companion object {
46-
fun withDefaultDirectoryResolution(dir: File) = LibraryFactory(LibraryResolutionInfo.ByDir(dir))
47-
4846
private val defaultParsers = listOf(
4947
LibraryResolutionInfoParser.make("ref", listOf(Parameter.Required("ref"))) { args ->
5048
LibraryResolutionInfo.getInfoByRef(args["ref"] ?: error("Argument 'ref' should be specified"))
@@ -59,5 +57,9 @@ class LibraryFactory(
5957
LibraryResolutionInfo.ByURL(URL(args["url"] ?: error("Argument 'url' should be specified")))
6058
},
6159
).map { it.name to it }.toMap()
60+
61+
val EMPTY = LibraryFactory(EmptyResolutionInfoProvider)
62+
63+
fun withDefaultDirectoryResolution(dir: File) = LibraryFactory(StandardResolutionInfoProvider(LibraryResolutionInfo.ByDir(dir)))
6264
}
6365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.jetbrains.kotlin.jupyter.libraries
2+
3+
import org.jetbrains.kotlin.jupyter.GitHubApiPrefix
4+
import org.jetbrains.kotlin.jupyter.LibrariesDir
5+
import java.io.File
6+
import java.net.URL
7+
8+
interface ResolutionInfoProvider {
9+
var fallback: LibraryResolutionInfo
10+
11+
fun get(): LibraryResolutionInfo = fallback
12+
fun get(string: String): LibraryResolutionInfo
13+
}
14+
15+
object EmptyResolutionInfoProvider : ResolutionInfoProvider {
16+
private val fallbackInfo = LibraryResolutionInfo.ByNothing()
17+
18+
override var fallback: LibraryResolutionInfo
19+
get() = fallbackInfo
20+
set(_) {}
21+
22+
override fun get(string: String) = LibraryResolutionInfo.getInfoByRef(string)
23+
}
24+
25+
class StandardResolutionInfoProvider(override var fallback: LibraryResolutionInfo) : ResolutionInfoProvider {
26+
override fun get(string: String): LibraryResolutionInfo {
27+
return tryGetAsRef(string) ?: tryGetAsDir(string) ?: tryGetAsFile(string) ?: tryGetAsURL(string) ?: fallback
28+
}
29+
30+
private fun tryGetAsRef(ref: String): LibraryResolutionInfo? {
31+
val response = khttp.get("$GitHubApiPrefix/contents/$LibrariesDir?ref=$ref")
32+
return if (response.statusCode == 200) LibraryResolutionInfo.getInfoByRef(ref) else null
33+
}
34+
35+
private fun tryGetAsDir(dirName: String): LibraryResolutionInfo? {
36+
val file = File(dirName)
37+
return if (file.isDirectory) LibraryResolutionInfo.ByDir(file) else null
38+
}
39+
40+
private fun tryGetAsFile(fileName: String): LibraryResolutionInfo? {
41+
val file = File(fileName)
42+
return if (file.isFile) LibraryResolutionInfo.ByFile(file) else null
43+
}
44+
45+
private fun tryGetAsURL(url: String): LibraryResolutionInfo? {
46+
val response = khttp.get(url)
47+
return if (response.statusCode == 200) LibraryResolutionInfo.ByURL(URL(url)) else null
48+
}
49+
}

src/main/kotlin/org/jetbrains/kotlin/jupyter/libraries/util.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,21 @@ enum class DefaultInfoSwitch {
3232
GIT_REFERENCE, DIRECTORY
3333
}
3434

35-
class LibraryFactoryDefaultInfoSwitcher<T>(private val libraryFactory: LibraryFactory, initialSwitchVal: T, private val switcher: (T) -> LibraryResolutionInfo) {
35+
class LibraryFactoryDefaultInfoSwitcher<T>(private val infoProvider: ResolutionInfoProvider, initialSwitchVal: T, private val switcher: (T) -> LibraryResolutionInfo) {
3636
private val defaultInfoCache = hashMapOf<T, LibraryResolutionInfo>()
3737

3838
var switch: T = initialSwitchVal
3939
set(value) {
40-
libraryFactory.defaultResolutionInfo = defaultInfoCache.getOrPut(value) { switcher(value) }
40+
infoProvider.fallback = defaultInfoCache.getOrPut(value) { switcher(value) }
4141
field = value
4242
}
4343

4444
companion object {
45-
fun default(factory: LibraryFactory, defaultDir: File, defaultRef: String): LibraryFactoryDefaultInfoSwitcher<DefaultInfoSwitch> {
46-
val initialInfo = factory.defaultResolutionInfo
45+
fun default(provider: ResolutionInfoProvider, defaultDir: File, defaultRef: String): LibraryFactoryDefaultInfoSwitcher<DefaultInfoSwitch> {
46+
val initialInfo = provider.fallback
4747
val dirInfo = if (initialInfo is LibraryResolutionInfo.ByDir) initialInfo else LibraryResolutionInfo.ByDir(defaultDir)
4848
val refInfo = if (initialInfo is LibraryResolutionInfo.ByGitRef) initialInfo else LibraryResolutionInfo.getInfoByRef(defaultRef)
49-
return LibraryFactoryDefaultInfoSwitcher(factory, DefaultInfoSwitch.DIRECTORY) { switch ->
49+
return LibraryFactoryDefaultInfoSwitcher(provider, DefaultInfoSwitch.DIRECTORY) { switch ->
5050
when(switch) {
5151
DefaultInfoSwitch.DIRECTORY -> dirInfo
5252
DefaultInfoSwitch.GIT_REFERENCE -> refInfo

src/main/kotlin/org/jetbrains/kotlin/jupyter/magics.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ data class MagicProcessingResult(val code: String, val libraries: List<LibraryDe
3333

3434
class MagicsProcessor(val repl: ReplOptions, private val libraries: LibrariesProcessor) {
3535

36-
private val libraryResolutionInfoSwitcher = LibraryFactoryDefaultInfoSwitcher.default(libraries.libraryFactory, repl.librariesDir, repl.currentBranch)
36+
private val libraryResolutionInfoSwitcher = LibraryFactoryDefaultInfoSwitcher.default(libraries.libraryFactory.resolutionInfoProvider, repl.librariesDir, repl.currentBranch)
3737

3838
private fun updateOutputConfig(conf: OutputConfig, argv: List<String>): OutputConfig {
3939

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/kernelServerTestsBase.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.jetbrains.kotlin.jupyter.Message
88
import org.jetbrains.kotlin.jupyter.defaultRuntimeProperties
99
import org.jetbrains.kotlin.jupyter.iKotlinClass
1010
import org.jetbrains.kotlin.jupyter.kernelServer
11+
import org.jetbrains.kotlin.jupyter.libraries.EmptyResolutionInfoProvider
1112
import org.jetbrains.kotlin.jupyter.libraries.LibraryFactory
1213
import org.jetbrains.kotlin.jupyter.libraries.LibraryResolutionInfo
1314
import org.jetbrains.kotlin.jupyter.makeHeader
@@ -37,7 +38,7 @@ open class KernelServerTestsBase {
3738
scriptClasspath = classpath,
3839
resolverConfig = null,
3940
homeDir = File(""),
40-
libraryFactory = LibraryFactory(LibraryResolutionInfo.ByNothing())
41+
libraryFactory = LibraryFactory.EMPTY
4142
)
4243

4344
private val sessionId = UUID.randomUUID().toString()

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/parseMagicsTests.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.jetbrains.kotlin.jupyter.MagicsProcessor
88
import org.jetbrains.kotlin.jupyter.OutputConfig
99
import org.jetbrains.kotlin.jupyter.ReplOptions
1010
import org.jetbrains.kotlin.jupyter.defaultRuntimeProperties
11+
import org.jetbrains.kotlin.jupyter.libraries.EmptyResolutionInfoProvider
1112
import org.jetbrains.kotlin.jupyter.libraries.LibraryFactory
1213
import org.jetbrains.kotlin.jupyter.libraries.LibraryResolutionInfo
1314
import org.jetbrains.kotlin.jupyter.repl.SourceCodeImpl
@@ -19,7 +20,7 @@ import java.io.File
1920
import kotlin.test.assertTrue
2021

2122
class ParseArgumentsTests {
22-
private val libraryFactory = LibraryFactory(LibraryResolutionInfo.ByNothing())
23+
private val libraryFactory = LibraryFactory.EMPTY
2324

2425
@Test
2526
fun test1() {
@@ -101,7 +102,7 @@ class ParseMagicsTests {
101102
private val options = TestReplOptions()
102103

103104
private fun test(code: String, expectedProcessedCode: String, librariesChecker: (List<LibraryDefinition>) -> Unit = {}) {
104-
val libraryFactory = LibraryFactory(LibraryResolutionInfo.ByNothing())
105+
val libraryFactory = LibraryFactory.EMPTY
105106
val processor = MagicsProcessor(options, LibrariesProcessor(libraryFactory.testResolverConfig.libraries, defaultRuntimeProperties, libraryFactory))
106107
with(processor.processMagics(code, tryIgnoreErrors = true)) {
107108
assertEquals(expectedProcessedCode, this.code)

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/replTests.kt

+11-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ abstract class AbstractReplTest {
4343

4444
companion object {
4545
@JvmStatic
46-
protected val libraryFactory = LibraryFactory(LibraryResolutionInfo.ByNothing())
46+
protected val libraryFactory = LibraryFactory.EMPTY
4747

4848
@JvmStatic
4949
protected val homeDir = File("")
@@ -569,15 +569,16 @@ class ReplWithResolverTest : AbstractReplTest() {
569569
@Test
570570
fun testDefaultInfoSwitcher() {
571571
val repl = getReplWithStandardResolver()
572+
val infoProvider = repl.libraryFactory.resolutionInfoProvider
572573

573-
val initialDefaultResolutionInfo = repl.libraryFactory.defaultResolutionInfo
574+
val initialDefaultResolutionInfo = infoProvider.fallback
574575
assertTrue(initialDefaultResolutionInfo is LibraryResolutionInfo.ByDir)
575576

576577
repl.eval("%useLatestDescriptors")
577-
assertTrue(repl.libraryFactory.defaultResolutionInfo is LibraryResolutionInfo.ByGitRef)
578+
assertTrue(infoProvider.fallback is LibraryResolutionInfo.ByGitRef)
578579

579580
repl.eval("%useLatestDescriptors -off")
580-
assertTrue(repl.libraryFactory.defaultResolutionInfo === initialDefaultResolutionInfo)
581+
assertTrue(infoProvider.fallback === initialDefaultResolutionInfo)
581582
}
582583

583584
@Test
@@ -604,6 +605,12 @@ class ReplWithResolverTest : AbstractReplTest() {
604605
assertEquals(1, displays.count())
605606
assertNull(res3.resultValue)
606607
displays.clear()
608+
609+
val res4 = repl.eval("""
610+
%use @$libraryPath(name=z, value=44)
611+
z
612+
""".trimIndent())
613+
assertEquals(44, res4.resultValue)
607614
}
608615

609616
@Test

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/testUtil.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fun Collection<Pair<String, String>>.toLibraries(libraryFactory: LibraryFactory)
4646
}
4747

4848
fun LibraryFactory.getResolverFromNamesMap(map: Map<String, LibraryDescriptor>): LibraryResolver {
49-
return InMemoryLibraryResolver(null, map.mapKeys { entry -> LibraryReference(defaultResolutionInfo, entry.key) })
49+
return InMemoryLibraryResolver(null, map.mapKeys { entry -> LibraryReference(resolutionInfoProvider.get(), entry.key) })
5050
}
5151

5252
fun readLibraries(basePath: String? = null): Map<String, JsonObject> {

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/typeProviderTests.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import org.jetbrains.kotlin.jupyter.ReplCompilerException
77
import org.jetbrains.kotlin.jupyter.ReplForJupyterImpl
88
import org.jetbrains.kotlin.jupyter.ResolverConfig
99
import org.jetbrains.kotlin.jupyter.defaultRepositories
10+
import org.jetbrains.kotlin.jupyter.libraries.EmptyResolutionInfoProvider
1011
import org.jetbrains.kotlin.jupyter.libraries.LibraryFactory
11-
import org.jetbrains.kotlin.jupyter.libraries.LibraryResolutionInfo
1212
import org.jetbrains.kotlin.jupyter.libraries.parseLibraryDescriptors
1313
import org.junit.jupiter.api.Assertions.assertEquals
1414
import org.junit.jupiter.api.Test
@@ -30,7 +30,7 @@ class TypeProviderTests {
3030
""".trimIndent()
3131
val cp = classpath + File(TypeProviderReceiver::class.java.protectionDomain.codeSource.location.toURI().path)
3232
val libJsons = mapOf("mylib" to parser.parse(StringBuilder(descriptor)) as JsonObject)
33-
val libraryFactory = LibraryFactory(LibraryResolutionInfo.ByNothing())
33+
val libraryFactory = LibraryFactory.EMPTY
3434
val config = ResolverConfig(defaultRepositories, libraryFactory.getResolverFromNamesMap(parseLibraryDescriptors(libJsons)))
3535
val repl = ReplForJupyterImpl(libraryFactory, cp, null, config, scriptReceivers = listOf(TypeProviderReceiver()))
3636

0 commit comments

Comments
 (0)