diff --git a/build.gradle.kts b/build.gradle.kts index b1325e1..d018f1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import ru.mipt.npm.gradle.KScienceVersions plugins { @@ -21,11 +20,11 @@ application { applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") } -tasks.withType{ - kotlinOptions{ - freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers" - } -} +//tasks.withType{ +// kotlinOptions{ +// freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers" +// } +//} val dataforgeVersion by extra("0.6.0-dev-3") val ktorVersion = KScienceVersions.ktorVersion diff --git a/src/main/kotlin/ru/mipt/Application.kt b/src/main/kotlin/ru/mipt/Application.kt index 4f17931..3f269fd 100644 --- a/src/main/kotlin/ru/mipt/Application.kt +++ b/src/main/kotlin/ru/mipt/Application.kt @@ -16,12 +16,12 @@ import io.ktor.server.routing.route import io.ktor.server.routing.routing import ru.mipt.plugins.configureTemplating import ru.mipt.spc.magprog.DataSetSiteContext -import ru.mipt.spc.magprog.DirectoryDataTree import ru.mipt.spc.magprog.SiteContext import ru.mipt.spc.magprog.magProgPage import space.kscience.dataforge.context.Context import space.kscience.dataforge.io.io import space.kscience.dataforge.io.yaml.YamlPlugin +import space.kscience.snark.DirectoryDataTree import java.nio.file.Path diff --git a/src/main/kotlin/ru/mipt/snapshot.kt b/src/main/kotlin/ru/mipt/snapshot.kt new file mode 100644 index 0000000..11e321e --- /dev/null +++ b/src/main/kotlin/ru/mipt/snapshot.kt @@ -0,0 +1,12 @@ +package ru.mipt + +//private fun snapshotRoute(route: Route, path: Path){ +// route.children.forEach { +// it. +// } +//} +// +// +//fun Application.snapshotTo(path: Path): Job = launch{ +// pluginOrNull(Routing)?.children +//} \ No newline at end of file diff --git a/src/main/kotlin/ru/mipt/spc/magprog/DataSetSiteContext.kt b/src/main/kotlin/ru/mipt/spc/magprog/DataSetSiteContext.kt index 59987e6..091478b 100644 --- a/src/main/kotlin/ru/mipt/spc/magprog/DataSetSiteContext.kt +++ b/src/main/kotlin/ru/mipt/spc/magprog/DataSetSiteContext.kt @@ -7,7 +7,6 @@ import kotlinx.serialization.json.Json import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.parser.MarkdownParser -import ru.mipt.spc.magprog.DirectoryDataTree.Companion.META_FILE_EXTENSION_KEY import space.kscience.dataforge.context.Context import space.kscience.dataforge.data.* import space.kscience.dataforge.io.asBinary @@ -19,6 +18,7 @@ import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.toMeta import space.kscience.dataforge.misc.DFInternal import space.kscience.dataforge.names.Name +import space.kscience.snark.DirectoryDataTree.Companion.META_FILE_EXTENSION_KEY import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf @@ -35,12 +35,12 @@ class DataSetSiteContext( private val markdownParser = MarkdownParser(markdownFlavor) //TODO replace by a plugin - private suspend fun Data.toHtmlBlock(): HtmlBlock { + private suspend fun Data.toHtmlBlock(): HtmlData { val fileType = meta[META_FILE_EXTENSION_KEY].string val src = await().decodeToString() return when (fileType) { - "html" -> HtmlBlock(meta) { + "html" -> HtmlData(meta) { div { unsafe { +src @@ -48,7 +48,7 @@ class DataSetSiteContext( } } - "markdown", "mdown", "mkdn", "mkd", "md" -> HtmlBlock(meta) { + "markdown", "mdown", "mkdn", "mkd", "md" -> HtmlData(meta) { div("markdown") { val parsedTree = markdownParser.buildMarkdownTreeFromString(src) @@ -93,11 +93,11 @@ class DataSetSiteContext( override fun resolveAll(type: KType, filter: (name: Name, meta: Meta) -> Boolean): DataSet = dataSet.select(type, filter = filter) - override fun resolveHtml(name: Name): HtmlBlock? = runBlocking { + override fun resolveHtml(name: Name): HtmlData? = runBlocking { resolve(name)?.takeIf { it.published }?.toHtmlBlock() } - override fun resolveAllHtml(filter: (name: Name, meta: Meta) -> Boolean): Map = runBlocking { + override fun resolveAllHtml(filter: (name: Name, meta: Meta) -> Boolean): Map = runBlocking { buildMap { resolveAll(filter).dataSequence().filter { it.published }.forEach { put(it.name, it.toHtmlBlock()) diff --git a/src/main/kotlin/ru/mipt/spc/magprog/HtmlBlock.kt b/src/main/kotlin/ru/mipt/spc/magprog/HtmlBlock.kt deleted file mode 100644 index 168c3ca..0000000 --- a/src/main/kotlin/ru/mipt/spc/magprog/HtmlBlock.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.mipt.spc.magprog - -import kotlinx.html.FlowContent -import space.kscience.dataforge.meta.* - -enum class Language { - EN, - RU -} - -interface HtmlBlock { - val meta: Meta - val content: FlowContent.() -> Unit -} - -fun HtmlBlock(meta: Meta, content: FlowContent.() -> Unit ) = object: HtmlBlock{ - override val meta: Meta = meta - override val content: FlowContent.() -> Unit = content -} - -val HtmlBlock.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]" -val HtmlBlock.language: Language get() = meta["language"]?.enum() ?: Language.RU - -val HtmlBlock.order: Int? get() = meta["order"]?.int - diff --git a/src/main/kotlin/ru/mipt/spc/magprog/HtmlData.kt b/src/main/kotlin/ru/mipt/spc/magprog/HtmlData.kt new file mode 100644 index 0000000..746843f --- /dev/null +++ b/src/main/kotlin/ru/mipt/spc/magprog/HtmlData.kt @@ -0,0 +1,34 @@ +package ru.mipt.spc.magprog + +import kotlinx.coroutines.runBlocking +import kotlinx.html.FlowContent +import kotlinx.html.TagConsumer +import space.kscience.dataforge.data.Data +import space.kscience.dataforge.data.await +import space.kscience.dataforge.meta.* + +enum class Language { + EN, + RU +} + +//TODO replace by VisionForge type +typealias HtmlFragment = TagConsumer<*>.() -> Unit + +typealias HtmlData = Data + +fun HtmlData(meta: Meta, content: TagConsumer<*>.() -> Unit): HtmlData = Data(content, meta) + +val HtmlData.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]" +val HtmlData.language: Language get() = meta["language"]?.enum() ?: Language.RU + +val HtmlData.order: Int? get() = meta["order"]?.int + +fun TagConsumer<*>.htmlData(data: HtmlData) = runBlocking { + data.await().invoke(this@htmlData) +} + +fun FlowContent.htmlData(data: HtmlData) = runBlocking { + data.await().invoke(consumer) +} + diff --git a/src/main/kotlin/ru/mipt/spc/magprog/Person.kt b/src/main/kotlin/ru/mipt/spc/magprog/Person.kt index 27e9bf4..2336fe2 100644 --- a/src/main/kotlin/ru/mipt/spc/magprog/Person.kt +++ b/src/main/kotlin/ru/mipt/spc/magprog/Person.kt @@ -4,7 +4,7 @@ import kotlinx.css.* import kotlinx.html.* import space.kscience.dataforge.meta.string -class Person(val block: HtmlBlock) : HtmlBlock by block { +class Person(val block: HtmlData) : HtmlData by block { val name: String by meta.string { error("Mentor name is not defined") } val photo: String? by meta.string() } @@ -27,7 +27,7 @@ private fun FlowContent.personCards(list: List, prefix: String) { h2 { a(href = "#${prefix}_${mentor.id}") { +mentor.name } } - with(mentor) { content() } + htmlData(mentor.block) } } } diff --git a/src/main/kotlin/ru/mipt/spc/magprog/SiteContext.kt b/src/main/kotlin/ru/mipt/spc/magprog/SiteContext.kt index 61fa06a..3105b35 100644 --- a/src/main/kotlin/ru/mipt/spc/magprog/SiteContext.kt +++ b/src/main/kotlin/ru/mipt/spc/magprog/SiteContext.kt @@ -28,12 +28,12 @@ interface SiteContext: ContextAware { /** * Resolve a Html builder by its full name */ - fun resolveHtml(name: Name): HtmlBlock? + fun resolveHtml(name: Name): HtmlData? /** * Find all Html blocks using given name/meta filter */ - fun resolveAllHtml(filter: (name: Name, meta: Meta) -> Boolean): Map + fun resolveAllHtml(filter: (name: Name, meta: Meta) -> Boolean): Map } @OptIn(DFInternal::class) diff --git a/src/main/kotlin/ru/mipt/spc/magprog/magProgPage.kt b/src/main/kotlin/ru/mipt/spc/magprog/magProgPage.kt index 0a9b12e..5d3fdcb 100644 --- a/src/main/kotlin/ru/mipt/spc/magprog/magProgPage.kt +++ b/src/main/kotlin/ru/mipt/spc/magprog/magProgPage.kt @@ -6,8 +6,10 @@ import kotlinx.html.* import space.kscience.dataforge.data.await import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.getIndexed import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus //fun CssBuilder.magProgCss() { @@ -26,9 +28,9 @@ class MagProgSection( val id: String, val title: String, val style: String, - override val content: FlowContent.() -> Unit, -) : HtmlBlock { - override val meta: Meta + val content: FlowContent.() -> Unit, +) { + val meta: Meta get() = Meta { "id" put id "title" put title @@ -47,13 +49,14 @@ private fun wrapSection( } private fun wrapSection( - block: HtmlBlock, + block: HtmlData, idOverride: String? = null, ): MagProgSection = wrapSection( idOverride ?: block.id, block.meta["section_title"]?.string ?: error("Section without title"), - block.content -) +){ + htmlData(block) +} private val CONTENT_NODE_NAME = Name.EMPTY//"content".asName() private val INTRO_PATH: Name = CONTENT_NODE_NAME + "intro" @@ -68,7 +71,7 @@ context(SiteContext) private fun FlowContent.programSection() { val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!! div("inner") { h2 { +"Учебная программа" } - with(programBlock) { content() } + htmlData(programBlock) button(classes = "fit btn btn-primary btn-lg") { attributes["data-bs-toggle"] = "collapse" attributes["data-bs-target"] = "#recommended-courses-collapse-text" @@ -79,7 +82,7 @@ context(SiteContext) private fun FlowContent.programSection() { div("collapse pt-3") { id = "recommended-courses-collapse-text" div("card card-body") { - with(recommendedBlock) { content() } + htmlData(recommendedBlock) } } } @@ -91,7 +94,7 @@ context(SiteContext) private fun FlowContent.partners() { div("inner") { h2 { +"Партнеры" } div("features") { - partnersData.items.values.forEach { partner -> + partnersData.getIndexed("content".asName()).values.forEach { partner -> section { a(href = partner["link"].string, target = "_blank") { rel = "noreferrer" @@ -205,7 +208,7 @@ context(SiteContext) fun HTML.magProgPage() { id = "footer" div("inner") { ul("menu") { - li { +"""© Untitled. All rights reserved.""" } + li { +"""© SPC. All rights reserved.""" } li { +"""Design:""" a { diff --git a/src/main/kotlin/ru/mipt/spc/magprog/DirectoryDataTree.kt b/src/main/kotlin/space/kscience/snark/DirectoryDataTree.kt similarity index 75% rename from src/main/kotlin/ru/mipt/spc/magprog/DirectoryDataTree.kt rename to src/main/kotlin/space/kscience/snark/DirectoryDataTree.kt index 75d4861..df0a66e 100644 --- a/src/main/kotlin/ru/mipt/spc/magprog/DirectoryDataTree.kt +++ b/src/main/kotlin/space/kscience/snark/DirectoryDataTree.kt @@ -1,9 +1,8 @@ -package ru.mipt.spc.magprog +package space.kscience.snark import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTreeItem -import space.kscience.dataforge.data.StaticData import space.kscience.dataforge.io.IOPlugin import space.kscience.dataforge.io.readEnvelopeFile import space.kscience.dataforge.io.readMetaFile @@ -21,22 +20,6 @@ import kotlin.io.path.nameWithoutExtension import kotlin.reflect.KType import kotlin.reflect.typeOf -//internal object ByteArrayIOFormat : IOFormat { -// -// override val type: KType = typeOf() -// -// override fun writeObject(output: Output, obj: ByteArray) { -// output.writeFully(obj) -// } -// -// override fun readObject(input: Input): ByteArray = input.readBytes() -// -// override fun toMeta(): Meta = Meta { -// IOFormat.NAME_KEY put "ByteArray" -// } -// -//} - class DirectoryDataTree(val io: IOPlugin, val path: Path) : DataTree { override val dataType: KType @@ -51,7 +34,9 @@ class DirectoryDataTree(val io: IOPlugin, val path: Path) : DataTree META_FILE_EXTENSION_KEY put filePath.extension //TODO add other file information } - return StaticData(typeOf(), envelope.data?.toByteArray() ?: ByteArray(0), meta) + return Data(meta){ + envelope.data?.toByteArray() ?: ByteArray(0) + } } diff --git a/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt new file mode 100644 index 0000000..86cca6f --- /dev/null +++ b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt @@ -0,0 +1,59 @@ +package space.kscience.snark + +import io.ktor.http.ContentType +import space.kscience.dataforge.actions.Action +import space.kscience.dataforge.actions.map +import space.kscience.dataforge.context.* +import space.kscience.dataforge.io.yaml.YamlPlugin +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.string +import space.kscience.dataforge.misc.Type +import space.kscience.dataforge.names.Name +import kotlin.reflect.KClass +import kotlin.reflect.KType + +@Type(SnarkParser.TYPE) +interface SnarkParser { + val contentType: ContentType + + val fileExtensions: Set + + val priority: Int + + val resultType: KType + + suspend fun parse(bytes: ByteArray): R + + companion object { + const val TYPE = "snark.parser" + } +} + + +class SnarkPlugin : AbstractPlugin() { + val yaml by require(YamlPlugin) + val io get() = yaml.io + + override val tag: PluginTag get() = Companion.tag + + private val parsers: Map> by lazy { context.gather(SnarkParser.TYPE, true) } + + private val parseAction = Action.map { + result { bytes -> + parsers.values.filter { parser -> + parser.contentType.toString() == meta["contentType"].string || + meta[DirectoryDataTree.META_FILE_EXTENSION_KEY].string in parser.fileExtensions + }.maxByOrNull { + it.priority + }?.parse(bytes) ?: bytes + } + } + + companion object : PluginFactory { + override val tag: PluginTag = PluginTag("snark") + override val type: KClass = SnarkPlugin::class + + override fun build(context: Context, meta: Meta): SnarkPlugin = SnarkPlugin() + } +} \ No newline at end of file