diff --git a/src/main/kotlin/space/kscience/snark/HtmlData.kt b/src/main/kotlin/space/kscience/snark/HtmlData.kt index fd836b0..ad44fea 100644 --- a/src/main/kotlin/space/kscience/snark/HtmlData.kt +++ b/src/main/kotlin/space/kscience/snark/HtmlData.kt @@ -13,22 +13,18 @@ import space.kscience.dataforge.meta.string //TODO replace by VisionForge type -typealias HtmlFragment = TagConsumer<*>.() -> Unit +typealias HtmlFragment = context(PageBuilder, TagConsumer<*>) () -> Unit typealias HtmlData = Data -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()}]" -val HtmlData.language: String? get() = meta["language"].string?.lowercase() +internal val HtmlData.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]" +internal val HtmlData.language: String? get() = meta["language"].string?.lowercase() -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) -} +internal val HtmlData.order: Int? get() = meta["order"]?.int +context(PageBuilder) fun FlowContent.htmlData(data: HtmlData) = runBlocking(Dispatchers.IO) { + data.await().invoke(this@PageBuilder, consumer) +} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/SiteLayout.kt b/src/main/kotlin/space/kscience/snark/SiteLayout.kt index 9062a78..f86469d 100644 --- a/src/main/kotlin/space/kscience/snark/SiteLayout.kt +++ b/src/main/kotlin/space/kscience/snark/SiteLayout.kt @@ -1,9 +1,10 @@ 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.DataTreeItem -import space.kscience.dataforge.data.await import space.kscience.dataforge.data.getItem import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get @@ -117,12 +118,16 @@ fun interface SiteLayout { const val ASSETS_KEY = "assets" val INDEX_PAGE_TOKEN = NameToken("index") - val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data -> + val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data: Data<*> -> if (data.type == typeOf()) { page { - @Suppress("UNCHECKED_CAST") - val pageFragment: HtmlFragment = runBlocking { data.await() as HtmlFragment } - pageFragment.invoke(consumer) + head { + title = data.meta["title"].string ?: "Untitled page" + } + body { + @Suppress("UNCHECKED_CAST") + htmlData(data as HtmlData) + } } } } diff --git a/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt index ee5c680..41df20d 100644 --- a/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt +++ b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt @@ -1,12 +1,14 @@ package space.kscience.snark import io.ktor.util.extension +import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.readBytes import space.kscience.dataforge.context.* import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.io.IOReader import space.kscience.dataforge.io.JsonMetaFormat 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.YamlPlugin 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 */ @Type(SnarkParser.TYPE) -interface SnarkParser : IOReader { +interface SnarkParser { + val type: KType + val fileExtensions: Set val priority: Int get() = DEFAULT_PRIORITY - suspend fun parse(bytes: ByteArray, meta: Meta): R = bytes.asBinary().read { - readObject(this) + fun parse(snark: SnarkPlugin, meta: Meta, bytes: ByteArray): R + + fun reader(snark: SnarkPlugin, meta: Meta) = object : IOReader { + override val type: KType get() = this@SnarkParser.type + + override fun readObject(input: Input): R = parse(snark, meta, input.readBytes()) } companion object { @@ -47,7 +55,9 @@ internal class SnarkParserWrapper( val reader: IOReader, override val type: KType, override val fileExtensions: Set, -) : SnarkParser, IOReader by reader +) : SnarkParser { + override fun parse(snark: SnarkPlugin, meta: Meta, bytes: ByteArray): R = bytes.asBinary().readWith(reader) +} /** * Create a generic parser from reader @@ -65,25 +75,25 @@ class SnarkPlugin : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag - private val parsers: Map> by lazy { + private val parsers: Map> by lazy { context.gather(SnarkParser.TYPE, true) } fun readDirectory(path: Path): DataTree = io.readDataDirectory(path) { dataPath, meta -> val fileExtension = meta[FileData.META_FILE_EXTENSION_KEY].string ?: dataPath.extension - val parser: SnarkParser<*>? = parsers.values.filter { parser -> + val parser: SnarkParser = parsers.values.filter { parser -> fileExtension in parser.fileExtensions }.maxByOrNull { it.priority + } ?: run { + logger.warn { "The parser is not found for file $dataPath with meta $meta" } + byteArraySnarkParser } - parser ?: run { - logger.warn { "The parser is not found for file $dataPath with meta $meta" } - byteArrayIOReader - } + parser.reader(this, meta) } - fun layout(meta: Meta): SiteLayout = when(meta[SiteLayout.LAYOUT_KEY]){ + fun layout(meta: Meta): SiteLayout = when (meta[SiteLayout.LAYOUT_KEY]) { else -> DefaultSiteLayout } @@ -95,7 +105,7 @@ class SnarkPlugin : AbstractPlugin() { "yaml".asName() to SnarkParser(YamlMetaFormat, "yaml", "yml"), "png".asName() to SnarkParser(ImageIOReader, "png"), "jpg".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"), - "gif".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"), + "gif".asName() to SnarkParser(ImageIOReader, "gif"), ) else -> super.content(target) } @@ -109,5 +119,7 @@ class SnarkPlugin : AbstractPlugin() { private val byteArrayIOReader = IOReader { readBytes() } + + private val byteArraySnarkParser = SnarkParser(byteArrayIOReader) } } \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/parsers.kt b/src/main/kotlin/space/kscience/snark/parsers.kt index 05ac768..eb0658d 100644 --- a/src/main/kotlin/space/kscience/snark/parsers.kt +++ b/src/main/kotlin/space/kscience/snark/parsers.kt @@ -8,33 +8,41 @@ import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.parser.MarkdownParser import space.kscience.dataforge.io.IOReader +import space.kscience.dataforge.meta.Meta import java.awt.image.BufferedImage import javax.imageio.ImageIO import kotlin.reflect.KType import kotlin.reflect.typeOf -internal object SnarkHtmlParser : SnarkParser { +abstract class SnarkTextParser : SnarkParser { + 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() { override val fileExtensions: Set = setOf("html") override val type: KType = typeOf() - override fun readObject(input: Input): HtmlFragment = { + override fun parseText(text: String, meta: Meta): HtmlFragment = { div { - unsafe { +input.readText() } + unsafe { +text } } } } -internal object SnarkMarkdownParser : SnarkParser { +internal object SnarkMarkdownParser : SnarkTextParser() { override val fileExtensions: Set = setOf("markdown", "mdown", "mkdn", "mkd", "md") override val type: KType = typeOf() private val markdownFlavor = CommonMarkFlavourDescriptor() private val markdownParser = MarkdownParser(markdownFlavor) - override fun readObject(input: Input): HtmlFragment { - val src = input.readText() - val parsedTree = markdownParser.buildMarkdownTreeFromString(src) - val htmlString = HtmlGenerator(src, parsedTree, markdownFlavor).generateHtml() + override fun parseText(text: String, meta: Meta): HtmlFragment { + val parsedTree = markdownParser.buildMarkdownTreeFromString(text) + val htmlString = HtmlGenerator(text, parsedTree, markdownFlavor).generateHtml() return { div {