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