Implemented binary propagation

This commit is contained in:
Alexander Nozik 2023-03-27 10:23:27 +03:00
parent 941da6fab7
commit e6bee125d3
17 changed files with 190 additions and 187 deletions

View File

@ -12,7 +12,7 @@ allprojects {
}
}
val dataforgeVersion by extra("0.6.1-dev-4")
val dataforgeVersion by extra("0.6.1-dev-6")
ksciencePublish {
github("SciProgCentre", "snark")

View File

@ -1,3 +1,3 @@
kotlin.code.style=official
toolsVersion=0.14.2-kotlin-1.8.10
toolsVersion=0.14.5-kotlin-1.8.20-RC

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,7 +1,7 @@
rootProject.name = "snark"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
enableFeaturePreview("VERSION_CATALOGS")
//enableFeaturePreview("VERSION_CATALOGS")
pluginManagement {

View File

@ -0,0 +1,4 @@
package space.kscience.snark
@DslMarker
public annotation class SnarkBuilder

View File

@ -3,4 +3,5 @@ package space.kscience.snark
/**
* A marker interface for Snark Page and Site builders
*/
@SnarkBuilder
public interface SnarkContext

View File

@ -1,40 +0,0 @@
package space.kscience.snark
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.Plugin
import space.kscience.dataforge.data.DataSourceBuilder
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.DataTreeBuilder
import space.kscience.dataforge.meta.MutableMeta
import kotlin.reflect.typeOf
public class SnarkEnvironment(public val parentContext: Context) {
private var _data: DataTree<*>? = null
public val data: DataTree<Any> get() = _data ?: DataTree.empty()
public fun data(builder: DataSourceBuilder<Any>.() -> Unit) {
_data = DataTreeBuilder<Any>(typeOf<Any>(), parentContext.coroutineContext).apply(builder)
//TODO use node meta
}
public val meta: MutableMeta = MutableMeta()
public fun meta(block: MutableMeta.() -> Unit) {
meta.apply(block)
}
private val _plugins = HashSet<Plugin>()
public val plugins: Set<Plugin> get() = _plugins
public fun registerPlugin(plugin: Plugin) {
_plugins.add(plugin)
}
public companion object{
public val default: SnarkEnvironment = SnarkEnvironment(Global)
}
}
public fun SnarkEnvironment(parentContext: Context = Global, block: SnarkEnvironment.() -> Unit): SnarkEnvironment =
SnarkEnvironment(parentContext).apply(block)

View File

@ -0,0 +1,15 @@
package space.kscience.snark.html
import io.ktor.util.asStream
import io.ktor.utils.io.core.Input
import space.kscience.dataforge.io.IOReader
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.reflect.KType
import kotlin.reflect.typeOf
internal object ImageIOReader : IOReader<BufferedImage> {
override val type: KType get() = typeOf<BufferedImage>()
override fun readObject(input: Input): BufferedImage = ImageIO.read(input.asStream())
}

View File

@ -3,6 +3,7 @@ package space.kscience.snark.html
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.*
import space.kscience.snark.SnarkBuilder
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGES_KEY
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_KEY
@ -46,7 +47,7 @@ public class Language : Scheme() {
context(SiteBuilder)
public fun forName(name: Name): Meta = Meta {
val currentLanguagePrefix = languages[language]?.get(Language::prefix.name)?.string ?: language
val fullName = (route.removeHeadOrNull(currentLanguagePrefix.asName()) ?: route) + name
val fullName = (route.removeFirstOrNull(currentLanguagePrefix.asName()) ?: route) + name
languages.forEach { (key, meta) ->
val languagePrefix: String = meta[Language::prefix.name].string ?: key
val nameWithLanguage: Name = if (languagePrefix.isBlank()) {
@ -90,6 +91,7 @@ public fun SiteBuilder.withLanguages(languageMap: Map<String, Meta>, block: Site
}
}
@SnarkBuilder
public fun SiteBuilder.withLanguages(
vararg language: Pair<String, String>,
block: SiteBuilder.(language: String) -> Unit,

View File

@ -5,6 +5,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.DataTreeItem
import space.kscience.dataforge.data.branch
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
@ -14,6 +15,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.snark.SnarkBuilder
import space.kscience.snark.SnarkContext
import space.kscience.snark.html.SiteLayout.Companion.LAYOUT_KEY
@ -21,6 +23,7 @@ import space.kscience.snark.html.SiteLayout.Companion.LAYOUT_KEY
/**
* An abstraction, which is used to render sites to the different rendering engines
*/
@SnarkBuilder
public interface SiteBuilder : ContextAware, SnarkContext {
/**
@ -48,29 +51,16 @@ public interface SiteBuilder : ContextAware, SnarkContext {
/**
* Serve a static data as a file from [data] with given [dataName] at given [routeName].
*/
public fun file(dataName: Name, routeName: Name = dataName)
//
// /**
// * Add a static file or directory to this site/route at [webPath]
// */
// public fun file(file: Path, webPath: String = file.fileName.toString())
//
// /**
// * Add a static file (single) from resources
// */
// public fun resourceFile(resourcesPath: String, webPath: String = resourcesPath)
//
// /**
// * Add a resource directory to route
// */
// public fun resourceDirectory(resourcesPath: String)
public fun static(dataName: Name, routeName: Name = dataName)
/**
* Create a single page at given [route]. If route is empty, create an index page at current route.
*
* @param pageMeta additional page meta. [WebPage] will use both it and [siteMeta]
*/
public fun page(route: Name = Name.EMPTY, pageMeta: Meta = Meta.EMPTY, content: context(WebPage, HTML) () -> Unit)
@SnarkBuilder
public fun page(route: Name = Name.EMPTY, pageMeta: Meta = Meta.EMPTY, content: context(HTML, WebPage) () -> Unit)
/**
* Create a route with optional data tree override. For example one could use a subtree of the initial tree.
@ -100,9 +90,10 @@ public interface SiteBuilder : ContextAware, SnarkContext {
}
context(SiteBuilder)
public val siteBuilder: SiteBuilder
public val site: SiteBuilder
get() = this@SiteBuilder
@SnarkBuilder
public inline fun SiteBuilder.route(
route: Name,
dataOverride: DataTree<*>? = null,
@ -112,6 +103,7 @@ public inline fun SiteBuilder.route(
route(route, dataOverride, routeMeta).apply(block)
}
@SnarkBuilder
public inline fun SiteBuilder.route(
route: String,
dataOverride: DataTree<*>? = null,
@ -121,6 +113,7 @@ public inline fun SiteBuilder.route(
route(route.parseAsName(), dataOverride, routeMeta).apply(block)
}
@SnarkBuilder
public inline fun SiteBuilder.site(
route: Name,
dataOverride: DataTree<*>? = null,
@ -130,6 +123,7 @@ public inline fun SiteBuilder.site(
site(route, dataOverride, routeMeta).apply(block)
}
@SnarkBuilder
public inline fun SiteBuilder.site(
route: String,
dataOverride: DataTree<*>? = null,
@ -139,6 +133,26 @@ public inline fun SiteBuilder.site(
site(route.parseAsName(), dataOverride, routeMeta).apply(block)
}
public inline fun SiteBuilder.withData(
data: DataTree<*>,
block: SiteBuilder.() -> Unit
){
route(Name.EMPTY, data).apply(block)
}
public inline fun SiteBuilder.withDataBranch(
name: Name,
block: SiteBuilder.() -> Unit
){
route(Name.EMPTY, data.branch(name)).apply(block)
}
public inline fun SiteBuilder.withDataBranch(
name: String,
block: SiteBuilder.() -> Unit
){
route(Name.EMPTY, data.branch(name)).apply(block)
}
///**
// * Create a stand-alone site at a given node
@ -154,14 +168,19 @@ public inline fun SiteBuilder.site(
// }
//}
public fun SiteBuilder.static(dataName: String): Unit = static(dataName.parseAsName())
public fun SiteBuilder.static(dataName: String, routeName: String): Unit = static(
dataName.parseAsName(),
routeName.parseAsName()
)
internal fun SiteBuilder.assetsFrom(rootMeta: Meta) {
rootMeta.getIndexed("file".asName()).forEach { (_, meta) ->
val webName: String? by meta.string()
val name by meta.string { error("File path is not provided") }
val fileName = name.parseAsName()
file(fileName, webName?.parseAsName() ?: fileName)
static(fileName, webName?.parseAsName() ?: fileName)
}
}

View File

@ -3,6 +3,7 @@ package space.kscience.snark.html
import io.ktor.utils.io.core.readBytes
import space.kscience.dataforge.context.*
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.io.IOFormat
import space.kscience.dataforge.io.IOPlugin
import space.kscience.dataforge.io.IOReader
import space.kscience.dataforge.io.JsonMetaFormat
@ -17,7 +18,6 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.workspace.FileData
import space.kscience.dataforge.workspace.readDataDirectory
import space.kscience.snark.SnarkEnvironment
import space.kscience.snark.SnarkParser
import java.nio.file.Path
import kotlin.io.path.extension
@ -66,16 +66,19 @@ public class SnarkHtmlPlugin : AbstractPlugin() {
"png".asName() to SnarkParser(ImageIOReader, "png"),
"jpg".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"),
"gif".asName() to SnarkParser(ImageIOReader, "gif"),
"svg".asName() to SnarkParser(IOReader.binary, "svg"),
"raw".asName() to SnarkParser(IOReader.binary, "css", "js", "scss", "woff", "woff2", "ttf", "eot")
)
TextProcessor.TYPE -> mapOf(
"basic".asName() to BasicTextProcessor
)
else -> super.content(target)
}
public companion object : PluginFactory<SnarkHtmlPlugin> {
override val tag: PluginTag = PluginTag("snark")
override val type: KClass<out SnarkHtmlPlugin> = SnarkHtmlPlugin::class
override fun build(context: Context, meta: Meta): SnarkHtmlPlugin = SnarkHtmlPlugin()
@ -87,30 +90,28 @@ public class SnarkHtmlPlugin : AbstractPlugin() {
}
}
/**
* Load necessary dependencies and return a [SnarkHtmlPlugin] in a finalized context
*/
public fun SnarkEnvironment.buildHtmlPlugin(): SnarkHtmlPlugin {
val context = parentContext.buildContext("snark".asName()) {
plugin(SnarkHtmlPlugin)
plugins.forEach {
plugin(it)
}
}
return context.request(SnarkHtmlPlugin)
}
@OptIn(DFExperimental::class)
public fun SnarkHtmlPlugin.readDirectory(path: Path): DataTree<Any> = io.readDataDirectory(path) { dataPath, meta ->
public fun SnarkHtmlPlugin.readDirectory(path: Path): DataTree<Any> = io.readDataDirectory(path, setOf("md","html")) { dataPath, meta ->
val fileExtension = meta[FileData.FILE_EXTENSION_KEY].string ?: dataPath.extension
val parser: SnarkParser<Any> = parsers.values.filter { parser ->
fileExtension in parser.fileExtensions
}.maxByOrNull {
it.priority
} ?: run {
logger.warn { "The parser is not found for file $dataPath with meta $meta" }
logger.debug { "The parser is not found for file $dataPath with meta $meta" }
SnarkHtmlPlugin.byteArraySnarkParser
}
parser.asReader(context, meta)
}
}
public fun SnarkHtmlPlugin.readResourceDirectory(
resource: String = "",
classLoader: ClassLoader = SnarkHtmlPlugin::class.java.classLoader,
): DataTree<Any> = readDirectory(
Path.of(
classLoader.getResource(resource)?.toURI() ?: error(
"Resource with name $resource is not resolved"
)
)
)

View File

@ -1,19 +1,14 @@
package space.kscience.snark.html
import io.ktor.util.asStream
import io.ktor.utils.io.core.Input
import kotlinx.html.div
import kotlinx.html.unsafe
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.IOReader
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.snark.SnarkParser
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -61,8 +56,3 @@ internal object SnarkMarkdownParser : SnarkTextParser<HtmlFragment>() {
}
}
internal object ImageIOReader : IOReader<BufferedImage> {
override val type: KType get() = typeOf<BufferedImage>()
override fun readObject(input: Input): BufferedImage = ImageIO.read(input.asStream())
}

View File

@ -10,7 +10,6 @@ import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.plus
import space.kscience.snark.SnarkEnvironment
import java.nio.file.Files
import java.nio.file.Path
import kotlin.contracts.InvocationKind
@ -30,7 +29,7 @@ internal class StaticSiteBuilder(
private val outputPath: Path,
) : SiteBuilder {
override fun file(dataName: Name, routeName: Name) {
override fun static(dataName: Name, routeName: Name) {
TODO("Not yet implemented")
}
@ -82,15 +81,16 @@ internal class StaticSiteBuilder(
override val snark: SnarkHtmlPlugin get() = this@StaticSiteBuilder.snark
override fun resolveRef(ref: String): String = resolveRef(baseUrl, ref)
override fun resolveRef(ref: String): String =
this@StaticSiteBuilder.resolveRef(this@StaticSiteBuilder.baseUrl, ref)
override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef(
(if (relative) route + pageName else pageName).toWebPath() + ".html"
(if (relative) this@StaticSiteBuilder.route + pageName else pageName).toWebPath() + ".html"
)
}
override fun page(route: Name, pageMeta: Meta, content: context(WebPage, HTML) () -> Unit) {
override fun page(route: Name, pageMeta: Meta, content: context(HTML) WebPage.() -> Unit) {
val htmlBuilder = createHTML()
val modifiedPageMeta = pageMeta.toMutableMeta().apply {
@ -98,7 +98,7 @@ internal class StaticSiteBuilder(
}
htmlBuilder.html {
content(StaticWebPage(Laminate(modifiedPageMeta, siteMeta)), this)
content(this, StaticWebPage(Laminate(modifiedPageMeta, siteMeta)))
}
val newPath = if (route.isEmpty()) {
@ -132,7 +132,7 @@ internal class StaticSiteBuilder(
snark = snark,
data = dataOverride ?: data,
siteMeta = Laminate(routeMeta, siteMeta),
baseUrl = if(baseUrl == "") "" else resolveRef(baseUrl, routeName.toWebPath()),
baseUrl = if (baseUrl == "") "" else resolveRef(baseUrl, routeName.toWebPath()),
route = Name.EMPTY,
outputPath = outputPath.resolve(routeName.toWebPath())
)
@ -143,14 +143,15 @@ internal class StaticSiteBuilder(
* Use [siteUrl] as a base for all resolved URLs. By default, use [outputPath] absolute path as a base.
*
*/
public fun SnarkEnvironment.static(
public fun SnarkHtmlPlugin.static(
data: DataTree<*>,
outputPath: Path,
siteUrl: String = outputPath.absolutePathString().replace("\\", "/"),
siteMeta: Meta = data.meta,
block: SiteBuilder.() -> Unit,
) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val plugin = buildHtmlPlugin()
StaticSiteBuilder(plugin, data, meta, siteUrl, Name.EMPTY, outputPath).block()
StaticSiteBuilder(this, data, siteMeta, siteUrl, Name.EMPTY, outputPath).block()
}

View File

@ -7,6 +7,7 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.*
import space.kscience.snark.SnarkBuilder
import space.kscience.snark.SnarkContext
context(SnarkContext)
@ -21,6 +22,7 @@ public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") {
/**
* A context for building a single page
*/
@SnarkBuilder
public interface WebPage : ContextAware, SnarkContext {
public val snark: SnarkHtmlPlugin
@ -62,7 +64,7 @@ public val WebPage.name: Name? get() = pageMeta["name"].string?.parseAsName()
* Resolve a Html builder by its full name
*/
context(SnarkContext)
public fun DataTree<*>.resolveHtml(name: Name): HtmlData? {
public fun DataTree<*>.resolveHtmlOrNull(name: Name): HtmlData? {
val resolved = (getByType<HtmlFragment>(name) ?: getByType<HtmlFragment>(name + SiteBuilder.INDEX_PAGE_TOKEN))
return resolved?.takeIf {
@ -71,7 +73,11 @@ public fun DataTree<*>.resolveHtml(name: Name): HtmlData? {
}
context(SnarkContext)
public fun DataTree<*>.resolveHtml(name: String): HtmlData? = resolveHtml(name.parseAsName())
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

View File

@ -1,42 +1,37 @@
package space.kscience.snark.html
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.data.DataTreeItem
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.snark.SnarkEnvironment
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
public class SnarkHtmlEnvironmentBuilder {
public val layouts: HashMap<Name, SiteLayout> = HashMap()
public fun layout(name: String, body: context(SiteBuilder) (DataTreeItem<*>) -> Unit) {
layouts[name.parseAsName()] = object : SiteLayout {
context(SiteBuilder) override fun render(item: DataTreeItem<*>) = body(siteBuilder, item)
context(SiteBuilder) override fun render(item: DataTreeItem<*>) = body(site, item)
}
}
}
public fun SnarkEnvironment.html(block: SnarkHtmlEnvironmentBuilder.() -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val envBuilder = SnarkHtmlEnvironmentBuilder().apply(block)
val plugin = object : AbstractPlugin() {
val snark by require(SnarkHtmlPlugin)
override val tag: PluginTag = PluginTag("@extension[${hashCode()}]")
override fun content(target: String): Map<Name, Any> = when (target) {
SiteLayout.TYPE -> envBuilder.layouts
else -> super.content(target)
}
}
registerPlugin(plugin)
}
//public fun SnarkEnvironment.html(block: SnarkHtmlEnvironmentBuilder.() -> Unit) {
// contract {
// callsInPlace(block, InvocationKind.EXACTLY_ONCE)
// }
//
// val envBuilder = SnarkHtmlEnvironmentBuilder().apply(block)
//
// val plugin = object : AbstractPlugin() {
// val snark by require(SnarkHtmlPlugin)
//
// override val tag: PluginTag = PluginTag("@extension[${hashCode()}]")
//
//
// override fun content(target: String): Map<Name, Any> = when (target) {
// SiteLayout.TYPE -> envBuilder.layouts
// else -> super.content(target)
// }
// }
// registerPlugin(plugin)
//}

View File

@ -20,6 +20,8 @@ import kotlinx.css.CssBuilder
import kotlinx.html.CommonAttributeGroupFacade
import kotlinx.html.HTML
import kotlinx.html.style
import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.DataTreeItem
import space.kscience.dataforge.data.await
@ -32,8 +34,10 @@ import space.kscience.dataforge.names.cutLast
import space.kscience.dataforge.names.endsWith
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.workspace.FileData
import space.kscience.snark.SnarkEnvironment
import space.kscience.snark.html.*
import space.kscience.snark.html.SiteBuilder
import space.kscience.snark.html.SnarkHtmlPlugin
import space.kscience.snark.html.WebPage
import space.kscience.snark.html.toWebPath
import java.nio.file.Path
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -52,34 +56,35 @@ public class KtorSiteBuilder(
private val ktorRoute: Route,
) : SiteBuilder {
private fun file(item: DataTreeItem<Any>, routeName: Name) {
val extension = item.meta[FileData.FILE_EXTENSION_KEY]?.string?.let { ".$it" } ?: ""
private fun files(item: DataTreeItem<Any>, routeName: Name) {
//try using direct file rendering
item.meta[FileData.FILE_PATH_KEY]?.string?.let {
try {
val file = Path.of(it).toFile()
if (file.isDirectory) {
ktorRoute.static(routeName.toWebPath()) {
files(file)
}
} else {
val fileName = routeName.toWebPath() + extension //TODO add extension
ktorRoute.file(fileName, file)
}
//success, don't do anything else
return@file
val file = try {
Path.of(it).toFile()
} catch (ex: Exception) {
//failure,
logger.error { "File $it could not be converted to java.io.File"}
return@let
}
if (file.isDirectory) {
ktorRoute.static(routeName.toWebPath()) {
files(file)
}
} else {
val fileName = routeName.toWebPath()
ktorRoute.file(fileName, file)
}
//success, don't do anything else
return@files
}
when (item) {
is DataTreeItem.Leaf -> {
val datum = item.data
if (datum.type != typeOf<Binary>()) error("Can't directly serve file of type ${item.data.type}")
ktorRoute.get(routeName.toWebPath() + extension) {
ktorRoute.get(routeName.toWebPath()) {
val binary = datum.await() as Binary
val extension = item.meta[FileData.FILE_EXTENSION_KEY]?.string?.let { ".$it" } ?: ""
val contentType: ContentType = extension
.let(ContentType::fromFileExtension)
.firstOrNull()
@ -93,15 +98,15 @@ public class KtorSiteBuilder(
is DataTreeItem.Node -> {
item.tree.items.forEach { (token, childItem) ->
file(childItem, routeName + token)
files(childItem, routeName + token)
}
}
}
}
override fun file(dataName: Name, routeName: Name) {
val item: DataTreeItem<Any> = data.getItem(dataName) ?: error("Data with name is not resolved")
file(item, routeName)
override fun static(dataName: Name, routeName: Name) {
val item: DataTreeItem<Any> = data.getItem(dataName) ?: error("Data with name $dataName is not resolved")
files(item, routeName)
}
//
// override fun file(file: Path, webPath: String) {
@ -140,13 +145,13 @@ public class KtorSiteBuilder(
override val snark: SnarkHtmlPlugin get() = this@KtorSiteBuilder.snark
override val data: DataTree<*> get() = this@KtorSiteBuilder.data
override fun resolveRef(ref: String): String = resolveRef(pageBaseUrl, ref)
override fun resolveRef(ref: String): String = this@KtorSiteBuilder.resolveRef(pageBaseUrl, ref)
override fun resolvePageRef(
pageName: Name,
relative: Boolean,
): String {
val fullPageName = if (relative) route + pageName else pageName
val fullPageName = if (relative) this@KtorSiteBuilder.route + pageName else pageName
return if (fullPageName.endsWith(SiteBuilder.INDEX_PAGE_TOKEN)) {
resolveRef(fullPageName.cutLast().toWebPath())
} else {
@ -155,7 +160,7 @@ public class KtorSiteBuilder(
}
}
override fun page(route: Name, pageMeta: Meta, content: context(WebPage, HTML)() -> Unit) {
override fun page(route: Name, pageMeta: Meta, content: context(HTML, WebPage) () -> Unit) {
ktorRoute.get(route.toWebPath()) {
call.respondHtml {
val request = call.request
@ -172,7 +177,7 @@ public class KtorSiteBuilder(
}
val pageBuilder = KtorWebPage(url.buildString(), Laminate(modifiedPageMeta, siteMeta))
content(pageBuilder, this)
content(this, pageBuilder)
}
}
}
@ -213,26 +218,30 @@ public class KtorSiteBuilder(
// }
}
context(Route, SnarkEnvironment)
private fun siteInRoute(
private fun Route.site(
snarkHtmlPlugin: SnarkHtmlPlugin,
data: DataTree<*>,
baseUrl: String = "",
siteMeta: Meta = data.meta,
block: KtorSiteBuilder.() -> Unit,
) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(KtorSiteBuilder(buildHtmlPlugin(), data, meta, baseUrl, route = Name.EMPTY, this@Route))
block(KtorSiteBuilder(snarkHtmlPlugin, data, siteMeta, baseUrl, route = Name.EMPTY, this@Route))
}
context(Application)
public fun SnarkEnvironment.site(
public fun Application.site(
snark: SnarkHtmlPlugin,
data: DataTree<*>,
baseUrl: String = "",
block: KtorSiteBuilder.() -> Unit,
siteMeta: Meta = data.meta,
block: SiteBuilder.() -> Unit,
) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
routing {
siteInRoute(baseUrl, block)
site(snark, data, baseUrl, siteMeta, block)
}
}

View File

@ -12,30 +12,30 @@ import java.time.LocalDateTime
import kotlin.io.path.*
public fun KtorSiteBuilder.extractResources(uri: URI, targetPath: Path): Path {
if (Files.isDirectory(targetPath)) {
logger.info { "Using existing data directory at $targetPath." }
} else {
logger.info { "Copying data from $uri into $targetPath." }
targetPath.createDirectories()
//Copy everything into a temporary directory
FileSystems.newFileSystem(uri, emptyMap<String, Any>()).use { fs ->
val rootPath: Path = fs.provider().getPath(uri)
Files.walk(rootPath).forEach { source: Path ->
if (source.isRegularFile()) {
val relative = source.relativeTo(rootPath).toString()
val destination: Path = targetPath.resolve(relative)
destination.parent.createDirectories()
Files.copy(source, destination)
}
}
}
}
return targetPath
}
public fun KtorSiteBuilder.extractResources(resource: String, targetPath: Path): Path =
extractResources(javaClass.getResource(resource)!!.toURI(), targetPath)
//public fun KtorSiteBuilder.extractResources(uri: URI, targetPath: Path): Path {
// if (Files.isDirectory(targetPath)) {
// logger.info { "Using existing data directory at $targetPath." }
// } else {
// logger.info { "Copying data from $uri into $targetPath." }
// targetPath.createDirectories()
// //Copy everything into a temporary directory
// FileSystems.newFileSystem(uri, emptyMap<String, Any>()).use { fs ->
// val rootPath: Path = fs.provider().getPath(uri)
// Files.walk(rootPath).forEach { source: Path ->
// if (source.isRegularFile()) {
// val relative = source.relativeTo(rootPath).toString()
// val destination: Path = targetPath.resolve(relative)
// destination.parent.createDirectories()
// Files.copy(source, destination)
// }
// }
// }
// }
// return targetPath
//}
//
//public fun KtorSiteBuilder.extractResources(resource: String, targetPath: Path): Path =
// extractResources(javaClass.getResource(resource)!!.toURI(), targetPath)
private const val DEPLOY_DATE_FILE = "deployDate"
private const val BUILD_DATE_FILE = "/buildDate"