forked from SPC/spc-site
Refactor snark part
This commit is contained in:
parent
285057fbb0
commit
600a9b5529
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
@ -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<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
|
||||
val partnersData: Meta = runBlocking { data.getByType<Meta>(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())
|
||||
|
||||
|
@ -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<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,
|
||||
contentFilter: (Name, Meta) -> Boolean,
|
||||
) {
|
||||
|
@ -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<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()
|
||||
} 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"))
|
||||
|
||||
|
@ -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
|
||||
|
79
src/main/kotlin/space/kscience/snark/SiteBuilder.kt
Normal file
79
src/main/kotlin/space/kscience/snark/SiteBuilder.kt
Normal 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))
|
||||
}
|
@ -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<HtmlFragment>(name) ?: data.getByType<HtmlFragment>(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<Name, HtmlData> =
|
||||
fun SiteContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
|
||||
data.filterByType<HtmlFragment> { 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<Any> = readDirectory(path)
|
||||
fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): SiteContext {
|
||||
val parsedData: DataTree<Any> = 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))
|
||||
}
|
99
src/main/kotlin/space/kscience/snark/SiteLayout.kt
Normal file
99
src/main/kotlin/space/kscience/snark/SiteLayout.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -69,7 +69,6 @@ class SnarkPlugin : AbstractPlugin() {
|
||||
context.gather(SnarkParser.TYPE, true)
|
||||
}
|
||||
|
||||
|
||||
fun readDirectory(path: Path): DataTree<Any> = 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<Name, Any> = when (target) {
|
||||
SnarkParser.TYPE -> mapOf(
|
||||
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user