1
0
forked from SPC/spc-site

Refactor snark part

This commit is contained in:
Alexander Nozik 2022-06-21 10:52:24 +03:00
parent 285057fbb0
commit 600a9b5529
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
13 changed files with 240 additions and 216 deletions

View File

@ -1,7 +1,7 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.PageContext import space.kscience.snark.SiteContext
import space.kscience.snark.resolveRef 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 { script {
src = resolveRef("assets/js/jquery.min.js") src = resolveRef("assets/js/jquery.min.js")
} }

View File

@ -1,9 +1,9 @@
package html5up.forty package html5up.forty
import kotlinx.html.* 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 { head {
title { title {
} }

View File

@ -1,10 +1,10 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.PageContext import space.kscience.snark.SiteContext
import space.kscience.snark.resolveRef import space.kscience.snark.resolveRef
context(PageContext) internal fun HTML.fortyPage(){ context(SiteContext) internal fun HTML.fortyPage(){
head { head {
title { title {
} }

View File

@ -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 RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses"
private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners" 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 programBlock = resolveHtml(PROGRAM_PATH)!!
val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!! val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!!
div("inner") { 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<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY //val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY
div("inner") { div("inner") {
@ -127,7 +127,7 @@ context(PageContext) private fun FlowContent.partners() {
// val photo: String? by meta.string() // 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 } val team = findByType("magprog_team").values.sortedBy { it.order }
div("inner") { 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 } val mentors = findByType("magprog_mentor").entries.sortedBy { it.value.id }
div("inner") { 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 { head {
this.title = title this.title = title
meta { 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") { footer("wrapper style1-alt") {
id = "footer" id = "footer"
div("inner") { div("inner") {
@ -296,11 +296,11 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
val snark = context.fetch(SnarkPlugin) val snark = context.fetch(SnarkPlugin)
val magProgPageContext: PageContext = snark.read(dataPath.resolve("content"), prefix) val magProgSiteContext: SiteContext = snark.read(dataPath.resolve("content"), prefix)
routing { routing {
route(prefix) { route(prefix) {
with(magProgPageContext) { with(magProgSiteContext) {
static { static {
files(dataPath.resolve("assets").toFile()) files(dataPath.resolve("assets").toFile())

View File

@ -11,15 +11,11 @@ import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.withIndex import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.string import space.kscience.dataforge.values.string
import space.kscience.snark.* import space.kscience.snark.*
import kotlin.collections.Map
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.forEach
import kotlin.collections.joinToString
import kotlin.collections.set import kotlin.collections.set
import kotlin.collections.sortedBy
context(PageContext) private fun FlowContent.spcSpotlightContent( context(SiteContext) private fun FlowContent.spcSpotlightContent(
landing: HtmlData, landing: HtmlData,
content: Map<Name, HtmlData>, content: Map<Name, HtmlData>,
) { ) {
@ -92,7 +88,7 @@ context(PageContext) private fun FlowContent.spcSpotlightContent(
} }
context(PageContext) internal fun SnarkRoute.spcSpotlight( context(SiteContext) internal fun SiteBuilder.spcSpotlight(
name: String, name: String,
contentFilter: (Name, Meta) -> Boolean, contentFilter: (Name, Meta) -> Boolean,
) { ) {

View File

@ -20,7 +20,7 @@ import space.kscience.snark.*
import java.nio.file.Path import java.nio.file.Path
context(PageContext) internal fun HTML.spcPageContent( context(SiteContext) internal fun HTML.spcPageContent(
meta: Meta, meta: Meta,
title: String = meta["title"].string ?: SPC_TITLE, title: String = meta["title"].string ?: SPC_TITLE,
fragment: FlowContent.() -> Unit, 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) { page(subRoute) {
spcPageContent(meta, fragment = fragment) spcPageContent(meta, fragment = fragment)
} }
} }
context(PageContext) internal fun SnarkRoute.spcPage( context(SiteContext) internal fun SiteBuilder.spcPage(
subRoute: String, subRoute: String,
dataPath: Name = subRoute.replace("/", ".").parseAsName(), dataPath: Name = subRoute.replace("/", ".").parseAsName(),
more: FlowContent.() -> Unit = {}, more: FlowContent.() -> Unit = {},
@ -83,12 +83,12 @@ context(PageContext) internal fun SnarkRoute.spcPage(
/** /**
* Route a directory * Route a directory
*/ */
context(PageContext) internal fun SnarkRoute.spcDirectory( context(SiteContext) internal fun SiteBuilder.spcDirectory(
subRoute: String, subRoute: String,
dataPath: Name = subRoute.replace("/", ".").parseAsName(), dataPath: Name = subRoute.replace("/", ".").parseAsName(),
) { ) {
data.filterByType<HtmlFragment> { name, _ -> name.startsWith(dataPath) }.forEach { html -> data.filterByType<HtmlFragment> { 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() html.name.cutLast()
} else { } else {
html.name 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, name: Name,
more: FlowContent.() -> Unit = {}, more: FlowContent.() -> Unit = {},
) { ) {
spcPage(name.tokens.joinToString("/"), name, more) spcPage(name.tokens.joinToString("/"), name, more)
} }
context(PageContext, HTML) private fun HTML.spcHome() { context(SiteContext, HTML) private fun HTML.spcHome() {
spcHead() spcHead()
body("is-preload") { body("is-preload") {
wrapper { wrapper {
@ -302,7 +302,7 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin
routing { routing {
route(prefix) { route(prefix) {
snark(homePageContext) { snarkSite(homePageContext) {
staticDirectory("assets", rootPath.resolve("assets")) staticDirectory("assets", rootPath.resolve("assets"))
staticDirectory("images", rootPath.resolve("images")) staticDirectory("images", rootPath.resolve("images"))

View File

@ -1,14 +1,14 @@
package ru.mipt.spc package ru.mipt.spc
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.PageContext import space.kscience.snark.SiteContext
import space.kscience.snark.homeRef import space.kscience.snark.homeRef
import space.kscience.snark.resolveRef import space.kscience.snark.resolveRef
internal const val SPC_TITLE = "Scientific Programming Centre" 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 { head {
title { title {
+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 { nav {
id = "menu" id = "menu"
ul("links") { 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 { footer {
id = "footer" id = "footer"
div("inner") { 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 { div {
id = "wrapper" id = "wrapper"
// Header // Header

View File

@ -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))
}

View File

@ -15,17 +15,19 @@ import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith 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 import java.nio.file.Path
data class PageContext( data class SiteContext(
override val context: Context, val snark: SnarkPlugin,
val path: String, val path: String,
val pageMeta: Meta, val meta: Meta,
val data: DataSet<*>, val data: DataTree<*>,
) : ContextAware { ) : ContextAware {
val language: String? by pageMeta.string() override val context: Context get() = snark.context
val language: String? by meta.string()
companion object { companion object {
const val INDEX_PAGE_NAME: String = "index" const val INDEX_PAGE_NAME: String = "index"
@ -35,14 +37,14 @@ data class PageContext(
/** /**
* Resolve a resource full path by its name * 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 * Resolve a Html builder by its full name
*/ */
fun PageContext.resolveHtml(name: Name): HtmlData? { fun SiteContext.resolveHtml(name: Name): HtmlData? {
val resolved = (data.getByType<HtmlFragment>(name) ?: data.getByType<HtmlFragment>(name + INDEX_PAGE_NAME)) val resolved = (data.getByType<HtmlFragment>(name) ?: data.getByType<HtmlFragment>(name + INDEX_PAGE_NAME))
return resolved?.takeIf { return resolved?.takeIf {
@ -53,40 +55,45 @@ fun PageContext.resolveHtml(name: Name): HtmlData? {
/** /**
* Find all Html blocks using given name/meta filter * Find all Html blocks using given name/meta filter
*/ */
fun PageContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> = fun SiteContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
data.filterByType<HtmlFragment> { name, meta -> data.filterByType<HtmlFragment> { name, meta ->
predicate(name, meta) predicate(name, meta)
&& meta["published"].string != "false" && meta["published"].string != "false"
//TODO add language confirmation //TODO add language confirmation
}.asSequence().associate { it.name to it.data } }.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 name.startsWith(baseName) && meta["content_type"].string == contentType
} }
internal val Data<*>.published: Boolean get() = meta["published"].string != "false" internal val Data<*>.published: Boolean get() = meta["published"].string != "false"
fun PageContext(dfContext: Context, rootUrl: String, data: DataSet<*>): PageContext = fun SnarkPlugin.siteContext(rootUrl: String, data: DataTree<*>): SiteContext =
PageContext(dfContext, rootUrl, data.meta, data) SiteContext(this, rootUrl, data.meta, data)
fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): PageContext { fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): SiteContext {
val parsedData: DataSet<Any> = readDirectory(path) val parsedData: DataTree<Any> = readDirectory(path)
return PageContext(context, rootUrl, parsedData) return siteContext(rootUrl, parsedData)
} }
/** @PublishedApi
* Substitute uri in [PageContext] with uri in the call to properly resolve relative refs. Only host properties are substituted. internal fun SiteContext.copyWithRequestHost(request: ApplicationRequest): SiteContext {
*/
context(PageContext) inline fun withRequest(request: ApplicationRequest, block: context(PageContext) () -> Unit) {
val uri = URLBuilder( val uri = URLBuilder(
protocol = URLProtocol.createOrDefault(request.origin.scheme), protocol = URLProtocol.createOrDefault(request.origin.scheme),
host = request.host(), host = request.host(),
port = request.port(), port = request.port(),
pathSegments = path.split("/"), 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))
} }

View File

@ -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<HtmlData>() && 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)
}
}

View File

@ -69,7 +69,6 @@ class SnarkPlugin : AbstractPlugin() {
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<*>? = 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<Name, Any> = when (target) { override fun content(target: String): Map<Name, Any> = when (target) {
SnarkParser.TYPE -> mapOf( SnarkParser.TYPE -> mapOf(

View File

@ -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))
}

View File

@ -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<HtmlFragment>("head")?.await()
// call.respondHtml {
// head {
// headFragment?.invoke(consumer)
// data.meta["title"].string?.let { title(it) }
// }
// body {
// data.filterByType<HtmlFragment> { 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<HtmlData>() && 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
}