Yet another bug with context receivers

This commit is contained in:
Alexander Nozik 2022-06-25 13:07:52 +03:00
parent f530b7605b
commit 781b185349
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 60 additions and 39 deletions

View File

@ -13,22 +13,18 @@ import space.kscience.dataforge.meta.string
//TODO replace by VisionForge type //TODO replace by VisionForge type
typealias HtmlFragment = TagConsumer<*>.() -> Unit typealias HtmlFragment = context(PageBuilder, TagConsumer<*>) () -> Unit
typealias HtmlData = Data<HtmlFragment> typealias HtmlData = Data<HtmlFragment>
fun HtmlData(meta: Meta, content: TagConsumer<*>.() -> Unit): HtmlData = Data(content, meta) fun HtmlData(meta: Meta, content: context(PageBuilder, TagConsumer<*>) () -> Unit): HtmlData =
Data(content, meta)
val HtmlData.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]" internal val HtmlData.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]"
val HtmlData.language: String? get() = meta["language"].string?.lowercase() internal val HtmlData.language: String? get() = meta["language"].string?.lowercase()
val HtmlData.order: Int? get() = meta["order"]?.int internal val HtmlData.order: Int? get() = meta["order"]?.int
fun TagConsumer<*>.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) { context(PageBuilder) fun FlowContent.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) {
data.await().invoke(this@htmlData) data.await().invoke(this@PageBuilder, consumer)
} }
fun FlowContent.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) {
data.await().invoke(consumer)
}

View File

@ -1,9 +1,10 @@
package space.kscience.snark package space.kscience.snark
import kotlinx.coroutines.runBlocking import kotlinx.html.body
import kotlinx.html.head
import kotlinx.html.title
import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.DataTreeItem import space.kscience.dataforge.data.DataTreeItem
import space.kscience.dataforge.data.await
import space.kscience.dataforge.data.getItem import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
@ -117,12 +118,16 @@ fun interface SiteLayout {
const val ASSETS_KEY = "assets" const val ASSETS_KEY = "assets"
val INDEX_PAGE_TOKEN = NameToken("index") val INDEX_PAGE_TOKEN = NameToken("index")
val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data -> val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data: Data<*> ->
if (data.type == typeOf<HtmlData>()) { if (data.type == typeOf<HtmlData>()) {
page { page {
head {
title = data.meta["title"].string ?: "Untitled page"
}
body {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val pageFragment: HtmlFragment = runBlocking { data.await() as HtmlFragment } htmlData(data as HtmlData)
pageFragment.invoke(consumer) }
} }
} }
} }

View File

@ -1,12 +1,14 @@
package space.kscience.snark package space.kscience.snark
import io.ktor.util.extension import io.ktor.util.extension
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readBytes import io.ktor.utils.io.core.readBytes
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.io.IOReader import space.kscience.dataforge.io.IOReader
import space.kscience.dataforge.io.JsonMetaFormat import space.kscience.dataforge.io.JsonMetaFormat
import space.kscience.dataforge.io.asBinary import space.kscience.dataforge.io.asBinary
import space.kscience.dataforge.io.readWith
import space.kscience.dataforge.io.yaml.YamlMetaFormat import space.kscience.dataforge.io.yaml.YamlMetaFormat
import space.kscience.dataforge.io.yaml.YamlPlugin import space.kscience.dataforge.io.yaml.YamlPlugin
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
@ -27,13 +29,19 @@ import kotlin.reflect.typeOf
* A parser of binary content including priority flag and file extensions * A parser of binary content including priority flag and file extensions
*/ */
@Type(SnarkParser.TYPE) @Type(SnarkParser.TYPE)
interface SnarkParser<out R : Any> : IOReader<R> { interface SnarkParser<out R> {
val type: KType
val fileExtensions: Set<String> val fileExtensions: Set<String>
val priority: Int get() = DEFAULT_PRIORITY val priority: Int get() = DEFAULT_PRIORITY
suspend fun parse(bytes: ByteArray, meta: Meta): R = bytes.asBinary().read { fun parse(snark: SnarkPlugin, meta: Meta, bytes: ByteArray): R
readObject(this)
fun reader(snark: SnarkPlugin, meta: Meta) = object : IOReader<R> {
override val type: KType get() = this@SnarkParser.type
override fun readObject(input: Input): R = parse(snark, meta, input.readBytes())
} }
companion object { companion object {
@ -47,7 +55,9 @@ internal class SnarkParserWrapper<R : Any>(
val reader: IOReader<R>, val reader: IOReader<R>,
override val type: KType, override val type: KType,
override val fileExtensions: Set<String>, override val fileExtensions: Set<String>,
) : SnarkParser<R>, IOReader<R> by reader ) : SnarkParser<R> {
override fun parse(snark: SnarkPlugin, meta: Meta, bytes: ByteArray): R = bytes.asBinary().readWith(reader)
}
/** /**
* Create a generic parser from reader * Create a generic parser from reader
@ -65,22 +75,22 @@ class SnarkPlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
private val parsers: Map<Name, SnarkParser<*>> by lazy { private val parsers: Map<Name, SnarkParser<Any>> by lazy {
context.gather(SnarkParser.TYPE, true) context.gather(SnarkParser.TYPE, true)
} }
fun readDirectory(path: Path): DataTree<Any> = io.readDataDirectory(path) { dataPath, meta -> fun readDirectory(path: Path): DataTree<Any> = io.readDataDirectory(path) { dataPath, meta ->
val fileExtension = meta[FileData.META_FILE_EXTENSION_KEY].string ?: dataPath.extension val fileExtension = meta[FileData.META_FILE_EXTENSION_KEY].string ?: dataPath.extension
val parser: SnarkParser<*>? = parsers.values.filter { parser -> val parser: SnarkParser<Any> = parsers.values.filter { parser ->
fileExtension in parser.fileExtensions fileExtension in parser.fileExtensions
}.maxByOrNull { }.maxByOrNull {
it.priority it.priority
} ?: run {
logger.warn { "The parser is not found for file $dataPath with meta $meta" }
byteArraySnarkParser
} }
parser ?: run { parser.reader(this, meta)
logger.warn { "The parser is not found for file $dataPath with meta $meta" }
byteArrayIOReader
}
} }
fun layout(meta: Meta): SiteLayout = when (meta[SiteLayout.LAYOUT_KEY]) { fun layout(meta: Meta): SiteLayout = when (meta[SiteLayout.LAYOUT_KEY]) {
@ -95,7 +105,7 @@ class SnarkPlugin : AbstractPlugin() {
"yaml".asName() to SnarkParser(YamlMetaFormat, "yaml", "yml"), "yaml".asName() to SnarkParser(YamlMetaFormat, "yaml", "yml"),
"png".asName() to SnarkParser(ImageIOReader, "png"), "png".asName() to SnarkParser(ImageIOReader, "png"),
"jpg".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"), "jpg".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"),
"gif".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"), "gif".asName() to SnarkParser(ImageIOReader, "gif"),
) )
else -> super.content(target) else -> super.content(target)
} }
@ -109,5 +119,7 @@ class SnarkPlugin : AbstractPlugin() {
private val byteArrayIOReader = IOReader { private val byteArrayIOReader = IOReader {
readBytes() readBytes()
} }
private val byteArraySnarkParser = SnarkParser(byteArrayIOReader)
} }
} }

View File

@ -8,33 +8,41 @@ import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser import org.intellij.markdown.parser.MarkdownParser
import space.kscience.dataforge.io.IOReader import space.kscience.dataforge.io.IOReader
import space.kscience.dataforge.meta.Meta
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
internal object SnarkHtmlParser : SnarkParser<HtmlFragment> { abstract class SnarkTextParser<R> : SnarkParser<R> {
abstract fun parseText(text: String, meta: Meta): R
override fun parse(snark: SnarkPlugin, meta: Meta, bytes: ByteArray): R =
parseText(bytes.decodeToString(), meta)
}
internal object SnarkHtmlParser : SnarkTextParser<HtmlFragment>() {
override val fileExtensions: Set<String> = setOf("html") override val fileExtensions: Set<String> = setOf("html")
override val type: KType = typeOf<HtmlFragment>() override val type: KType = typeOf<HtmlFragment>()
override fun readObject(input: Input): HtmlFragment = { override fun parseText(text: String, meta: Meta): HtmlFragment = {
div { div {
unsafe { +input.readText() } unsafe { +text }
} }
} }
} }
internal object SnarkMarkdownParser : SnarkParser<HtmlFragment> { internal object SnarkMarkdownParser : SnarkTextParser<HtmlFragment>() {
override val fileExtensions: Set<String> = setOf("markdown", "mdown", "mkdn", "mkd", "md") override val fileExtensions: Set<String> = setOf("markdown", "mdown", "mkdn", "mkd", "md")
override val type: KType = typeOf<HtmlFragment>() override val type: KType = typeOf<HtmlFragment>()
private val markdownFlavor = CommonMarkFlavourDescriptor() private val markdownFlavor = CommonMarkFlavourDescriptor()
private val markdownParser = MarkdownParser(markdownFlavor) private val markdownParser = MarkdownParser(markdownFlavor)
override fun readObject(input: Input): HtmlFragment { override fun parseText(text: String, meta: Meta): HtmlFragment {
val src = input.readText() val parsedTree = markdownParser.buildMarkdownTreeFromString(text)
val parsedTree = markdownParser.buildMarkdownTreeFromString(src) val htmlString = HtmlGenerator(text, parsedTree, markdownFlavor).generateHtml()
val htmlString = HtmlGenerator(src, parsedTree, markdownFlavor).generateHtml()
return { return {
div { div {