From 600a9b5529ad4950a9604ab6de76054f585b2030 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 21 Jun 2022 10:52:24 +0300 Subject: [PATCH] Refactor snark part --- src/main/kotlin/html5up/forty/common.kt | 4 +- src/main/kotlin/html5up/forty/landing.kt | 4 +- src/main/kotlin/html5up/forty/page.kt | 4 +- src/main/kotlin/ru/mipt/spc/master.kt | 18 ++-- src/main/kotlin/ru/mipt/spc/spcCollection.kt | 8 +- src/main/kotlin/ru/mipt/spc/spcHome.kt | 16 +-- src/main/kotlin/ru/mipt/spc/spcMisc.kt | 10 +- .../space/kscience/snark/SiteBuilder.kt | 79 +++++++++++++++ .../snark/{PageContext.kt => SiteContext.kt} | 51 +++++----- .../kotlin/space/kscience/snark/SiteLayout.kt | 99 +++++++++++++++++++ .../space/kscience/snark/SnarkPlugin.kt | 4 +- .../kotlin/space/kscience/snark/SnarkRoute.kt | 64 ------------ src/main/kotlin/space/kscience/snark/pages.kt | 95 ------------------ 13 files changed, 240 insertions(+), 216 deletions(-) create mode 100644 src/main/kotlin/space/kscience/snark/SiteBuilder.kt rename src/main/kotlin/space/kscience/snark/{PageContext.kt => SiteContext.kt} (60%) create mode 100644 src/main/kotlin/space/kscience/snark/SiteLayout.kt delete mode 100644 src/main/kotlin/space/kscience/snark/SnarkRoute.kt delete mode 100644 src/main/kotlin/space/kscience/snark/pages.kt diff --git a/src/main/kotlin/html5up/forty/common.kt b/src/main/kotlin/html5up/forty/common.kt index 867960a..df8acde 100644 --- a/src/main/kotlin/html5up/forty/common.kt +++ b/src/main/kotlin/html5up/forty/common.kt @@ -1,7 +1,7 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.PageContext +import space.kscience.snark.SiteContext import space.kscience.snark.resolveRef @@ -201,7 +201,7 @@ internal fun FlowContent.fortyFooter() { } } -context(PageContext) internal fun BODY.fortyScripts() { +context(SiteContext) internal fun BODY.fortyScripts() { script { src = resolveRef("assets/js/jquery.min.js") } diff --git a/src/main/kotlin/html5up/forty/landing.kt b/src/main/kotlin/html5up/forty/landing.kt index a543424..b18919e 100644 --- a/src/main/kotlin/html5up/forty/landing.kt +++ b/src/main/kotlin/html5up/forty/landing.kt @@ -1,9 +1,9 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.PageContext +import space.kscience.snark.SiteContext -context(PageContext) internal fun HTML.landing(){ +context(SiteContext) internal fun HTML.landing(){ head { title { } diff --git a/src/main/kotlin/html5up/forty/page.kt b/src/main/kotlin/html5up/forty/page.kt index e6d7e2c..c933dda 100644 --- a/src/main/kotlin/html5up/forty/page.kt +++ b/src/main/kotlin/html5up/forty/page.kt @@ -1,10 +1,10 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.PageContext +import space.kscience.snark.SiteContext import space.kscience.snark.resolveRef -context(PageContext) internal fun HTML.fortyPage(){ +context(SiteContext) internal fun HTML.fortyPage(){ head { title { } diff --git a/src/main/kotlin/ru/mipt/spc/master.kt b/src/main/kotlin/ru/mipt/spc/master.kt index af51e06..3347957 100644 --- a/src/main/kotlin/ru/mipt/spc/master.kt +++ b/src/main/kotlin/ru/mipt/spc/master.kt @@ -80,7 +80,7 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program" private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses" private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners" -context(PageContext) private fun FlowContent.programSection() { +context(SiteContext) private fun FlowContent.programSection() { val programBlock = resolveHtml(PROGRAM_PATH)!! val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!! div("inner") { @@ -97,7 +97,7 @@ context(PageContext) private fun FlowContent.programSection() { } } -context(PageContext) private fun FlowContent.partners() { +context(SiteContext) private fun FlowContent.partners() { //val partnersData: Meta = resolve(PARTNERS_PATH)?.meta ?: Meta.EMPTY val partnersData: Meta = runBlocking { data.getByType(PARTNERS_PATH)?.await() } ?: Meta.EMPTY div("inner") { @@ -127,7 +127,7 @@ context(PageContext) private fun FlowContent.partners() { // val photo: String? by meta.string() //} -context(PageContext) private fun FlowContent.team() { +context(SiteContext) private fun FlowContent.team() { val team = findByType("magprog_team").values.sortedBy { it.order } div("inner") { @@ -182,7 +182,7 @@ context(PageContext) private fun FlowContent.team() { // } } -context(PageContext) private fun FlowContent.mentors() { +context(SiteContext) private fun FlowContent.mentors() { val mentors = findByType("magprog_mentor").entries.sortedBy { it.value.id } div("inner") { @@ -219,12 +219,12 @@ context(PageContext) private fun FlowContent.mentors() { } } -context(PageContext) internal fun FlowContent.contacts() { +context(SiteContext) internal fun FlowContent.contacts() { } -context(PageContext) internal fun HTML.magProgHead(title: String) { +context(SiteContext) internal fun HTML.magProgHead(title: String) { head { this.title = title meta { @@ -251,7 +251,7 @@ context(PageContext) internal fun HTML.magProgHead(title: String) { } } -context(PageContext) internal fun BODY.magProgFooter() { +context(SiteContext) internal fun BODY.magProgFooter() { footer("wrapper style1-alt") { id = "footer" div("inner") { @@ -296,11 +296,11 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str val snark = context.fetch(SnarkPlugin) - val magProgPageContext: PageContext = snark.read(dataPath.resolve("content"), prefix) + val magProgSiteContext: SiteContext = snark.read(dataPath.resolve("content"), prefix) routing { route(prefix) { - with(magProgPageContext) { + with(magProgSiteContext) { static { files(dataPath.resolve("assets").toFile()) diff --git a/src/main/kotlin/ru/mipt/spc/spcCollection.kt b/src/main/kotlin/ru/mipt/spc/spcCollection.kt index 168e01c..352bc34 100644 --- a/src/main/kotlin/ru/mipt/spc/spcCollection.kt +++ b/src/main/kotlin/ru/mipt/spc/spcCollection.kt @@ -11,15 +11,11 @@ import space.kscience.dataforge.names.parseAsName import space.kscience.dataforge.names.withIndex import space.kscience.dataforge.values.string import space.kscience.snark.* -import kotlin.collections.Map import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.forEach -import kotlin.collections.joinToString import kotlin.collections.set -import kotlin.collections.sortedBy -context(PageContext) private fun FlowContent.spcSpotlightContent( +context(SiteContext) private fun FlowContent.spcSpotlightContent( landing: HtmlData, content: Map, ) { @@ -92,7 +88,7 @@ context(PageContext) private fun FlowContent.spcSpotlightContent( } -context(PageContext) internal fun SnarkRoute.spcSpotlight( +context(SiteContext) internal fun SiteBuilder.spcSpotlight( name: String, contentFilter: (Name, Meta) -> Boolean, ) { diff --git a/src/main/kotlin/ru/mipt/spc/spcHome.kt b/src/main/kotlin/ru/mipt/spc/spcHome.kt index 4dadaa1..f5f0e51 100644 --- a/src/main/kotlin/ru/mipt/spc/spcHome.kt +++ b/src/main/kotlin/ru/mipt/spc/spcHome.kt @@ -20,7 +20,7 @@ import space.kscience.snark.* import java.nio.file.Path -context(PageContext) internal fun HTML.spcPageContent( +context(SiteContext) internal fun HTML.spcPageContent( meta: Meta, title: String = meta["title"].string ?: SPC_TITLE, fragment: FlowContent.() -> Unit, @@ -58,13 +58,13 @@ context(PageContext) internal fun HTML.spcPageContent( } -context(PageContext) internal fun SnarkRoute.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) { +context(SiteContext) internal fun SiteBuilder.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) { page(subRoute) { spcPageContent(meta, fragment = fragment) } } -context(PageContext) internal fun SnarkRoute.spcPage( +context(SiteContext) internal fun SiteBuilder.spcPage( subRoute: String, dataPath: Name = subRoute.replace("/", ".").parseAsName(), more: FlowContent.() -> Unit = {}, @@ -83,12 +83,12 @@ context(PageContext) internal fun SnarkRoute.spcPage( /** * Route a directory */ -context(PageContext) internal fun SnarkRoute.spcDirectory( +context(SiteContext) internal fun SiteBuilder.spcDirectory( subRoute: String, dataPath: Name = subRoute.replace("/", ".").parseAsName(), ) { data.filterByType { name, _ -> name.startsWith(dataPath) }.forEach { html -> - val pageName = if (html.name.lastOrNull()?.body == PageContext.INDEX_PAGE_NAME) { + val pageName = if (html.name.lastOrNull()?.body == SiteContext.INDEX_PAGE_NAME) { html.name.cutLast() } else { html.name @@ -100,14 +100,14 @@ context(PageContext) internal fun SnarkRoute.spcDirectory( } } -context(PageContext) internal fun SnarkRoute.spcPage( +context(SiteContext) internal fun SiteBuilder.spcPage( name: Name, more: FlowContent.() -> Unit = {}, ) { spcPage(name.tokens.joinToString("/"), name, more) } -context(PageContext, HTML) private fun HTML.spcHome() { +context(SiteContext, HTML) private fun HTML.spcHome() { spcHead() body("is-preload") { wrapper { @@ -302,7 +302,7 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin routing { route(prefix) { - snark(homePageContext) { + snarkSite(homePageContext) { staticDirectory("assets", rootPath.resolve("assets")) staticDirectory("images", rootPath.resolve("images")) diff --git a/src/main/kotlin/ru/mipt/spc/spcMisc.kt b/src/main/kotlin/ru/mipt/spc/spcMisc.kt index fa49e00..b6725f3 100644 --- a/src/main/kotlin/ru/mipt/spc/spcMisc.kt +++ b/src/main/kotlin/ru/mipt/spc/spcMisc.kt @@ -1,14 +1,14 @@ package ru.mipt.spc import kotlinx.html.* -import space.kscience.snark.PageContext +import space.kscience.snark.SiteContext import space.kscience.snark.homeRef import space.kscience.snark.resolveRef internal const val SPC_TITLE = "Scientific Programming Centre" -context(PageContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { +context(SiteContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { head { title { +title @@ -27,7 +27,7 @@ context(PageContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { } } -context(PageContext) internal fun FlowContent.spcHomeMenu() { +context(SiteContext) internal fun FlowContent.spcHomeMenu() { nav { id = "menu" ul("links") { @@ -79,7 +79,7 @@ context(PageContext) internal fun FlowContent.spcHomeMenu() { } } -context(PageContext) internal fun FlowContent.spcFooter() { +context(SiteContext) internal fun FlowContent.spcFooter() { footer { id = "footer" div("inner") { @@ -129,7 +129,7 @@ context(PageContext) internal fun FlowContent.spcFooter() { } } -context(PageContext) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { +context(SiteContext) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { div { id = "wrapper" // Header diff --git a/src/main/kotlin/space/kscience/snark/SiteBuilder.kt b/src/main/kotlin/space/kscience/snark/SiteBuilder.kt new file mode 100644 index 0000000..3733bdf --- /dev/null +++ b/src/main/kotlin/space/kscience/snark/SiteBuilder.kt @@ -0,0 +1,79 @@ +package space.kscience.snark + +import io.ktor.server.application.call +import io.ktor.server.html.respondHtml +import io.ktor.server.http.content.* +import io.ktor.server.routing.Route +import io.ktor.server.routing.createRouteFromPath +import io.ktor.server.routing.get +import kotlinx.html.HTML +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.ContextAware +import java.nio.file.Path + +/** + * An abstraction, which is used to render sites to the different rendering engines + */ +interface SiteBuilder : ContextAware { + + val siteContext: SiteContext + + override val context: Context get() = siteContext.context + + fun staticFile(remotePath: String, file: Path) + + fun staticDirectory(remotePath: String, directory: Path) + + fun staticResourceFile(remotePath: String, resourcesPath: String) + + fun staticResourceDirectory(resourcesPath: String) + + fun page(route: String = "", content: context(SiteContext, HTML) () -> Unit) + + /** + * Create a route + */ + fun route(subRoute: String): SiteBuilder +} + +public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit) { + route(route).apply(block) +} + +class KtorSiteRoute(override val siteContext: SiteContext, private val ktorRoute: Route) : SiteBuilder { + override fun staticFile(remotePath: String, file: Path) { + ktorRoute.file(remotePath, file.toFile()) + } + + override fun staticDirectory(remotePath: String, directory: Path) { + ktorRoute.static(remotePath) { + files(directory.toFile()) + } + } + + override fun page(route: String, content: context(SiteContext, HTML)() -> Unit) { + ktorRoute.get(route) { + call.respondHtml { + content(siteContext.copyWithRequestHost(call.request), this) + } + } + } + + override fun route(subRoute: String): SiteBuilder = + KtorSiteRoute(siteContext, ktorRoute.createRouteFromPath(subRoute)) + + override fun staticResourceFile(remotePath: String, resourcesPath: String) { + ktorRoute.resource(resourcesPath, resourcesPath) + } + + override fun staticResourceDirectory(resourcesPath: String) { + ktorRoute.resources(resourcesPath) + } +} + +inline fun Route.snarkSite( + siteContext: SiteContext, + block: context(SiteContext, SiteBuilder)() -> Unit, +) { + block(siteContext, KtorSiteRoute(siteContext, this@snarkSite)) +} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/PageContext.kt b/src/main/kotlin/space/kscience/snark/SiteContext.kt similarity index 60% rename from src/main/kotlin/space/kscience/snark/PageContext.kt rename to src/main/kotlin/space/kscience/snark/SiteContext.kt index 14cdb6d..b125140 100644 --- a/src/main/kotlin/space/kscience/snark/PageContext.kt +++ b/src/main/kotlin/space/kscience/snark/SiteContext.kt @@ -15,17 +15,19 @@ import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.startsWith -import space.kscience.snark.PageContext.Companion.INDEX_PAGE_NAME +import space.kscience.snark.SiteContext.Companion.INDEX_PAGE_NAME import java.nio.file.Path -data class PageContext( - override val context: Context, +data class SiteContext( + val snark: SnarkPlugin, val path: String, - val pageMeta: Meta, - val data: DataSet<*>, + val meta: Meta, + val data: DataTree<*>, ) : ContextAware { - val language: String? by pageMeta.string() + override val context: Context get() = snark.context + + val language: String? by meta.string() companion object { const val INDEX_PAGE_NAME: String = "index" @@ -35,14 +37,14 @@ data class PageContext( /** * Resolve a resource full path by its name */ -fun PageContext.resolveRef(name: String): String = "${path.removeSuffix("/")}/$name" +fun SiteContext.resolveRef(name: String): String = "${path.removeSuffix("/")}/$name" -fun PageContext.resolveRef(name: Name): String = "${path.removeSuffix("/")}/${name.tokens.joinToString("/")}" +fun SiteContext.resolveRef(name: Name): String = "${path.removeSuffix("/")}/${name.tokens.joinToString("/")}" /** * Resolve a Html builder by its full name */ -fun PageContext.resolveHtml(name: Name): HtmlData? { +fun SiteContext.resolveHtml(name: Name): HtmlData? { val resolved = (data.getByType(name) ?: data.getByType(name + INDEX_PAGE_NAME)) return resolved?.takeIf { @@ -53,40 +55,45 @@ fun PageContext.resolveHtml(name: Name): HtmlData? { /** * Find all Html blocks using given name/meta filter */ -fun PageContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map = +fun SiteContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map = data.filterByType { name, meta -> predicate(name, meta) && meta["published"].string != "false" //TODO add language confirmation }.asSequence().associate { it.name to it.data } -val PageContext.homeRef get() = resolveRef("").removeSuffix("/") +val SiteContext.homeRef get() = resolveRef("").removeSuffix("/") -fun PageContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta -> +fun SiteContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta -> name.startsWith(baseName) && meta["content_type"].string == contentType } internal val Data<*>.published: Boolean get() = meta["published"].string != "false" -fun PageContext(dfContext: Context, rootUrl: String, data: DataSet<*>): PageContext = - PageContext(dfContext, rootUrl, data.meta, data) +fun SnarkPlugin.siteContext(rootUrl: String, data: DataTree<*>): SiteContext = + SiteContext(this, rootUrl, data.meta, data) -fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): PageContext { - val parsedData: DataSet = readDirectory(path) +fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): SiteContext { + val parsedData: DataTree = readDirectory(path) - return PageContext(context, rootUrl, parsedData) + return siteContext(rootUrl, parsedData) } -/** - * Substitute uri in [PageContext] with uri in the call to properly resolve relative refs. Only host properties are substituted. - */ -context(PageContext) inline fun withRequest(request: ApplicationRequest, block: context(PageContext) () -> Unit) { +@PublishedApi +internal fun SiteContext.copyWithRequestHost(request: ApplicationRequest): SiteContext { val uri = URLBuilder( protocol = URLProtocol.createOrDefault(request.origin.scheme), host = request.host(), port = request.port(), pathSegments = path.split("/"), ) - block(copy(path = uri.buildString())) + return copy(path = uri.buildString()) +} + +/** + * Substitute uri in [SiteContext] with uri in the call to properly resolve relative refs. Only host properties are substituted. + */ +context(SiteContext) inline fun withRequest(request: ApplicationRequest, block: context(SiteContext) () -> Unit) { + block(copyWithRequestHost(request)) } \ 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 new file mode 100644 index 0000000..b638c4a --- /dev/null +++ b/src/main/kotlin/space/kscience/snark/SiteLayout.kt @@ -0,0 +1,99 @@ +package space.kscience.snark + +import kotlinx.coroutines.runBlocking +import space.kscience.dataforge.data.DataTree +import space.kscience.dataforge.data.DataTreeItem +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 +import space.kscience.snark.SiteLayout.Companion.DESIGNATION_KEY +import space.kscience.snark.SiteLayout.Companion.LAYOUT_KEY +import java.nio.file.Path +import kotlin.reflect.typeOf + +internal fun SiteBuilder.staticFrom(rootMeta: Meta) { + rootMeta.getIndexed("resource".asName()).forEach { (_, meta) -> + + val path by meta.string() + val remotePath by meta.string() + + path?.let { resourcePath -> + //If remote path provided, use a single resource + remotePath?.let { + staticResourceFile(it, resourcePath) + return@forEach + } + + + //otherwise use package resources + staticResourceDirectory(resourcePath) + } + } + + rootMeta.getIndexed("file".asName()).forEach { (_, meta) -> + val remotePath by meta.string { error("File remote path is not provided") } + val path by meta.string { error("File path is not provided") } + staticFile(remotePath, Path.of(path)) + } + + rootMeta.getIndexed("directory".asName()).forEach { (_, meta) -> + val path by meta.string { error("Directory path is not provided") } + staticDirectory("", Path.of(path)) + } +} + +/** + * Represent pages in a [DataTree] + */ +fun SiteBuilder.data(data: DataTreeItem<*>, prefix: Name = Name.EMPTY) { + val layoutMeta = data.meta[LAYOUT_KEY] + if (layoutMeta != null) { + //use layout if it is defined + siteContext.snark.layout(layoutMeta).render(data) + } else { + when (data) { + is DataTreeItem.Node -> { + data.tree.items.forEach { (token, item) -> + data(item, prefix + token) + } + } + is DataTreeItem.Leaf -> { + val item = data.data + if (item.type == typeOf() && item.meta[DESIGNATION_KEY].string == "page") { + route(prefix.tokens.joinToString(separator = "/")) { + page { + @Suppress("UNCHECKED_CAST") + val pageFragment: HtmlFragment = runBlocking { item.await() as HtmlFragment } + pageFragment.invoke(consumer) + } + staticFrom(item.meta) + } + } + } + } + + } + //TODO watch for changes +} + + +fun interface SiteLayout { + + context(SiteBuilder) fun render(data: DataTreeItem<*>) + + companion object { + internal const val DESIGNATION_KEY = "designation" + const val LAYOUT_KEY = "layout" + } +} + +object DefaultSiteLayout : SiteLayout { + context(SiteBuilder) override fun render(data: DataTreeItem<*>) { + data(data) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt index 6ad9384..ee5c680 100644 --- a/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt +++ b/src/main/kotlin/space/kscience/snark/SnarkPlugin.kt @@ -69,7 +69,6 @@ class SnarkPlugin : AbstractPlugin() { 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 -> @@ -84,6 +83,9 @@ class SnarkPlugin : AbstractPlugin() { } } + fun layout(meta: Meta): SiteLayout = when(meta[SiteLayout.LAYOUT_KEY]){ + else -> DefaultSiteLayout + } override fun content(target: String): Map = when (target) { SnarkParser.TYPE -> mapOf( diff --git a/src/main/kotlin/space/kscience/snark/SnarkRoute.kt b/src/main/kotlin/space/kscience/snark/SnarkRoute.kt deleted file mode 100644 index 6998c88..0000000 --- a/src/main/kotlin/space/kscience/snark/SnarkRoute.kt +++ /dev/null @@ -1,64 +0,0 @@ -package space.kscience.snark - -import io.ktor.server.application.call -import io.ktor.server.html.respondHtml -import io.ktor.server.http.content.file -import io.ktor.server.http.content.files -import io.ktor.server.http.content.static -import io.ktor.server.routing.Route -import io.ktor.server.routing.get -import io.ktor.server.routing.route -import kotlinx.html.HTML -import java.nio.file.Path - -/** - * An abstraction, which is used to render sites to the different rendering engines - */ -interface SnarkRoute { - - fun staticFile(remotePath: String, file: Path) - - fun staticDirectory(remotePath: String, directory: Path) - - context(PageContext) fun page(route: String = "", content: context(PageContext, HTML) () -> Unit) - - context(PageContext) fun route(route: String, block: context(PageContext, SnarkRoute) () -> Unit) -} - -class KtorRouteBuilder(private val ktorRoute: Route) : SnarkRoute { - override fun staticFile(remotePath: String, file: Path) { - ktorRoute.file(remotePath, file.toFile()) - } - - override fun staticDirectory(remotePath: String, directory: Path) { - ktorRoute.static(remotePath) { - files(directory.toFile()) - } - } - - context(PageContext) override fun page(route: String, content: context(PageContext, HTML)() -> Unit) { - ktorRoute.get(route) { - withRequest(call.request) { - call.respondHtml { - content(this@PageContext, this) - } - } - } - } - - context(PageContext) override fun route( - route: String, - block: context(PageContext, SnarkRoute)() -> Unit, - ) { - ktorRoute.route(route) { - block(this@PageContext, KtorRouteBuilder(this)) - } - } -} - -inline fun Route.snark( - pageContext: PageContext, - block: context(PageContext, SnarkRoute)() -> Unit, -) { - block(pageContext, KtorRouteBuilder(this@snark)) -} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/pages.kt b/src/main/kotlin/space/kscience/snark/pages.kt deleted file mode 100644 index 529ac90..0000000 --- a/src/main/kotlin/space/kscience/snark/pages.kt +++ /dev/null @@ -1,95 +0,0 @@ -package space.kscience.snark - -import io.ktor.server.application.call -import io.ktor.server.html.respondHtml -import io.ktor.server.routing.get -import space.kscience.dataforge.data.DataTree -import space.kscience.dataforge.data.DataTreeItem -import space.kscience.dataforge.data.await -import space.kscience.dataforge.data.type -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 -import java.nio.file.Path -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.reflect.typeOf - -internal const val DESIGNATION_KEY = "designation" - -private fun SnarkRoute.staticFrom(rootMeta: Meta) { -// rootMeta.getIndexed("resource".asName()).forEach { (_, meta) -> -// val resourcePackage by meta.string() -// val remotePath by meta.string() -// val resourceName by meta.string() -// -// //If remote path provided, use a single resource -// remotePath?.let { -// resource(it, resourceName ?: it, resourcePackage) -// return@forEach -// } -// -// //otherwise use package resources -// resources(resourcePackage) -// } - - rootMeta.getIndexed("file".asName()).forEach { (_, meta) -> - val remotePath by meta.string { error("File remote path is not provided") } - val path by meta.string { error("File path is not provided") } - staticFile(remotePath, Path.of(path)) - } - - rootMeta.getIndexed("directory".asName()).forEach { (_, meta) -> - val path by meta.string { error("Directory path is not provided") } - staticDirectory("", Path.of(path)) - } -} - -/** - * Represent pages in a [DataTree] - */ -context(PageContext) fun SnarkRoute.pagesFrom(prefix: Name, data: DataTree<*>) { - if (data.meta[DESIGNATION_KEY].string == "page") { - TODO("Implement node-based pages") -// route(prefix.tokens.joinToString(separator = "/")) { -// get { -// val headFragment = data.getByType("head")?.await() -// call.respondHtml { -// head { -// headFragment?.invoke(consumer) -// data.meta["title"].string?.let { title(it) } -// } -// body { -// data.filterByType { name, meta -> -// name.first().body == "section" && meta["published"].boolean != false -// }.traverse().sortedBy { } -// } -// } -// } -// staticFrom(data.meta) -// } - } else { - data.items.forEach { (token, item) -> - when (item) { - is DataTreeItem.Node -> pagesFrom(prefix + token, item.tree) - is DataTreeItem.Leaf -> if (item.type == typeOf() && item.meta[DESIGNATION_KEY].string == "page") { - route(prefix.tokens.joinToString(separator = "/")) { - get { - @Suppress("UNCHECKED_CAST") - val pageFragment: HtmlFragment = item.data.await() as HtmlFragment - call.respondHtml { - pageFragment.invoke(consumer) - } - } - staticFrom(item.meta) - } - } - } - } - } - //TODO watch for changes -}