From 8112ac353128e68e366b040bb2ffce3ea06be9fc Mon Sep 17 00:00:00 2001 From: Ilya Muradyan Date: Thu, 6 May 2021 13:03:47 +0300 Subject: [PATCH] Add code preprocessors as a part of library API Fixes #223 --- gradle.properties | 2 +- .../kotlinx/jupyter/api/CodePreprocessor.kt | 20 +++++++ .../api/libraries/JupyterIntegration.kt | 49 +++++++++++----- .../api/libraries/LibraryDefinition.kt | 7 +++ .../api/libraries/LibraryDefinitionImpl.kt | 53 +++++++++++------- .../jupyter/libraries/LibraryDescriptor.kt | 26 ++++----- .../magics/CompoundCodePreprocessor.kt | 31 ++++++++++ .../kotlinx/jupyter/magics/MagicsProcessor.kt | 14 +++-- .../org/jetbrains/kotlinx/jupyter/repl.kt | 6 +- .../jupyter/repl/impl/CellExecutorImpl.kt | 3 +- .../jupyter/repl/impl/SharedReplContext.kt | 4 +- .../kotlinx/jupyter/test/embeddingTest.kt | 14 ++--- .../jupyter/test/repl/IntegrationApiTests.kt | 17 +++++- .../kotlin-jupyter-api-test-0.0.16.jar | Bin 0 -> 8241 bytes .../testData/notebook-api-test-0.0.15.jar | Bin 7092 -> 0 bytes 15 files changed, 181 insertions(+), 65 deletions(-) create mode 100644 jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/CodePreprocessor.kt create mode 100644 jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/CompoundCodePreprocessor.kt create mode 100644 src/test/testData/kotlin-jupyter-api-test-0.0.16.jar delete mode 100644 src/test/testData/notebook-api-test-0.0.15.jar diff --git a/gradle.properties b/gradle.properties index ab4e418e2..45211062e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ logbackVersion=1.2.3 http4kVersion=4.4.0.1 artifactsPath=build/artifacts -baseVersion=0.9.1 +baseVersion=0.10.0 projectRepoUrl=https://github.com/Kotlin/kotlin-jupyter docsRepo=git@github.com:ileasile/kotlin-jupyter-docs.git diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/CodePreprocessor.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/CodePreprocessor.kt new file mode 100644 index 000000000..0034de2e4 --- /dev/null +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/CodePreprocessor.kt @@ -0,0 +1,20 @@ +package org.jetbrains.kotlinx.jupyter.api + +import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionProducer + +/** + * Preprocesses the cell code before its execution + */ +interface CodePreprocessor { + /** + * Returns `true` if this preprocessor accepts the given [code] + */ + fun accepts(code: String): Boolean = true + + /** + * Performs code preprocessing + */ + fun process(code: String, host: KotlinKernelHost): Result + + data class Result(val code: Code, val libraries: List = emptyList()) +} diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt index f8f886394..adee7bbde 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt @@ -3,7 +3,9 @@ package org.jetbrains.kotlinx.jupyter.api.libraries import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler import org.jetbrains.kotlinx.jupyter.api.ClassDeclarationsCallback +import org.jetbrains.kotlinx.jupyter.api.Code import org.jetbrains.kotlinx.jupyter.api.CodeCell +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback import org.jetbrains.kotlinx.jupyter.api.FieldHandler import org.jetbrains.kotlinx.jupyter.api.FieldHandlerByClass @@ -54,6 +56,8 @@ abstract class JupyterIntegration : LibraryDefinitionProducer { private val repositories = mutableListOf() + private val codePreprocessors = mutableListOf() + fun addRenderer(handler: RendererTypeHandler) { renderers.add(handler) } @@ -70,6 +74,10 @@ abstract class JupyterIntegration : LibraryDefinitionProducer { fileAnnotations.add(handler) } + fun addCodePreprocessor(preprocessor: CodePreprocessor) { + codePreprocessors.add(preprocessor) + } + inline fun render(noinline renderer: CodeCell.(T) -> Any) { return renderWithHost { _, value: T -> renderer(this, value) } } @@ -152,21 +160,34 @@ abstract class JupyterIntegration : LibraryDefinitionProducer { addFileAnnotationHanlder(FileAnnotationHandler(T::class, callback)) } + fun preprocessCodeWithLibraries(callback: KotlinKernelHost.(Code) -> CodePreprocessor.Result) { + addCodePreprocessor(object : CodePreprocessor { + override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result { + return host.callback(code) + } + }) + } + + fun preprocessCode(callback: KotlinKernelHost.(Code) -> Code) { + preprocessCodeWithLibraries { CodePreprocessor.Result(this.callback(it)) } + } + internal fun getDefinition() = - LibraryDefinitionImpl( - init = init, - renderers = renderers, - converters = converters, - imports = imports, - dependencies = dependencies, - repositories = repositories, - initCell = beforeCellExecution, - afterCellExecution = afterCellExecution, - shutdown = shutdownCallbacks, - classAnnotations = classAnnotations, - fileAnnotations = fileAnnotations, - resources = resources, - ) + libraryDefinition { + it.init = init + it.renderers = renderers + it.converters = converters + it.imports = imports + it.dependencies = dependencies + it.repositories = repositories + it.initCell = beforeCellExecution + it.afterCellExecution = afterCellExecution + it.shutdown = shutdownCallbacks + it.classAnnotations = classAnnotations + it.fileAnnotations = fileAnnotations + it.resources = resources + it.codePreprocessors = codePreprocessors + } } override fun getDefinitions(notebook: Notebook): List { diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinition.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinition.kt index 6bccb6ff6..11e47f468 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinition.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinition.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.jupyter.api.libraries import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback import org.jetbrains.kotlinx.jupyter.api.FieldHandler import org.jetbrains.kotlinx.jupyter.api.FileAnnotationHandler @@ -85,6 +86,12 @@ interface LibraryDefinition { val resources: List get() = emptyList() + /** + * List of code preprocessors + */ + val codePreprocessors: List + get() = emptyList() + /** * Minimal kernel version that is supported by this library */ diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinitionImpl.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinitionImpl.kt index 97b08b887..5ab847ce4 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinitionImpl.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinitionImpl.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.jupyter.api.libraries import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback import org.jetbrains.kotlinx.jupyter.api.FieldHandler import org.jetbrains.kotlinx.jupyter.api.FileAnnotationHandler @@ -9,23 +10,37 @@ import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion import org.jetbrains.kotlinx.jupyter.api.RendererTypeHandler /** - * Trivial implementation of [LibraryDefinition]. - * You may use it in simple cases instead of overriding [LibraryDefinition] - * to avoid additional anonymous classes creation + * Trivial implementation of [LibraryDefinition] - simple container. */ -class LibraryDefinitionImpl( - override val dependencies: List = emptyList(), - override val repositories: List = emptyList(), - override val imports: List = emptyList(), - override val init: List> = emptyList(), - override val initCell: List> = emptyList(), - override val afterCellExecution: List = emptyList(), - override val shutdown: List> = emptyList(), - override val renderers: List = emptyList(), - override val converters: List = emptyList(), - override val classAnnotations: List = emptyList(), - override val fileAnnotations: List = emptyList(), - override val resources: List = emptyList(), - override val minKernelVersion: KotlinKernelVersion? = null, - override val originalDescriptorText: String? = null, -) : LibraryDefinition +class LibraryDefinitionImpl private constructor() : LibraryDefinition { + override var dependencies: List = emptyList() + override var repositories: List = emptyList() + override var imports: List = emptyList() + override var init: List> = emptyList() + override var initCell: List> = emptyList() + override var afterCellExecution: List = emptyList() + override var shutdown: List> = emptyList() + override var renderers: List = emptyList() + override var converters: List = emptyList() + override var classAnnotations: List = emptyList() + override var fileAnnotations: List = emptyList() + override var resources: List = emptyList() + override var codePreprocessors: List = emptyList() + override var minKernelVersion: KotlinKernelVersion? = null + override var originalDescriptorText: String? = null + + companion object { + internal fun build(buildAction: (LibraryDefinitionImpl) -> Unit): LibraryDefinition { + return LibraryDefinitionImpl().also(buildAction) + } + } +} + +/** + * Builds an instance of [LibraryDefinition]. + * Build action receives [LibraryDefinitionImpl] as an explicit argument + * because of problems with names clashing that may arise. + */ +fun libraryDefinition(buildAction: (LibraryDefinitionImpl) -> Unit): LibraryDefinition { + return LibraryDefinitionImpl.build(buildAction) +} diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibraryDescriptor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibraryDescriptor.kt index 5a7ea018e..d04ab3950 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibraryDescriptor.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibraryDescriptor.kt @@ -8,8 +8,8 @@ import org.jetbrains.kotlinx.jupyter.api.ExactRendererTypeHandler import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion import org.jetbrains.kotlinx.jupyter.api.libraries.CodeExecution import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition -import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionImpl import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryResource +import org.jetbrains.kotlinx.jupyter.api.libraries.libraryDefinition import org.jetbrains.kotlinx.jupyter.config.currentKernelVersion import org.jetbrains.kotlinx.jupyter.exceptions.ReplPreprocessingException import org.jetbrains.kotlinx.jupyter.util.KotlinKernelVersionSerializer @@ -78,18 +78,18 @@ class LibraryDescriptor( } private fun processDescriptor(mapping: Map): LibraryDefinition { - return LibraryDefinitionImpl( - dependencies = dependencies.replaceVariables(mapping), - repositories = repositories.replaceVariables(mapping), - imports = imports.replaceVariables(mapping), - init = init.replaceVariables(mapping), - shutdown = shutdown.replaceVariables(mapping), - initCell = initCell.replaceVariables(mapping), - renderers = renderers.replaceVariables(mapping), - resources = resources.replaceVariables(mapping), - minKernelVersion = minKernelVersion, - originalDescriptorText = Json.encodeToString(this), - ) + return libraryDefinition { + it.dependencies = dependencies.replaceVariables(mapping) + it.repositories = repositories.replaceVariables(mapping) + it.imports = imports.replaceVariables(mapping) + it.init = init.replaceVariables(mapping) + it.shutdown = shutdown.replaceVariables(mapping) + it.initCell = initCell.replaceVariables(mapping) + it.renderers = renderers.replaceVariables(mapping) + it.resources = resources.replaceVariables(mapping) + it.minKernelVersion = minKernelVersion + it.originalDescriptorText = Json.encodeToString(this) + } } companion object { diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/CompoundCodePreprocessor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/CompoundCodePreprocessor.kt new file mode 100644 index 000000000..15dc6351c --- /dev/null +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/CompoundCodePreprocessor.kt @@ -0,0 +1,31 @@ +package org.jetbrains.kotlinx.jupyter.magics + +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor +import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost + +/** + * Containing [preprocessors]' [process] are run in reversed order: last added processors + * are run first + */ +class CompoundCodePreprocessor( + private val preprocessors: MutableList +) : CodePreprocessor { + constructor(vararg preprocessors: CodePreprocessor) : this(preprocessors.toMutableList()) + + override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result { + return preprocessors.foldRight(CodePreprocessor.Result(code, emptyList())) { preprocessor, result -> + if (preprocessor.accepts(result.code)) { + val newResult = preprocessor.process(result.code, host) + CodePreprocessor.Result(newResult.code, result.libraries + newResult.libraries) + } else result + } + } + + fun add(preprocessor: CodePreprocessor) { + preprocessors.add(preprocessor) + } + + fun addAll(preprocessors: Iterable) { + this.preprocessors.addAll(preprocessors) + } +} diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/MagicsProcessor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/MagicsProcessor.kt index 847ef5d1c..b98ca4502 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/MagicsProcessor.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/MagicsProcessor.kt @@ -1,7 +1,7 @@ package org.jetbrains.kotlinx.jupyter.magics -import org.jetbrains.kotlinx.jupyter.api.Code -import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionProducer +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor +import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost import org.jetbrains.kotlinx.jupyter.common.ReplLineMagic import org.jetbrains.kotlinx.jupyter.compiler.util.CodeInterval import org.jetbrains.kotlinx.jupyter.exceptions.ReplPreprocessingException @@ -10,8 +10,8 @@ import kotlin.script.experimental.jvm.util.determineSep class MagicsProcessor( private val handler: MagicsHandler, private val parseOutCellMarker: Boolean = false, -) { - fun processMagics(code: String, parseOnly: Boolean = false, tryIgnoreErrors: Boolean = false): MagicProcessingResult { +) : CodePreprocessor { + fun processMagics(code: String, parseOnly: Boolean = false, tryIgnoreErrors: Boolean = false): CodePreprocessor.Result { val magics = magicsIntervals(code) for (magicRange in magics) { @@ -38,7 +38,7 @@ class MagicsProcessor( val codes = codeIntervals(code, magics, true) val preprocessedCode = codes.joinToString("") { code.substring(it.from, it.to) } - return MagicProcessingResult(preprocessedCode, handler.getLibraries()) + return CodePreprocessor.Result(preprocessedCode, handler.getLibraries()) } fun codeIntervals( @@ -79,7 +79,9 @@ class MagicsProcessor( } } - data class MagicProcessingResult(val code: Code, val libraries: List) + override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result { + return processMagics(code) + } companion object { private const val MAGICS_SIGN = '%' diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt index 055cf0703..91849ea11 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt @@ -7,6 +7,7 @@ import jupyter.kotlin.KotlinKernelHostProvider import jupyter.kotlin.Repository import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlinx.jupyter.api.Code +import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion @@ -40,6 +41,7 @@ import org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner import org.jetbrains.kotlinx.jupyter.libraries.LibraryResourcesProcessorImpl import org.jetbrains.kotlinx.jupyter.libraries.ResolutionInfoProvider import org.jetbrains.kotlinx.jupyter.libraries.ResolutionInfoSwitcher +import org.jetbrains.kotlinx.jupyter.magics.CompoundCodePreprocessor import org.jetbrains.kotlinx.jupyter.magics.FullMagicsHandler import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor import org.jetbrains.kotlinx.jupyter.repl.CellExecutor @@ -231,6 +233,8 @@ class ReplForJupyterImpl( ) ) + private val codePreprocessor = CompoundCodePreprocessor(magics) + private val importsCollector: ScriptImportsCollector = ScriptImportsCollectorImpl() // Used for various purposes, i.e. completion and listing errors @@ -328,7 +332,7 @@ class ReplForJupyterImpl( fileAnnotationsProcessor, fieldsProcessor, typeRenderersProcessor, - magics, + codePreprocessor, resourcesProcessor, librariesScanner, notebook, diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/CellExecutorImpl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/CellExecutorImpl.kt index 170f2228f..e84a0b309 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/CellExecutorImpl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/CellExecutorImpl.kt @@ -45,7 +45,7 @@ internal class CellExecutorImpl(private val replContext: SharedReplContext) : Ce log.debug("Executing code:\n$code") val preprocessedCode = if (processMagics) { - val processedMagics = magicsProcessor.processMagics(code) + val processedMagics = codePreprocessor.process(code, context) log.debug("Adding ${processedMagics.libraries.size} libraries") processedMagics.libraries.getDefinitions(notebook).forEach { @@ -128,6 +128,7 @@ internal class CellExecutorImpl(private val replContext: SharedReplContext) : Ce library.classAnnotations.forEach(sharedContext.classAnnotationsProcessor::register) library.fileAnnotations.forEach(sharedContext.fileAnnotationsProcessor::register) sharedContext.afterCellExecution.addAll(library.afterCellExecution) + sharedContext.codePreprocessor.addAll(library.codePreprocessors) val classLoader = sharedContext.evaluator.lastClassLoader rethrowAsLibraryException(LibraryProblemPart.RESOURCES) { diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt index e82d8f18f..ad02a21bc 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt @@ -9,7 +9,7 @@ import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor import org.jetbrains.kotlinx.jupyter.codegen.ResultsTypeRenderersProcessor import org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner import org.jetbrains.kotlinx.jupyter.libraries.LibraryResourcesProcessor -import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor +import org.jetbrains.kotlinx.jupyter.magics.CompoundCodePreprocessor import org.jetbrains.kotlinx.jupyter.repl.InternalEvaluator internal data class SharedReplContext( @@ -17,7 +17,7 @@ internal data class SharedReplContext( val fileAnnotationsProcessor: FileAnnotationsProcessor, val fieldsProcessor: FieldsProcessor, val typeRenderersProcessor: ResultsTypeRenderersProcessor, - val magicsProcessor: MagicsProcessor, + val codePreprocessor: CompoundCodePreprocessor, val resourcesProcessor: LibraryResourcesProcessor, val librariesScanner: LibrariesScanner, val notebook: Notebook, diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/embeddingTest.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/embeddingTest.kt index 2420b928d..9cae348f2 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/embeddingTest.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/embeddingTest.kt @@ -3,12 +3,12 @@ package org.jetbrains.kotlinx.jupyter.test import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult import org.jetbrains.kotlinx.jupyter.api.ResultHandlerCodeExecution import org.jetbrains.kotlinx.jupyter.api.SubtypeRendererTypeHandler -import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionImpl import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryResource import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceFallbacksBundle import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceLocation import org.jetbrains.kotlinx.jupyter.api.libraries.ResourcePathType import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceType +import org.jetbrains.kotlinx.jupyter.api.libraries.libraryDefinition import org.jetbrains.kotlinx.jupyter.execute import org.jetbrains.kotlinx.jupyter.test.repl.AbstractSingleReplTest import org.junit.jupiter.api.Test @@ -45,8 +45,8 @@ class TestFunList(private val head: T, private val tail: TestFunList?) { * Used for [EmbedReplTest.testSubtypeRenderer] */ @Suppress("unused") -val testLibraryDefinition1 = LibraryDefinitionImpl( - renderers = listOf( +val testLibraryDefinition1 = libraryDefinition { + it.renderers = listOf( SubtypeRendererTypeHandler( TestSum::class, ResultHandlerCodeExecution("\$it.a + \$it.b") @@ -56,14 +56,14 @@ val testLibraryDefinition1 = LibraryDefinitionImpl( ResultHandlerCodeExecution("\$it.render()") ) ) -) +} /** * Used for [EmbedReplTest.testJsResources] */ @Suppress("unused") -val testLibraryDefinition2 = LibraryDefinitionImpl( - resources = listOf( +val testLibraryDefinition2 = libraryDefinition { + it.resources = listOf( LibraryResource( listOf( ResourceFallbacksBundle( @@ -83,7 +83,7 @@ val testLibraryDefinition2 = LibraryDefinitionImpl( "testLib2" ) ) -) +} class EmbedReplTest : AbstractSingleReplTest() { override val repl = makeEmbeddedRepl() diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt index 658c4ec3c..bfdd92cd1 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt @@ -116,7 +116,7 @@ class IntegrationApiTests { val repl = makeRepl() repl.eval( """ - @file:DependsOn("src/test/testData/notebook-api-test-0.0.15.jar") + @file:DependsOn("src/test/testData/kotlin-jupyter-api-test-0.0.16.jar") """.trimIndent() ) @@ -166,4 +166,19 @@ class IntegrationApiTests { val result = repl.eval("B(A())") assertEquals("iB: iA", result.resultValue) } + + @Test + fun `code preprocessing`() { + val repl = makeRepl() + repl.eval( + """ + USE { + preprocessCode { it.replace('b', 'x') } + } + """.trimIndent() + ) + + val result = repl.eval("\"abab\"") + assertEquals("axax", result.resultValue) + } } diff --git a/src/test/testData/kotlin-jupyter-api-test-0.0.16.jar b/src/test/testData/kotlin-jupyter-api-test-0.0.16.jar new file mode 100644 index 0000000000000000000000000000000000000000..b16902e8853c418f93a183f9fb96c296ad433395 GIT binary patch literal 8241 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 literal 0 HcmV?d00001 diff --git a/src/test/testData/notebook-api-test-0.0.15.jar b/src/test/testData/notebook-api-test-0.0.15.jar deleted file mode 100644 index 09032ac9d7630b750e63e89251bb5ba82e878729..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7092 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