Implemented binary propagation
This commit is contained in:
parent
941da6fab7
commit
e6bee125d3
@ -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")
|
||||
|
@ -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
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -1,7 +1,7 @@
|
||||
rootProject.name = "snark"
|
||||
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
enableFeaturePreview("VERSION_CATALOGS")
|
||||
//enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
package space.kscience.snark
|
||||
|
||||
@DslMarker
|
||||
public annotation class SnarkBuilder
|
@ -3,4 +3,5 @@ package space.kscience.snark
|
||||
/**
|
||||
* A marker interface for Snark Page and Site builders
|
||||
*/
|
||||
@SnarkBuilder
|
||||
public interface SnarkContext
|
@ -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)
|
@ -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())
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
)
|
||||
)
|
@ -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())
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
//}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user