Skip to content

Commit 71a8bc6

Browse files
committed
Add code preprocessors as a part of library API
Fixes #223
1 parent efb5d6c commit 71a8bc6

File tree

13 files changed

+115
-12
lines changed

13 files changed

+115
-12
lines changed

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ logbackVersion=1.2.3
1616
http4kVersion=4.4.0.1
1717
artifactsPath=build/artifacts
1818

19-
baseVersion=0.9.1
19+
baseVersion=0.10.0
2020

2121
projectRepoUrl=https://github.com/Kotlin/kotlin-jupyter
2222
docsRepo=git@github.com:ileasile/kotlin-jupyter-docs.git
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.jetbrains.kotlinx.jupyter.api
2+
3+
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionProducer
4+
5+
/**
6+
* Preprocesses the cell code before its execution
7+
*/
8+
interface CodePreprocessor {
9+
/**
10+
* Returns `true` if this preprocessor accepts the given [code]
11+
*/
12+
fun accepts(code: String): Boolean = true
13+
14+
/**
15+
* Performs code preprocessing
16+
*/
17+
fun process(code: String, host: KotlinKernelHost): Result
18+
19+
data class Result(val code: Code, val libraries: List<LibraryDefinitionProducer> = emptyList())
20+
}

jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt

+21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package org.jetbrains.kotlinx.jupyter.api.libraries
33
import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback
44
import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler
55
import org.jetbrains.kotlinx.jupyter.api.ClassDeclarationsCallback
6+
import org.jetbrains.kotlinx.jupyter.api.Code
67
import org.jetbrains.kotlinx.jupyter.api.CodeCell
8+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
79
import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback
810
import org.jetbrains.kotlinx.jupyter.api.FieldHandler
911
import org.jetbrains.kotlinx.jupyter.api.FieldHandlerByClass
@@ -54,6 +56,8 @@ abstract class JupyterIntegration : LibraryDefinitionProducer {
5456

5557
private val repositories = mutableListOf<String>()
5658

59+
private val codePreprocessors = mutableListOf<CodePreprocessor>()
60+
5761
fun addRenderer(handler: RendererTypeHandler) {
5862
renderers.add(handler)
5963
}
@@ -70,6 +74,10 @@ abstract class JupyterIntegration : LibraryDefinitionProducer {
7074
fileAnnotations.add(handler)
7175
}
7276

77+
fun addCodePreprocessor(preprocessor: CodePreprocessor) {
78+
codePreprocessors.add(preprocessor)
79+
}
80+
7381
inline fun <reified T : Any> render(noinline renderer: CodeCell.(T) -> Any) {
7482
return renderWithHost { _, value: T -> renderer(this, value) }
7583
}
@@ -152,6 +160,18 @@ abstract class JupyterIntegration : LibraryDefinitionProducer {
152160
addFileAnnotationHanlder(FileAnnotationHandler(T::class, callback))
153161
}
154162

163+
fun preprocessCodeWithLibraries(callback: KotlinKernelHost.(Code) -> CodePreprocessor.Result) {
164+
addCodePreprocessor(object : CodePreprocessor {
165+
override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result {
166+
return host.callback(code)
167+
}
168+
})
169+
}
170+
171+
fun preprocessCode(callback: KotlinKernelHost.(Code) -> Code) {
172+
preprocessCodeWithLibraries { CodePreprocessor.Result(this.callback(it)) }
173+
}
174+
155175
internal fun getDefinition() =
156176
LibraryDefinitionImpl(
157177
init = init,
@@ -166,6 +186,7 @@ abstract class JupyterIntegration : LibraryDefinitionProducer {
166186
classAnnotations = classAnnotations,
167187
fileAnnotations = fileAnnotations,
168188
resources = resources,
189+
codePreprocessors = codePreprocessors,
169190
)
170191
}
171192

jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinition.kt

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.jupyter.api.libraries
22

33
import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback
44
import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler
5+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
56
import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback
67
import org.jetbrains.kotlinx.jupyter.api.FieldHandler
78
import org.jetbrains.kotlinx.jupyter.api.FileAnnotationHandler
@@ -85,6 +86,12 @@ interface LibraryDefinition {
8586
val resources: List<LibraryResource>
8687
get() = emptyList()
8788

89+
/**
90+
* List of code preprocessors
91+
*/
92+
val codePreprocessors: List<CodePreprocessor>
93+
get() = emptyList()
94+
8895
/**
8996
* Minimal kernel version that is supported by this library
9097
*/

jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/LibraryDefinitionImpl.kt

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.jupyter.api.libraries
22

33
import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback
44
import org.jetbrains.kotlinx.jupyter.api.ClassAnnotationHandler
5+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
56
import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback
67
import org.jetbrains.kotlinx.jupyter.api.FieldHandler
78
import org.jetbrains.kotlinx.jupyter.api.FileAnnotationHandler
@@ -26,6 +27,7 @@ class LibraryDefinitionImpl(
2627
override val classAnnotations: List<ClassAnnotationHandler> = emptyList(),
2728
override val fileAnnotations: List<FileAnnotationHandler> = emptyList(),
2829
override val resources: List<LibraryResource> = emptyList(),
30+
override val codePreprocessors: List<CodePreprocessor> = emptyList(),
2931
override val minKernelVersion: KotlinKernelVersion? = null,
3032
override val originalDescriptorText: String? = null,
3133
) : LibraryDefinition
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.jetbrains.kotlinx.jupyter.magics
2+
3+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
4+
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
5+
6+
/**
7+
* Containing [preprocessors]' [process] are run in reversed order: last added processors
8+
* are run first
9+
*/
10+
class CompoundCodePreprocessor(
11+
private val preprocessors: MutableList<CodePreprocessor>
12+
) : CodePreprocessor {
13+
constructor(vararg preprocessors: CodePreprocessor) : this(preprocessors.toMutableList())
14+
15+
override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result {
16+
return preprocessors.foldRight(CodePreprocessor.Result(code, emptyList())) { preprocessor, result ->
17+
if (preprocessor.accepts(result.code)) {
18+
val newResult = preprocessor.process(result.code, host)
19+
CodePreprocessor.Result(newResult.code, result.libraries + newResult.libraries)
20+
} else result
21+
}
22+
}
23+
24+
fun add(preprocessor: CodePreprocessor) {
25+
preprocessors.add(preprocessor)
26+
}
27+
28+
fun addAll(preprocessors: Iterable<CodePreprocessor>) {
29+
this.preprocessors.addAll(preprocessors)
30+
}
31+
}

jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/magics/MagicsProcessor.kt

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.jetbrains.kotlinx.jupyter.magics
22

3-
import org.jetbrains.kotlinx.jupyter.api.Code
4-
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinitionProducer
3+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
4+
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
55
import org.jetbrains.kotlinx.jupyter.common.ReplLineMagic
66
import org.jetbrains.kotlinx.jupyter.compiler.util.CodeInterval
77
import org.jetbrains.kotlinx.jupyter.exceptions.ReplPreprocessingException
@@ -10,8 +10,8 @@ import kotlin.script.experimental.jvm.util.determineSep
1010
class MagicsProcessor(
1111
private val handler: MagicsHandler,
1212
private val parseOutCellMarker: Boolean = false,
13-
) {
14-
fun processMagics(code: String, parseOnly: Boolean = false, tryIgnoreErrors: Boolean = false): MagicProcessingResult {
13+
) : CodePreprocessor {
14+
fun processMagics(code: String, parseOnly: Boolean = false, tryIgnoreErrors: Boolean = false): CodePreprocessor.Result {
1515
val magics = magicsIntervals(code)
1616

1717
for (magicRange in magics) {
@@ -38,7 +38,7 @@ class MagicsProcessor(
3838

3939
val codes = codeIntervals(code, magics, true)
4040
val preprocessedCode = codes.joinToString("") { code.substring(it.from, it.to) }
41-
return MagicProcessingResult(preprocessedCode, handler.getLibraries())
41+
return CodePreprocessor.Result(preprocessedCode, handler.getLibraries())
4242
}
4343

4444
fun codeIntervals(
@@ -79,7 +79,9 @@ class MagicsProcessor(
7979
}
8080
}
8181

82-
data class MagicProcessingResult(val code: Code, val libraries: List<LibraryDefinitionProducer>)
82+
override fun process(code: String, host: KotlinKernelHost): CodePreprocessor.Result {
83+
return processMagics(code)
84+
}
8385

8486
companion object {
8587
private const val MAGICS_SIGN = '%'

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import jupyter.kotlin.KotlinKernelHostProvider
77
import jupyter.kotlin.Repository
88
import org.jetbrains.kotlin.config.KotlinCompilerVersion
99
import org.jetbrains.kotlinx.jupyter.api.Code
10+
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
1011
import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback
1112
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
1213
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion
@@ -40,6 +41,7 @@ import org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner
4041
import org.jetbrains.kotlinx.jupyter.libraries.LibraryResourcesProcessorImpl
4142
import org.jetbrains.kotlinx.jupyter.libraries.ResolutionInfoProvider
4243
import org.jetbrains.kotlinx.jupyter.libraries.ResolutionInfoSwitcher
44+
import org.jetbrains.kotlinx.jupyter.magics.CompoundCodePreprocessor
4345
import org.jetbrains.kotlinx.jupyter.magics.FullMagicsHandler
4446
import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor
4547
import org.jetbrains.kotlinx.jupyter.repl.CellExecutor
@@ -231,6 +233,8 @@ class ReplForJupyterImpl(
231233
)
232234
)
233235

236+
private val codePreprocessor = CompoundCodePreprocessor(magics)
237+
234238
private val importsCollector: ScriptImportsCollector = ScriptImportsCollectorImpl()
235239

236240
// Used for various purposes, i.e. completion and listing errors
@@ -328,7 +332,7 @@ class ReplForJupyterImpl(
328332
fileAnnotationsProcessor,
329333
fieldsProcessor,
330334
typeRenderersProcessor,
331-
magics,
335+
codePreprocessor,
332336
resourcesProcessor,
333337
librariesScanner,
334338
notebook,

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/CellExecutorImpl.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal class CellExecutorImpl(private val replContext: SharedReplContext) : Ce
4545

4646
log.debug("Executing code:\n$code")
4747
val preprocessedCode = if (processMagics) {
48-
val processedMagics = magicsProcessor.processMagics(code)
48+
val processedMagics = codePreprocessor.process(code, context)
4949

5050
log.debug("Adding ${processedMagics.libraries.size} libraries")
5151
processedMagics.libraries.getDefinitions(notebook).forEach {
@@ -128,6 +128,7 @@ internal class CellExecutorImpl(private val replContext: SharedReplContext) : Ce
128128
library.classAnnotations.forEach(sharedContext.classAnnotationsProcessor::register)
129129
library.fileAnnotations.forEach(sharedContext.fileAnnotationsProcessor::register)
130130
sharedContext.afterCellExecution.addAll(library.afterCellExecution)
131+
sharedContext.codePreprocessor.addAll(library.codePreprocessors)
131132

132133
val classLoader = sharedContext.evaluator.lastClassLoader
133134
rethrowAsLibraryException(LibraryProblemPart.RESOURCES) {

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor
99
import org.jetbrains.kotlinx.jupyter.codegen.ResultsTypeRenderersProcessor
1010
import org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner
1111
import org.jetbrains.kotlinx.jupyter.libraries.LibraryResourcesProcessor
12-
import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor
12+
import org.jetbrains.kotlinx.jupyter.magics.CompoundCodePreprocessor
1313
import org.jetbrains.kotlinx.jupyter.repl.InternalEvaluator
1414

1515
internal data class SharedReplContext(
1616
val classAnnotationsProcessor: ClassAnnotationsProcessor,
1717
val fileAnnotationsProcessor: FileAnnotationsProcessor,
1818
val fieldsProcessor: FieldsProcessor,
1919
val typeRenderersProcessor: ResultsTypeRenderersProcessor,
20-
val magicsProcessor: MagicsProcessor,
20+
val codePreprocessor: CompoundCodePreprocessor,
2121
val resourcesProcessor: LibraryResourcesProcessor,
2222
val librariesScanner: LibrariesScanner,
2323
val notebook: Notebook,

src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt

+16-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class IntegrationApiTests {
116116
val repl = makeRepl()
117117
repl.eval(
118118
"""
119-
@file:DependsOn("src/test/testData/notebook-api-test-0.0.15.jar")
119+
@file:DependsOn("src/test/testData/kotlin-jupyter-api-test-0.0.16.jar")
120120
""".trimIndent()
121121
)
122122

@@ -166,4 +166,19 @@ class IntegrationApiTests {
166166
val result = repl.eval("B(A())")
167167
assertEquals("iB: iA", result.resultValue)
168168
}
169+
170+
@Test
171+
fun `code preprocessing`() {
172+
val repl = makeRepl()
173+
repl.eval(
174+
"""
175+
USE {
176+
preprocessCode { it.replace('b', 'x') }
177+
}
178+
""".trimIndent()
179+
)
180+
181+
val result = repl.eval("\"abab\"")
182+
assertEquals("axax", result.resultValue)
183+
}
169184
}
Binary file not shown.
-6.93 KB
Binary file not shown.

0 commit comments

Comments
 (0)