All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlinx.jupyter.api.TextRendering.kt Maven / Gradle / Ivy

There is a newer version: 0.12.0-335
Show newest version
package org.jetbrains.kotlinx.jupyter.api

import java.lang.reflect.Field
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.internal.ReflectProperties.Val
import kotlin.reflect.jvm.isAccessible

fun interface TextRenderer {
    fun render(
        processor: TextRenderersProcessor,
        value: Any?,
    ): String?
}

class TextRendererWithDescription(private val description: String, action: TextRenderer) : TextRenderer by action {
    override fun toString(): String {
        return "Text renderer: $description"
    }
}

data class TextRendererWithPriority(
    val renderer: TextRenderer,
    val priority: Int = ProcessingPriority.DEFAULT,
)

private fun String.indent(
    indent: String = "    ",
    exceptFirst: Boolean = false,
): String {
    val str = this
    return buildString {
        val lines = str.lines()
        for ((i, l) in lines.withIndex()) {
            if (!exceptFirst || i != 0) append(indent)
            append(l)
            if (i != lines.lastIndex) append("\n")
        }
    }
}

object TextRenderers {
    val NULL =
        TextRendererWithDescription("renders null") { _, value ->
            if (value == null) "null" else null
        }

    val PRIMITIVES =
        TextRendererWithDescription("renders strings, booleans and numbers") { _, value ->
            when (value) {
                is String -> value
                is Number -> value.toString()
                is Boolean -> value.toString()
                else -> null
            }
        }

    val ITERABLES =
        TextRendererWithDescription("renders iterables, elements are rendered transitively") { processor, value ->
            if (value !is Iterable<*>) {
                null
            } else {
                when (value) {
                    is ClosedRange<*> -> value.toString()
                    else ->
                        renderIterable(
                            processor,
                            processor.render(value::class),
                            value,
                        )
                }
            }
        }

    val MAPS =
        TextRendererWithDescription("renders maps, keys and values are rendered transitively") { processor, value ->
            if (value !is Map<*, *>) {
                null
            } else {
                renderMap(processor, processor.render(value::class), value)
            }
        }

    val CLASS =
        TextRendererWithDescription("renders KClass<*> objects") { _, value ->
            if (value !is KClass<*>) {
                null
            } else {
                value.simpleName
            }
        }

    val OBJECT =
        TextRendererWithDescription("renders any objects") { processor, value ->
            if (value == null) {
                null
            } else {
                when (value) {
                    is Val<*> -> value.toString()
                    is KClass<*> -> value.simpleName
                    is Class<*> -> value.simpleName
                    else -> {
                        val valueMap =
                            when (value) {
                                is MatchResult -> buildObjectMapByKotlinProperties(value, MatchResult::class)
                                is MatchGroup -> buildObjectMapByKotlinProperties(value, MatchGroup::class)
                                else -> buildObjectMapByJavaFields(value)
                            }
                        renderMap(
                            processor,
                            processor.render(value::class),
                            valueMap,
                            "=",
                            openBracket = "(",
                            closeBracket = ")",
                        )
                    }
                }
            }
        }

    val AVOID =
        TextRendererWithDescription("skips text rendering for some kinds of objects such as java.lang.Class") { _, value ->
            if (value == null) {
                null
            } else {
                val clazz = value::class
                val className = clazz.qualifiedName
                if (className?.startsWith("java.lang.") == true) {
                    value.toString()
                } else {
                    null
                }
            }
        }

    private fun renderIterable(
        processor: TextRenderersProcessor,
        title: String,
        iterable: Iterable<*>,
        separator: String = ",",
        multiline: Boolean = false,
        openBracket: String = "[",
        closeBracket: String = "]",
    ): String {
        return buildString {
            append("$title$openBracket")
            if (multiline) {
                append('\n')
            }
            val collection = iterable as? Collection<*>
            for ((i, el) in iterable.withIndex()) {
                processor.render(el)
                    .let { rel -> if (multiline) rel.indent() else rel }
                    .let { append(it) }
                if (collection == null || i < collection.size - 1) {
                    append(separator)
                    if (!multiline) append(' ')
                }
                if (multiline) append('\n')
            }
            append(closeBracket)
        }
    }

    private fun renderMap(
        processor: TextRenderersProcessor,
        title: String,
        map: Map<*, *>,
        arrowString: String = " => ",
        separator: String = ",",
        multiline: Boolean = false,
        openBracket: String = "{",
        closeBracket: String = "}",
    ): String {
        return buildString {
            append("$title$openBracket")
            if (multiline) {
                append('\n')
            }
            var i = 0
            for ((k, v) in map) {
                processor.render(k)
                    .let { rk -> if (multiline) rk.indent() else rk }
                    .let { append(it) }
                append(arrowString)
                processor.render(v)
                    .let { rv -> if (multiline) rv.indent(exceptFirst = true) else rv }
                    .let { append(it) }
                if (i < map.size - 1) {
                    append(separator)
                    if (!multiline) append(' ')
                }
                if (multiline) append('\n')
                ++i
            }
            append(closeBracket)
        }
    }

    private fun buildObjectMapByJavaFields(obj: Any): Map {
        val clazz: Class<*> = obj::class.java
        return buildObjectMapByJavaFields(obj, clazz.declaredFields)
    }

    private fun buildObjectMapByJavaFields(
        obj: Any,
        fields: Array,
    ): Map {
        return buildAbstractObjectMap(obj, fields.toList(), { it.name }) { f, o ->
            f.isAccessible = true
            f.get(o)
        }
    }

    private fun buildObjectMapByKotlinProperties(
        obj: Any,
        clazz: KClass<*>,
    ): Map {
        return buildObjectMapByKotlinProperties(obj, clazz.memberProperties)
    }

    private fun buildObjectMapByKotlinProperties(
        obj: Any,
        properties: Collection>,
    ): Map {
        return buildAbstractObjectMap(obj, properties, { it.name }) { p, o ->
            @Suppress("UNCHECKED_CAST")
            (p as KProperty1)
            p.isAccessible = true
            p.get(o)
        }
    }

    private fun 

buildAbstractObjectMap( obj: Any, abstractProps: Iterable

, nameGetter: (P) -> String, valueGetter: (P, Any) -> Any?, ): Map { return hashMapOf().apply { for (prop in abstractProps) { val name: String = nameGetter(prop) val value: Any? = try { valueGetter(prop, obj) } catch (e: IllegalAccessException) { "[exception thrown]" } catch (e: RuntimeException) { "[inaccessible field]" } put(name, value) } } } } fun TextRenderersProcessor.registerDefaultRenderers() { register(TextRenderers.NULL, ProcessingPriority.DEFAULT) register(TextRenderers.MAPS, ProcessingPriority.DEFAULT) register(TextRenderers.ITERABLES, ProcessingPriority.LOW) register(TextRenderers.PRIMITIVES, ProcessingPriority.DEFAULT) register(TextRenderers.CLASS, ProcessingPriority.DEFAULT) register(TextRenderers.OBJECT, ProcessingPriority.LOWER) register(TextRenderers.AVOID, ProcessingPriority.HIGH) }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy