No compile-time errors

This commit is contained in:
Alexander Nozik 2024-01-01 21:30:17 +03:00
parent 738f41265f
commit c0f869f6e3
14 changed files with 211 additions and 168 deletions

View File

@ -14,7 +14,7 @@ allprojects {
} }
} }
val dataforgeVersion by extra("0.7.0") val dataforgeVersion by extra("0.7.1")
ksciencePublish { ksciencePublish {
pom("https://github.com/SciProgCentre/snark") { pom("https://github.com/SciProgCentre/snark") {

View File

@ -2,12 +2,12 @@ package space.kscience.snark
import space.kscience.dataforge.io.IOReader import space.kscience.dataforge.io.IOReader
import space.kscience.dataforge.io.asBinary import space.kscience.dataforge.io.asBinary
import space.kscience.dataforge.misc.DfId import space.kscience.dataforge.misc.DfType
import space.kscience.snark.SnarkReader.Companion.DEFAULT_PRIORITY import space.kscience.snark.SnarkReader.Companion.DEFAULT_PRIORITY
import space.kscience.snark.SnarkReader.Companion.DF_TYPE import space.kscience.snark.SnarkReader.Companion.DF_TYPE
@DfId(DF_TYPE) @DfType(DF_TYPE)
public interface SnarkReader<out T>: IOReader<T> { public interface SnarkReader<out T> : IOReader<T> {
public val types: Set<String> public val types: Set<String>
public val priority: Int get() = DEFAULT_PRIORITY public val priority: Int get() = DEFAULT_PRIORITY
public fun readFrom(source: String): T public fun readFrom(source: String): T
@ -39,5 +39,5 @@ private class SnarkReaderWrapper<out T>(
public fun <T : Any> SnarkReader( public fun <T : Any> SnarkReader(
reader: IOReader<T>, reader: IOReader<T>,
vararg types: String, vararg types: String,
priority: Int = DEFAULT_PRIORITY priority: Int = DEFAULT_PRIORITY,
): SnarkReader<T> = SnarkReaderWrapper(reader, types.toSet(), priority) ): SnarkReader<T> = SnarkReaderWrapper(reader, types.toSet(), priority)

View File

@ -1,12 +1,12 @@
package space.kscience.snark package space.kscience.snark
import space.kscience.dataforge.misc.DfId import space.kscience.dataforge.misc.DfType
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
/** /**
* An object that conducts page-based text transformation. Like using link replacement or templating. * An object that conducts page-based text transformation. Like using link replacement or templating.
*/ */
@DfId(TextProcessor.DF_TYPE) @DfType(TextProcessor.DF_TYPE)
public fun interface TextProcessor { public fun interface TextProcessor {
public fun process(text: CharSequence): String public fun process(text: CharSequence): String

View File

@ -0,0 +1,16 @@
package space.kscience.snark
import space.kscience.dataforge.data.DataSet
import space.kscience.dataforge.data.filterByType
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
import kotlin.reflect.typeOf
public inline fun <reified R : Any> DataSet<*>.branch(
branchName: Name,
): DataSet<R> = filterByType(typeOf<R>()) { name, _ -> name.startsWith(branchName) }
public inline fun <reified R : Any> DataSet<*>.branch(
branchName: String,
): DataSet<R> = filterByType(typeOf<R>()) { name, _ -> name.startsWith(branchName.parseAsName()) }

View File

@ -0,0 +1,81 @@
package space.kscience.snark.html
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.html.FlowContent
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.snark.SnarkContext
public fun interface DataFragment {
public suspend fun FlowContent.renderFragment(page: PageContext, data: DataSet<*>)
}
context(PageContext)
public fun FlowContent.htmlData(data: DataSet<*>, fragment: Data<DataFragment>): Unit = runBlocking(Dispatchers.IO) {
with(fragment.await()) { renderFragment(page, data) }
}
context(SnarkContext)
public val Data<*>.id: String
get() = meta["id"]?.string ?: "block[${hashCode()}]"
context(SnarkContext)
public val Data<*>.language: String?
get() = meta["language"].string?.lowercase()
context(SnarkContext)
public val Data<*>.order: Int?
get() = meta["order"]?.int
context(SnarkContext)
public val Data<*>.published: Boolean
get() = meta["published"].string != "false"
/**
* Resolve a Html builder by its full name
*/
context(SnarkContext)
public fun DataSet<*>.resolveHtmlOrNull(name: Name): Data<DataFragment>? {
val resolved = (getByType<DataFragment>(name) ?: getByType<DataFragment>(name + SiteContext.INDEX_PAGE_TOKEN))
return resolved?.takeIf {
it.published //TODO add language confirmation
}
}
context(SnarkContext)
public fun DataSet<*>.resolveHtmlOrNull(name: String): Data<DataFragment>? = resolveHtmlOrNull(name.parseAsName())
context(SnarkContext)
public fun DataSet<*>.resolveHtml(name: String): Data<DataFragment> = resolveHtmlOrNull(name)
?: error("Html fragment with name $name is not resolved")
/**
* Find all Html blocks using given name/meta filter
*/
context(SnarkContext)
public fun DataSet<*>.resolveAllHtml(
predicate: (name: Name, meta: Meta) -> Boolean,
): Map<Name, Data<DataFragment>> = filterByType<DataFragment> { name, meta ->
predicate(name, meta)
&& meta["published"].string != "false"
//TODO add language confirmation
}.asSequence().associate { it.name to it.data }
context(SnarkContext)
public fun DataSet<*>.findHtmlByContentType(
contentType: String,
baseName: Name = Name.EMPTY,
): Map<Name, Data<DataFragment>> = resolveAllHtml { name, meta ->
name.startsWith(baseName) && meta["content_type"].string == contentType
}

View File

@ -1,44 +0,0 @@
package space.kscience.snark.html
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.html.FlowContent
import kotlinx.html.TagConsumer
import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.await
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
import space.kscience.snark.SnarkContext
//TODO replace by VisionForge type
public fun interface HtmlFragment {
public fun TagConsumer<*>.renderFragment()
}
public typealias HtmlData = Data<HtmlFragment>
public fun FlowContent.htmlData(page: PageContext, data: HtmlData): Unit = runBlocking(Dispatchers.IO) {
withSnarkPage(page) {
with(data.await()) { consumer.renderFragment() }
}
}
context(SnarkContext)
public val Data<*>.id: String
get() = meta["id"]?.string ?: "block[${hashCode()}]"
context(SnarkContext)
public val Data<*>.language: String?
get() = meta["language"].string?.lowercase()
context(SnarkContext)
public val Data<*>.order: Int?
get() = meta["order"]?.int
context(SnarkContext)
public val Data<*>.published: Boolean
get() = meta["published"].string != "false"

View File

@ -1,15 +1,29 @@
package space.kscience.snark.html package space.kscience.snark.html
import kotlinx.html.HTML import kotlinx.html.HTML
import kotlinx.html.stream.createHTML
import kotlinx.html.visitTagAndFinalize
import space.kscience.dataforge.data.DataSet import space.kscience.dataforge.data.DataSet
import space.kscience.dataforge.data.DataSetBuilder import space.kscience.dataforge.data.DataSetBuilder
import space.kscience.dataforge.data.node
import space.kscience.dataforge.data.static import space.kscience.dataforge.data.static
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
public fun interface HtmlPage { public fun interface HtmlPage {
public fun HTML.renderPage(pageContext: PageContext, pageData: DataSet<*>) public suspend fun HTML.renderPage(page: PageContext, data: DataSet<*>)
public companion object{
public suspend fun createHtmlString(pageContext: PageContext, page: HtmlPage, data: DataSet<*>): String{
return createHTML().run {
HTML(kotlinx.html.emptyMap, this, null).visitTagAndFinalize(this) {
with(page) {
renderPage(pageContext, data)
}
}
}
}
}
} }
@ -22,6 +36,7 @@ public fun DataSetBuilder<Any>.page(name: Name, pageMeta: Meta = Meta.EMPTY, blo
// if (data.type == typeOf<HtmlData>()) { // if (data.type == typeOf<HtmlData>()) {
// val languageMeta: Meta = Language.forName(name) // val languageMeta: Meta = Language.forName(name)
// //

View File

@ -11,7 +11,7 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName import space.kscience.dataforge.names.parseAsName
public fun interface HtmlSite { public fun interface HtmlSite {
public fun renderSite(siteContext: SiteContext, siteData: DataSet<Any>) public suspend fun SiteContext.renderSite(data: DataSet<Any>)
} }
public fun DataSetBuilder<Any>.site( public fun DataSetBuilder<Any>.site(

View File

@ -75,9 +75,9 @@ public val SiteContext.languagePrefix: Name
get() = languages[language]?.let { it[Language::prefix.name].string ?: language }?.parseAsName() ?: Name.EMPTY get() = languages[language]?.let { it[Language::prefix.name].string ?: language }?.parseAsName() ?: Name.EMPTY
@SnarkBuilder @SnarkBuilder
public suspend fun SiteContext.multiLanguageSite(data: DataSet<Any>, languageMap: Map<String, Meta>, site: HtmlSite) { public suspend fun SiteContext.multiLanguageSite(data: DataSet<Any>, languageMap: Map<String, Language>, site: HtmlSite) {
languageMap.forEach { (languageKey, languageMeta) -> languageMap.forEach { (languageKey, language) ->
val prefix = languageMeta[Language::prefix.name].string ?: languageKey val prefix = language.prefix ?: languageKey
val languageSiteMeta = Meta { val languageSiteMeta = Meta {
SITE_LANGUAGE_KEY put languageKey SITE_LANGUAGE_KEY put languageKey
SITE_LANGUAGES_KEY put Meta { SITE_LANGUAGES_KEY put Meta {

View File

@ -25,6 +25,8 @@ public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") {
@SnarkBuilder @SnarkBuilder
public interface PageContext : SnarkContext { public interface PageContext : SnarkContext {
public val site: SiteContext
/** /**
* A metadata for a page. It should include site metadata * A metadata for a page. It should include site metadata
*/ */
@ -53,42 +55,3 @@ public fun PageContext.resolvePageRef(pageName: String): String = resolvePageRef
public val PageContext.homeRef: String get() = resolvePageRef(SiteContext.INDEX_PAGE_TOKEN.asName()) public val PageContext.homeRef: String get() = resolvePageRef(SiteContext.INDEX_PAGE_TOKEN.asName())
public val PageContext.name: Name? get() = pageMeta["name"].string?.parseAsName() public val PageContext.name: Name? get() = pageMeta["name"].string?.parseAsName()
/**
* Resolve a Html builder by its full name
*/
context(SnarkContext)
public fun DataTree<*>.resolveHtmlOrNull(name: Name): HtmlData? {
val resolved = (getByType<HtmlFragment>(name) ?: getByType<HtmlFragment>(name + SiteContext.INDEX_PAGE_TOKEN))
return resolved?.takeIf {
it.published //TODO add language confirmation
}
}
context(SnarkContext)
public fun DataTree<*>.resolveHtmlOrNull(name: String): HtmlData? = resolveHtmlOrNull(name.parseAsName())
context(SnarkContext)
public fun DataTree<*>.resolveHtml(name: String): HtmlData = resolveHtmlOrNull(name)
?: error("Html fragment with name $name is not resolved")
/**
* Find all Html blocks using given name/meta filter
*/
context(SnarkContext)
public fun DataTree<*>.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
filterByType<HtmlFragment> { name, meta ->
predicate(name, meta)
&& meta["published"].string != "false"
//TODO add language confirmation
}.asSequence().associate { it.name to it.data }
context(SnarkContext)
public fun DataTree<*>.findByContentType(
contentType: String,
baseName: Name = Name.EMPTY,
): Map<Name, Data<HtmlFragment>> = resolveAllHtml { name, meta ->
name.startsWith(baseName) && meta["content_type"].string == contentType
}

View File

@ -6,12 +6,11 @@ import space.kscience.dataforge.io.Binary
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.workspace.Workspace
import space.kscience.snark.SnarkBuilder import space.kscience.snark.SnarkBuilder
import space.kscience.snark.SnarkContext import space.kscience.snark.SnarkContext
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
/** /**
@ -74,6 +73,24 @@ public interface SiteContext : SnarkContext {
} }
} }
public suspend fun SiteContext.static(dataSet: DataSet<Binary>, prefix: Name = Name.EMPTY) {
dataSet.forEach { (name, data) ->
static(prefix + name, data)
}
}
public suspend fun SiteContext.static(dataSet: DataSet<*>, branch: String, prefix: String = branch) {
val branchName = branch.parseAsName()
val prefixName = prefix.parseAsName()
val binaryType = typeOf<Binary>()
dataSet.forEach { (name, data) ->
@Suppress("UNCHECKED_CAST")
if (name.startsWith(branchName) && data.type.isSubtypeOf(binaryType)) {
static(prefixName + name, data as Data<Binary>)
}
}
}
@SnarkBuilder @SnarkBuilder
public suspend fun SiteContext.page( public suspend fun SiteContext.page(
route: Name, route: Name,

View File

@ -1,6 +1,5 @@
package space.kscience.snark.html package space.kscience.snark.html
import io.ktor.http.ContentType
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.unsafe import kotlinx.html.unsafe
import kotlinx.io.Source import kotlinx.io.Source
@ -12,25 +11,25 @@ import space.kscience.snark.SnarkReader
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
public object HtmlReader : SnarkReader<HtmlFragment> { public object HtmlReader : SnarkReader<DataFragment> {
override val types: Set<String> = setOf("html") override val types: Set<String> = setOf("html")
override fun readFrom(source: String): HtmlFragment = HtmlFragment { override fun readFrom(source: String): DataFragment = DataFragment { _, _ ->
div { div {
unsafe { +source } unsafe { +source }
} }
} }
override fun readFrom(source: Source): HtmlFragment = readFrom(source.readString()) override fun readFrom(source: Source): DataFragment = readFrom(source.readString())
override val type: KType = typeOf<HtmlFragment>() override val type: KType = typeOf<DataFragment>()
} }
public object MarkdownReader : SnarkReader<HtmlFragment> { public object MarkdownReader : SnarkReader<DataFragment> {
override val type: KType = typeOf<HtmlFragment>() override val type: KType = typeOf<DataFragment>()
override val types: Set<String> = setOf("text/markdown", "md", "markdown") override val types: Set<String> = setOf("text/markdown", "md", "markdown")
override fun readFrom(source: String): HtmlFragment = HtmlFragment { override fun readFrom(source: String): DataFragment = DataFragment { _, _ ->
val parsedTree = markdownParser.buildMarkdownTreeFromString(source) val parsedTree = markdownParser.buildMarkdownTreeFromString(source)
val htmlString = HtmlGenerator(source, parsedTree, markdownFlavor).generateHtml() val htmlString = HtmlGenerator(source, parsedTree, markdownFlavor).generateHtml()
@ -44,9 +43,9 @@ public object MarkdownReader : SnarkReader<HtmlFragment> {
private val markdownFlavor = CommonMarkFlavourDescriptor() private val markdownFlavor = CommonMarkFlavourDescriptor()
private val markdownParser = MarkdownParser(markdownFlavor) private val markdownParser = MarkdownParser(markdownFlavor)
override fun readFrom(source: Source): HtmlFragment = readFrom(source.readString()) override fun readFrom(source: Source): DataFragment = readFrom(source.readString())
public val snarkReader: SnarkReader<HtmlFragment> = SnarkReader(this, "text/markdown") public val snarkReader: SnarkReader<DataFragment> = SnarkReader(this, "text/markdown")
} }

View File

@ -12,7 +12,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.workspace.FileData import space.kscience.dataforge.workspace.FileData
import space.kscience.dataforge.workspace.Workspace
import space.kscience.snark.html.* import space.kscience.snark.html.*
import java.nio.file.Path import java.nio.file.Path
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
@ -32,7 +31,6 @@ internal class StaticSiteContext(
) : SiteContext { ) : SiteContext {
// @OptIn(ExperimentalPathApi::class) // @OptIn(ExperimentalPathApi::class)
// private suspend fun files(item: DataTreeItem<Any>, routeName: Name) { // private suspend fun files(item: DataTreeItem<Any>, routeName: Name) {
// //try using direct file rendering // //try using direct file rendering
@ -91,30 +89,23 @@ internal class StaticSiteContext(
"${baseUrl.removeSuffix("/")}/$ref" "${baseUrl.removeSuffix("/")}/$ref"
} }
inner class StaticPageContext(override val pageMeta: Meta) : PageContext { class StaticPageContext(override val site: StaticSiteContext, override val pageMeta: Meta) : PageContext {
override fun resolveRef(ref: String): String = override fun resolveRef(ref: String): String =
this@StaticSiteContext.resolveRef(this@StaticSiteContext.baseUrl, ref) site.resolveRef(site.baseUrl, ref)
override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef( override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef(
(if (relative) this@StaticSiteContext.route + pageName else pageName).toWebPath() + ".html" (if (relative) site.route + pageName else pageName).toWebPath() + ".html"
) )
} }
override suspend fun page(route: Name, data: DataSet<Any>, pageMeta: Meta, htmlPage: HtmlPage) { override suspend fun page(route: Name, data: DataSet<Any>, pageMeta: Meta, htmlPage: HtmlPage) {
val htmlBuilder = createHTML()
val modifiedPageMeta = pageMeta.toMutableMeta().apply { val modifiedPageMeta = pageMeta.toMutableMeta().apply {
"name" put route.toString() "name" put route.toString()
} }
htmlBuilder.html {
with(htmlPage) {
renderPage(StaticPageContext(Laminate(modifiedPageMeta, siteMeta)), data)
}
}
val newPath = if (route.isEmpty()) { val newPath = if (route.isEmpty()) {
outputPath.resolve("index.html") outputPath.resolve("index.html")
} else { } else {
@ -122,17 +113,20 @@ internal class StaticSiteContext(
} }
newPath.parent.createDirectories() newPath.parent.createDirectories()
newPath.writeText(htmlBuilder.finalize())
val pageContext = StaticPageContext(this, Laminate(modifiedPageMeta, siteMeta))
newPath.writeText(HtmlPage.createHtmlString(pageContext,htmlPage, data))
} }
override suspend fun site(route: Name, data: DataSet<Any>, siteMeta: Meta, htmlSite: HtmlSite) { override suspend fun site(route: Name, data: DataSet<Any>, siteMeta: Meta, htmlSite: HtmlSite) {
val subSiteContext = StaticSiteContext( with(htmlSite) {
siteMeta = Laminate(siteMeta, this.siteMeta), StaticSiteContext(
baseUrl = if (baseUrl == "") "" else resolveRef(baseUrl, route.toWebPath()), siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
route = Name.EMPTY, baseUrl = if (baseUrl == "") "" else resolveRef(baseUrl, route.toWebPath()),
outputPath = outputPath.resolve(route.toWebPath()) route = Name.EMPTY,
) outputPath = outputPath.resolve(route.toWebPath())
htmlSite.renderSite(subSiteContext, data) ).renderSite(data)
}
} }
} }

View File

@ -1,28 +1,31 @@
package space.kscience.snark.ktor package space.kscience.snark.ktor
import io.ktor.http.ContentType import io.ktor.http.*
import io.ktor.http.URLBuilder import io.ktor.http.content.TextContent
import io.ktor.http.URLProtocol
import io.ktor.http.fromFileExtension
import io.ktor.server.application.Application import io.ktor.server.application.Application
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.staticFiles import io.ktor.server.http.content.staticFiles
import io.ktor.server.plugins.origin import io.ktor.server.plugins.origin
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes import io.ktor.server.response.respondBytes
import io.ktor.server.routing.* import io.ktor.server.routing.Route
import kotlinx.css.CssBuilder import io.ktor.server.routing.createRouteFromPath
import kotlinx.html.CommonAttributeGroupFacade import io.ktor.server.routing.get
import kotlinx.html.head import io.ktor.server.routing.routing
import kotlinx.html.style
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.error import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.DataSet
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.await
import space.kscience.dataforge.io.Binary import space.kscience.dataforge.io.Binary
import space.kscience.dataforge.io.toByteArray import space.kscience.dataforge.io.toByteArray
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.cutLast import space.kscience.dataforge.names.cutLast
import space.kscience.dataforge.names.endsWith import space.kscience.dataforge.names.endsWith
@ -34,11 +37,11 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
public fun CommonAttributeGroupFacade.css(block: CssBuilder.() -> Unit) { //public fun CommonAttributeGroupFacade.css(block: CssBuilder.() -> Unit) {
style = CssBuilder().block().toString() // style = CssBuilder().block().toString()
} //}
public class KtorSiteBuilder( public class KtorSiteContext(
override val context: Context, override val context: Context,
override val siteMeta: Meta, override val siteMeta: Meta,
private val baseUrl: String, private val baseUrl: String,
@ -87,18 +90,19 @@ public class KtorSiteBuilder(
} }
private inner class KtorPageContext( private class KtorPageContext(
override val site: KtorSiteContext,
val pageBaseUrl: String, val pageBaseUrl: String,
override val pageMeta: Meta, override val pageMeta: Meta,
) : PageContext { ) : PageContext {
override fun resolveRef(ref: String): String = this@KtorSiteBuilder.resolveRef(pageBaseUrl, ref) override fun resolveRef(ref: String): String = site.resolveRef(pageBaseUrl, ref)
override fun resolvePageRef( override fun resolvePageRef(
pageName: Name, pageName: Name,
relative: Boolean, relative: Boolean,
): String { ): String {
val fullPageName = if (relative) this@KtorSiteBuilder.route + pageName else pageName val fullPageName = if (relative) site.route + pageName else pageName
return if (fullPageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) { return if (fullPageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) {
resolveRef(fullPageName.cutLast().toWebPath()) resolveRef(fullPageName.cutLast().toWebPath())
} else { } else {
@ -121,27 +125,25 @@ public class KtorSiteBuilder(
"name" put route.toString() "name" put route.toString()
"url" put url.buildString() "url" put url.buildString()
} }
val pageBuilder = KtorPageContext(url.buildString(), Laminate(modifiedPageMeta, siteMeta)) val pageContext =
KtorPageContext(this@KtorSiteContext, url.buildString(), Laminate(modifiedPageMeta, siteMeta))
//render page in suspend environment
val html = HtmlPage.createHtmlString(pageContext, htmlPage, data)
call.respondHtml { call.respond(TextContent(html, ContentType.Text.Html.withCharset(Charsets.UTF_8), HttpStatusCode.OK))
head {}
with(htmlPage) {
renderPage(pageBuilder, data)
}
}
} }
} }
override suspend fun site(route: Name, data: DataSet<Any>, siteMeta: Meta, htmlSite: HtmlSite) { override suspend fun site(route: Name, data: DataSet<Any>, siteMeta: Meta, htmlSite: HtmlSite) {
val context = KtorSiteBuilder( with(htmlSite) {
context, KtorSiteContext(
siteMeta = Laminate(siteMeta, this.siteMeta), context,
baseUrl = resolveRef(baseUrl, route.toWebPath()), siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
route = Name.EMPTY, baseUrl = resolveRef(baseUrl, route.toWebPath()),
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath()) route = Name.EMPTY,
) ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
).renderSite(data)
htmlSite.renderSite(context, data) }
} }
} }
@ -151,12 +153,12 @@ private fun Route.site(
data: DataTree<*>, data: DataTree<*>,
baseUrl: String = "", baseUrl: String = "",
siteMeta: Meta = data.meta, siteMeta: Meta = data.meta,
block: KtorSiteBuilder.() -> Unit, block: KtorSiteContext.() -> Unit,
) { ) {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
block(KtorSiteBuilder(context, siteMeta, baseUrl, route = Name.EMPTY, this@Route)) block(KtorSiteContext(context, siteMeta, baseUrl, route = Name.EMPTY, this@Route))
} }
public fun Application.site( public fun Application.site(