Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow value rendering using renderers processor from API #183

Merged
merged 1 commit into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ interface Notebook {
* Current JRE info
*/
val jreInfo: JREInfoProvider

/**
* Renderers processor gives an ability to render values and
* and add new renderers
*/
val renderersProcessor: TypeRenderersProcessor
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jetbrains.kotlinx.jupyter.api

import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost

/**
* [TypeRenderersProcessor] is responsible for rendering objects.
* You may use it to render values exactly like notebook renders results,
* and also register new renderers in runtime.
*/
interface TypeRenderersProcessor {
/**
* Renders [value] in context of this execution [host]
*/
fun renderValue(host: ExecutionHost, value: Any?): Any?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear what this function does. Documentation would be nice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a problem here. Suppose the value is a container (List<Something>). The type of actual element renderer could not be resolved in the runtime due to type erasure. Won't it require KType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding documentation: I've added it.
Regarding renderers: yes, due to type erasure KClass is the most we can get in runtime. It is how all the rendering mechanics works, it only uses KClass for getting type information. We could add Ktype argument here, but it would require breaking the renderers API, and it will actually be useless because it cannot be used for cells results rendering.


/**
* Adds new [renderer] for this notebook.
* Don't turn on the optimizations for [PrecompiledRendererTypeHandler]
*/
fun registerWithoutOptimizing(renderer: RendererTypeHandler)
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ abstract class JupyterIntegration : LibraryDefinitionProducer {
}

inline fun <reified T : Any> render(noinline renderer: CodeCell.(T) -> Any) {
val execution = ResultHandlerExecution { _, property ->
return renderWithHost { _, value: T -> renderer(this, value) }
}

inline fun <reified T : Any> renderWithHost(noinline renderer: CodeCell.(ExecutionHost, T) -> Any) {
val execution = ResultHandlerExecution { host, property ->
val currentCell = notebook.currentCell
?: throw IllegalStateException("Current cell should not be null on renderer invocation")
FieldValue(renderer(currentCell, property.value as T), property.name)
FieldValue(renderer(currentCell, host, property.value as T), property.name)
}
addRenderer(SubtypeRendererTypeHandler(T::class, execution))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.jetbrains.kotlinx.jupyter.codegen

import org.jetbrains.kotlinx.jupyter.api.Code
import org.jetbrains.kotlinx.jupyter.api.FieldValue
import org.jetbrains.kotlinx.jupyter.api.PrecompiledRendererTypeHandler
import org.jetbrains.kotlinx.jupyter.api.RendererTypeHandler
import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor
import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost

interface ResultsTypeRenderersProcessor : TypeRenderersProcessor {
/**
* Renders cell result [field] represented as [FieldValue] in the [host] context
*/
fun renderResult(host: ExecutionHost, field: FieldValue): Any?

/**
* Adds new [renderer] for this notebook.
* Returns code to be executed on execution host
* for [PrecompiledRendererTypeHandler]'s.
*/
fun register(renderer: RendererTypeHandler): Code?
}

This file was deleted.

5 changes: 5 additions & 0 deletions src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.jupyter.api.DisplayResultWithCell
import org.jetbrains.kotlinx.jupyter.api.JREInfoProvider
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion
import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor
import java.lang.IllegalStateException

class DisplayResultWrapper private constructor(
Expand Down Expand Up @@ -98,6 +99,7 @@ class NotebookImpl(
private val runtimeProperties: ReplRuntimeProperties,
) : Notebook {
private val cells = hashMapOf<Int, CodeCellImpl>()
internal var typeRenderersProcessor: TypeRenderersProcessor? = null

override val cellsList: Collection<CodeCellImpl>
get() = cells.values
Expand Down Expand Up @@ -156,4 +158,7 @@ class NotebookImpl(

override val lastCell: CodeCellImpl?
get() = history(1)

override val renderersProcessor: TypeRenderersProcessor
get() = typeRenderersProcessor ?: throw IllegalStateException("Type renderers processor is not initialized yet")
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.jetbrains.kotlinx.jupyter.repl.ContextUpdater

class TypeRenderersProcessorImpl(
private val contextUpdater: ContextUpdater,
) : TypeRenderersProcessor {
) : ResultsTypeRenderersProcessor {
private var counter = 0
private val typeRenderers: MutableList<HandlerWithInfo> = mutableListOf()

Expand All @@ -35,8 +35,20 @@ class TypeRenderersProcessorImpl(
}
}

override fun renderValue(host: ExecutionHost, value: Any?): Any? {
return renderResult(host, FieldValue(value, null))
}

override fun register(renderer: RendererTypeHandler): Code? {
if (renderer !is PrecompiledRendererTypeHandler || !renderer.mayBePrecompiled) {
return register(renderer, true)
}

override fun registerWithoutOptimizing(renderer: RendererTypeHandler) {
register(renderer, false)
}

private fun register(renderer: RendererTypeHandler, doOptimization: Boolean): Code? {
if (!doOptimization || renderer !is PrecompiledRendererTypeHandler || !renderer.mayBePrecompiled) {
typeRenderers.add(HandlerWithInfo(renderer, null))
return null
}
Expand Down
12 changes: 8 additions & 4 deletions src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessor
import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessorImpl
import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor
import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessorImpl
import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessor
import org.jetbrains.kotlinx.jupyter.codegen.ResultsTypeRenderersProcessor
import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessorImpl
import org.jetbrains.kotlinx.jupyter.common.looksLikeReplCommand
import org.jetbrains.kotlinx.jupyter.compiler.CompilerArgsConfigurator
Expand Down Expand Up @@ -307,7 +307,11 @@ class ReplForJupyterImpl(
executedCodeLogging != ExecutedCodeLogging.Off
)

private val typeRenderersProcessor: TypeRenderersProcessor = TypeRenderersProcessorImpl(contextUpdater)
private val typeRenderersProcessor: ResultsTypeRenderersProcessor = run {
val processor = TypeRenderersProcessorImpl(contextUpdater)
notebook.typeRenderersProcessor = processor
processor
}

private val fieldsProcessor: FieldsProcessor = FieldsProcessorImpl(contextUpdater)

Expand Down Expand Up @@ -421,11 +425,11 @@ class ReplForJupyterImpl(
currentClasspath.addAll(newClasspath)
if (trackClasspath) {
val sb = StringBuilder()
if (newClasspath.count() > 0) {
if (newClasspath.isNotEmpty()) {
sb.appendLine("${newClasspath.count()} new paths were added to classpath:")
newClasspath.sortedBy { it }.forEach { sb.appendLine(it) }
}
if (oldClasspath.count() > 0) {
if (oldClasspath.isNotEmpty()) {
sb.appendLine("${oldClasspath.count()} resolved paths were already in classpath:")
oldClasspath.sortedBy { it }.forEach { sb.appendLine(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.codegen.ClassAnnotationsProcessor
import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessor
import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor
import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessor
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
Expand All @@ -16,7 +16,7 @@ internal data class SharedReplContext(
val classAnnotationsProcessor: ClassAnnotationsProcessor,
val fileAnnotationsProcessor: FileAnnotationsProcessor,
val fieldsProcessor: FieldsProcessor,
val typeRenderersProcessor: TypeRenderersProcessor,
val typeRenderersProcessor: ResultsTypeRenderersProcessor,
val magicsProcessor: MagicsProcessor,
val resourcesProcessor: LibraryResourcesProcessor,
val librariesScanner: LibrariesScanner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,23 @@ class IntegrationApiTests {
assertEquals("1. 420", repl.eval("42.1").resultValue)
assertEquals("2. 150", repl.eval("15").resultValue)
}

@Test
fun `rendering processor should work fine`() {
val repl = makeRepl()
repl.eval(
"""
class A
class B(val a: A)

USE {
render<A> { "iA" }
renderWithHost<B> { host, value -> "iB: " + notebook!!.renderersProcessor.renderValue(host, value.a) }
}
""".trimIndent()
)

val result = repl.eval("B(A())")
assertEquals("iB: iA", result.resultValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.jetbrains.kotlinx.jupyter.api.DisplayResultWithCell
import org.jetbrains.kotlinx.jupyter.api.JREInfoProvider
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion
import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition
import org.jetbrains.kotlinx.jupyter.config.defaultRepositories
Expand Down Expand Up @@ -59,7 +60,7 @@ fun assertStartsWith(expectedPrefix: String, actual: String) {
}

fun Collection<Pair<String, String>>.toLibraries(): LibraryResolver {
val libJsons = map { it.first to it.second }.toMap()
val libJsons = associate { it.first to it.second }
return getResolverFromNamesMap(parseLibraryDescriptors(libJsons))
}

Expand Down Expand Up @@ -163,6 +164,9 @@ object NotebookMock : Notebook {
get() = defaultRuntimeProperties.version!!
override val jreInfo: JREInfoProvider
get() = JavaRuntime

override val renderersProcessor: TypeRenderersProcessor
get() = error("Not supposed to be called")
}

fun library(builder: JupyterIntegration.Builder.() -> Unit): LibraryDefinition {
Expand Down