forked from SPC/spc-site
Yet another bug with context receivers
This commit is contained in:
parent
f530b7605b
commit
781b185349
@ -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) {
|
|
||||||
data.await().invoke(this@htmlData)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FlowContent.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) {
|
|
||||||
data.await().invoke(consumer)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
context(PageBuilder) fun FlowContent.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) {
|
||||||
|
data.await().invoke(this@PageBuilder, consumer)
|
||||||
|
}
|
@ -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 {
|
||||||
@Suppress("UNCHECKED_CAST")
|
head {
|
||||||
val pageFragment: HtmlFragment = runBlocking { data.await() as HtmlFragment }
|
title = data.meta["title"].string ?: "Untitled page"
|
||||||
pageFragment.invoke(consumer)
|
}
|
||||||
|
body {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
htmlData(data as HtmlData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,25 +75,25 @@ 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]) {
|
||||||
else -> DefaultSiteLayout
|
else -> DefaultSiteLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user