Compare commits
2 Commits
8746360f14
...
eddeea8758
Author | SHA1 | Date | |
---|---|---|---|
eddeea8758 | |||
35cd0e828a |
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-8.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,7 +1,6 @@
|
||||
rootProject.name = "snark"
|
||||
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
//enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
package space.kscience.snark
|
||||
|
||||
import space.kscience.dataforge.data.Data
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.GenericDataTree
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import kotlin.reflect.KType
|
||||
|
||||
public class DataTreeWithDefault<T>(public val tree: DataTree<T>, val default: DataTree<T>) :
|
||||
DataTree<T> {
|
||||
override val dataType: KType get() = tree.dataType
|
||||
|
||||
override val self: DataTreeWithDefault<T> get() = this
|
||||
|
||||
override val data: Data<T>? get() = tree.data ?: default.data
|
||||
override val items: Map<NameToken, GenericDataTree<T, *>> get() = default.items + tree.items
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package space.kscience.snark
|
||||
|
||||
|
||||
import space.kscience.dataforge.data.DataSource
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.filterByType
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
|
||||
public inline fun <reified R : Any> DataTree<*>.branch(
|
||||
branchName: Name,
|
||||
): DataSource<R> = filterByType<R> { name, _, _ -> name.startsWith(branchName) }
|
@ -5,9 +5,10 @@ import space.kscience.dataforge.actions.transform
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.snark.DataTreeWithDefault
|
||||
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
|
||||
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_MAP_KEY
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
|
||||
@ -16,26 +17,37 @@ public class Language : Scheme() {
|
||||
/**
|
||||
* Language key override
|
||||
*/
|
||||
public var key: String? by string()
|
||||
public var key: String by string { error("Language key is not defined") }
|
||||
|
||||
/**
|
||||
* Page name prefix
|
||||
* Data location
|
||||
*/
|
||||
public var prefix: String? by string()
|
||||
public var dataPath: Name by value<Name>(
|
||||
reader = { (it?.string ?: key).parseAsName(true) },
|
||||
writer = { it.toString().asValue() }
|
||||
)
|
||||
|
||||
/**
|
||||
* An override for data path. By default uses [prefix]
|
||||
* Page name prefix override
|
||||
*/
|
||||
public var dataPath: String? by string()
|
||||
public var route: Name by value<Name>(
|
||||
reader = { (it?.string ?: key).parseAsName(true) },
|
||||
writer = { it.toString().asValue() }
|
||||
)
|
||||
|
||||
/**
|
||||
* Target page name with a given language key
|
||||
*/
|
||||
public var target: Name?
|
||||
get() = meta["target"].string?.parseAsName(false)
|
||||
set(value) {
|
||||
meta["target"] = value?.toString()?.asValue()
|
||||
}
|
||||
// /**
|
||||
// * An override for data path. By default uses [prefix]
|
||||
// */
|
||||
// public var dataPath: String? by string()
|
||||
//
|
||||
// /**
|
||||
// * Target page name with a given language key
|
||||
// */
|
||||
// public var target: Name?
|
||||
// get() = meta["target"].string?.parseAsName(false)
|
||||
// set(value) {
|
||||
// meta["target"] = value?.toString()?.asValue()
|
||||
// }
|
||||
|
||||
public companion object : SchemeSpec<Language>(::Language) {
|
||||
|
||||
@ -45,97 +57,122 @@ public class Language : Scheme() {
|
||||
|
||||
public val SITE_LANGUAGE_KEY: Name = SiteContext.SITE_META_KEY + LANGUAGE_KEY
|
||||
|
||||
public val SITE_LANGUAGES_KEY: Name = SiteContext.SITE_META_KEY + LANGUAGE_MAP_KEY
|
||||
public val SITE_LANGUAGE_MAP_KEY: Name = SiteContext.SITE_META_KEY + LANGUAGE_MAP_KEY
|
||||
|
||||
public const val DEFAULT_LANGUAGE: String = "en"
|
||||
|
||||
// /**
|
||||
// * Automatically build a language map for a data piece with given [name] based on existence of appropriate data nodes.
|
||||
// */
|
||||
// context(PageContextWithData)
|
||||
// public fun languageMapFor(name: Name): Meta = Meta {
|
||||
// val currentLanguagePrefix = languages[language]?.get(Language::prefix.name)?.string ?: language
|
||||
// val fullName = (site.route.removeFirstOrNull(currentLanguagePrefix.asName()) ?: site.route) + name
|
||||
// languages.forEach { (key, meta) ->
|
||||
// val languagePrefix: String = meta[Language::prefix.name].string ?: key
|
||||
// val nameWithLanguage: Name = if (languagePrefix.isBlank()) {
|
||||
// fullName
|
||||
// } else {
|
||||
// languagePrefix.asName() + fullName
|
||||
// }
|
||||
// if (data.resolveHtmlOrNull(name) != null) {
|
||||
// key put meta.asMutableMeta().apply {
|
||||
// Language::target.name put nameWithLanguage.toString()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public fun Language(prefix: String): Language = Language { this.prefix = prefix }
|
||||
public fun Language(
|
||||
key: String,
|
||||
route: Name = key.parseAsName(true),
|
||||
modifier: Language.() -> Unit = {},
|
||||
): Language = Language {
|
||||
this.key = key
|
||||
this.route = route
|
||||
modifier()
|
||||
}
|
||||
|
||||
public val SiteContext.languages: Map<String, Meta>
|
||||
get() = siteMeta[SITE_LANGUAGES_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||
internal val Data<*>.language: String?
|
||||
get() = meta[Language.LANGUAGE_KEY].string?.lowercase()
|
||||
|
||||
public val SiteContext.languageMap: Map<String, Language>
|
||||
get() = siteMeta[SITE_LANGUAGE_MAP_KEY]?.items?.map {
|
||||
it.key.toStringUnescaped() to Language.read(it.value)
|
||||
}?.toMap() ?: emptyMap()
|
||||
|
||||
public val SiteContext.language: String
|
||||
get() = siteMeta[SITE_LANGUAGE_KEY].string ?: Language.DEFAULT_LANGUAGE
|
||||
//
|
||||
//public val SiteContext.languagePrefix: Name
|
||||
// get() = languages[language]?.let { it[Language::prefix.name].string ?: language }?.parseAsName() ?: Name.EMPTY
|
||||
|
||||
|
||||
private class LanguageBranchAction(val prefix: Name) : AbstractAction<Any, Any>(typeOf<Any>()) {
|
||||
override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) {
|
||||
data.forEach { (name, item) ->
|
||||
val nameWithoutPrefix = name.removeFirstOrNull(prefix)
|
||||
if (nameWithoutPrefix != null) {
|
||||
// put language item as is
|
||||
put(nameWithoutPrefix, item)
|
||||
} else if (data[name + prefix] == null) {
|
||||
// put if language item is missing
|
||||
put(name, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun DataSink<Any>.update(source: DataTree<Any>, meta: Meta, namedData: NamedData<Any>) {
|
||||
val name = namedData.name
|
||||
val nameWithoutPrefix = name.removeFirstOrNull(prefix)
|
||||
if (nameWithoutPrefix != null) {
|
||||
// put language item as is
|
||||
put(nameWithoutPrefix, namedData.data)
|
||||
} else if (source[name + prefix] == null) {
|
||||
// put if language item is missing
|
||||
put(name, namedData.data)
|
||||
}
|
||||
/**
|
||||
* Walk the data tree depth-first
|
||||
*/
|
||||
private fun <T, TR : GenericDataTree<T, TR>> TR.walk(
|
||||
namePrefix: Name = Name.EMPTY,
|
||||
): Sequence<Pair<Name, TR>> = sequence {
|
||||
yield(namePrefix to this@walk)
|
||||
items.forEach { (token, tree) ->
|
||||
yieldAll(tree.walk(namePrefix + token))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multiple sites for different languages. All sites use the same [content], but rely on different data
|
||||
*
|
||||
* @param data a common data root for all sites
|
||||
*/
|
||||
@SnarkBuilder
|
||||
public fun SiteContext.multiLanguageSite(
|
||||
data: DataTree<*>,
|
||||
languageMap: Map<String, Language>,
|
||||
content: HtmlSite,
|
||||
) {
|
||||
languageMap.forEach { (languageKey, language) ->
|
||||
val prefix = language.prefix ?: languageKey
|
||||
val languageSiteMeta = Meta {
|
||||
SITE_LANGUAGE_KEY put languageKey
|
||||
SITE_LANGUAGES_KEY put Meta {
|
||||
languageMap.forEach {
|
||||
it.key put it.value
|
||||
private class LanguageMapAction(val languages: Set<Language>) : AbstractAction<Any, Any>(typeOf<Any>()) {
|
||||
override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) {
|
||||
val languageMapCache = mutableMapOf<Name, MutableMap<Language, DataTree<Any>>>()
|
||||
|
||||
data.walk().forEach { (name, node) ->
|
||||
val language = node.data?.language?.let { itemLanguage -> languages.find { it.key == itemLanguage } }
|
||||
if (language == null) {
|
||||
// put data without a language into all buckets
|
||||
languageMapCache[name] = languages.associateWithTo(HashMap()) { node }
|
||||
} else {
|
||||
// collect data with language markers
|
||||
val nameWithoutPrefix = if (name.startsWith(language.dataPath)) name.cutFirst() else name
|
||||
languageMapCache.getOrPut(nameWithoutPrefix) { mutableMapOf() }[language] = node
|
||||
}
|
||||
}
|
||||
|
||||
languageMapCache.forEach { (nodeName, languageMap) ->
|
||||
val languageMapMeta = Meta {
|
||||
languageMap.keys.forEach { language ->
|
||||
set(language.key, (language.route + nodeName).toWebPath())
|
||||
}
|
||||
}
|
||||
languageMap.forEach { (language, node) ->
|
||||
val languagePrefix = language.dataPath
|
||||
val nodeData = node.data
|
||||
if (nodeData != null) {
|
||||
put(
|
||||
languagePrefix + nodeName,
|
||||
nodeData.withMeta { set(Language.LANGUAGE_MAP_KEY, languageMapMeta) }
|
||||
)
|
||||
} else {
|
||||
wrap(languagePrefix + nodeName, Unit, Meta { set(Language.LANGUAGE_MAP_KEY, languageMapMeta) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun DataSink<Any>.update(source: DataTree<Any>, meta: Meta, namedData: NamedData<Any>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a multiple sites for different languages. All sites use the same [content], but rely on different data
|
||||
*/
|
||||
@SnarkBuilder
|
||||
public fun SiteContextWithData.multiLanguageSite(
|
||||
defaultLanguage: Language,
|
||||
vararg languages: Language,
|
||||
content: HtmlSite,
|
||||
) {
|
||||
val languageSet = setOf(defaultLanguage, *languages)
|
||||
|
||||
val languageMappedData = siteData.filterByType<Any>().transform(
|
||||
LanguageMapAction(languageSet)
|
||||
)
|
||||
|
||||
languageSet.forEach { language ->
|
||||
val languageSiteMeta = Meta {
|
||||
SITE_LANGUAGE_KEY put language.key
|
||||
SITE_LANGUAGE_MAP_KEY put Meta {
|
||||
languageSet.forEach {
|
||||
it.key put it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val overlayData = DataTreeWithDefault<Any>(
|
||||
languageMappedData.branch(language.dataPath)!!,
|
||||
languageMappedData.branch(defaultLanguage.dataPath)!!
|
||||
)
|
||||
|
||||
site(
|
||||
prefix.parseAsName(),
|
||||
data.filterByType<Any>().transform(LanguageBranchAction(Name.parse(language.dataPath ?: prefix))),
|
||||
language.route,
|
||||
overlayData,
|
||||
siteMeta = Laminate(languageSiteMeta, siteMeta),
|
||||
content
|
||||
)
|
||||
@ -151,35 +188,10 @@ public val PageContext.language: String
|
||||
/**
|
||||
* Mapping of language keys to other language versions of this page
|
||||
*/
|
||||
public fun PageContext.getLanguageMap(): Map<String, Meta> =
|
||||
pageMeta[Language.LANGUAGE_MAP_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||
public val PageContext.languageMap: Map<String, Meta>
|
||||
get() = pageMeta[Language.LANGUAGE_MAP_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||
|
||||
public fun PageContext.localisedPageRef(pageName: Name, relative: Boolean = false): String {
|
||||
val prefix = getLanguageMap()[language]?.get(Language::prefix.name)?.string?.parseAsName() ?: Name.EMPTY
|
||||
return resolvePageRef(prefix + pageName, relative)
|
||||
}
|
||||
|
||||
//
|
||||
///**
|
||||
// * Render all pages in a node with given name. Use localization prefix if appropriate data is available.
|
||||
// */
|
||||
//public fun SiteContext.localizedPages(
|
||||
// dataPath: Name,
|
||||
// remotePath: Name = dataPath,
|
||||
// dataRenderer: DataRenderer = DataRenderer.DEFAULT,
|
||||
//) {
|
||||
// val item = resolveData.getItem(languagePrefix + dataPath)
|
||||
// ?: resolveData.getItem(dataPath)
|
||||
// ?: error("No data found by name $dataPath")
|
||||
// route(remotePath) {
|
||||
// pages(item, dataRenderer)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public fun SiteContext.localizedPages(
|
||||
// dataPath: String,
|
||||
// remotePath: Name = dataPath.parseAsName(),
|
||||
// dataRenderer: DataRenderer = DataRenderer.DEFAULT,
|
||||
//) {
|
||||
// localizedPages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
|
||||
//}
|
||||
public fun PageContext.localisedPageRef(pageName: Name): String {
|
||||
val prefix = languageMap[language]?.get(Language::dataPath.name)?.string?.parseAsName() ?: Name.EMPTY
|
||||
return resolvePageRef(prefix + pageName)
|
||||
}
|
@ -17,4 +17,4 @@ public fun <T> Data<T>.withMeta(newMeta: Meta): Data<T> = if (this is MetaMaskDa
|
||||
MetaMaskData(this, newMeta)
|
||||
}
|
||||
|
||||
public inline fun <T> Data<T>.mapMeta(block: MutableMeta.() -> Unit): Data<T> = withMeta(meta.copy(block))
|
||||
public inline fun <T> Data<T>.withMeta(block: MutableMeta.() -> Unit): Data<T> = withMeta(meta.copy(block))
|
@ -1,13 +1,13 @@
|
||||
package space.kscience.snark.html
|
||||
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.Url
|
||||
import io.ktor.http.appendPathSegments
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.hasIndex
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.snark.SnarkBuilder
|
||||
import space.kscience.snark.SnarkContext
|
||||
|
||||
@ -27,32 +27,49 @@ public interface PageContext : SnarkContext {
|
||||
|
||||
public val site: SiteContext
|
||||
|
||||
public val host: Url
|
||||
|
||||
/**
|
||||
* A metadata for a page. It should include site metadata
|
||||
*/
|
||||
public val pageMeta: Meta
|
||||
|
||||
/**
|
||||
* Resolve absolute url for given [ref]
|
||||
* A route relative to parent site. Includes [SiteContext.siteRoute].
|
||||
*/
|
||||
public val pageRoute: Name
|
||||
|
||||
/**
|
||||
* Resolve absolute url for given relative [ref]
|
||||
*
|
||||
*/
|
||||
public fun resolveRef(ref: String): String
|
||||
public fun resolveRef(ref: String, targetSite: SiteContext = site): String {
|
||||
val pageUrl = URLBuilder(host)
|
||||
.appendPathSegments(targetSite.path, true)
|
||||
.appendPathSegments(ref)
|
||||
|
||||
return pageUrl.buildString().removeSuffix("/")
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve absolute url for a page with given [pageName].
|
||||
*
|
||||
* @param relative if true, add [SiteContext] route to the absolute page name
|
||||
*/
|
||||
public fun resolvePageRef(pageName: Name, relative: Boolean = false): String
|
||||
public fun resolvePageRef(pageName: Name): String
|
||||
|
||||
public fun resolveRelativePageRef(pageName: Name): String = resolvePageRef(pageRoute + pageName)
|
||||
|
||||
}
|
||||
|
||||
context(PageContext)
|
||||
public val page: PageContext
|
||||
get() = this@PageContext
|
||||
|
||||
public fun PageContext.resolvePageRef(pageName: String): String = resolvePageRef(pageName.parseAsName())
|
||||
public fun PageContext.resolvePageRef(pageName: String): String =
|
||||
resolvePageRef(pageName.parseAsName())
|
||||
|
||||
public val PageContext.homeRef: String get() = resolvePageRef(SiteContext.INDEX_PAGE_TOKEN.asName())
|
||||
public val PageContext.homeRef: String get() = resolvePageRef(Name.EMPTY)
|
||||
|
||||
public val PageContext.name: Name? get() = pageMeta["name"].string?.parseAsName()
|
||||
|
||||
|
@ -36,10 +36,6 @@ 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
|
||||
|
@ -12,7 +12,7 @@ import kotlin.reflect.typeOf
|
||||
public class ParseAction(private val snarkHtml: SnarkHtml) :
|
||||
AbstractAction<Binary, PageFragment>(typeOf<PageFragment>()) {
|
||||
|
||||
private fun parseOne(data: NamedData<Binary>, actionMeta: Meta): NamedData<PageFragment>? = with(snarkHtml) {
|
||||
private fun parseOne(data: NamedData<Binary>): NamedData<PageFragment>? = with(snarkHtml) {
|
||||
val contentType = getContentType(data.name, data.meta)
|
||||
|
||||
val parser = snark.readers.values.filterIsInstance<SnarkHtmlReader>().filter { parser ->
|
||||
@ -40,11 +40,11 @@ public class ParseAction(private val snarkHtml: SnarkHtml) :
|
||||
|
||||
override fun DataSink<PageFragment>.generate(data: DataTree<Binary>, meta: Meta) {
|
||||
data.forEach {
|
||||
parseOne(it, meta)?.let { put(it) }
|
||||
parseOne(it)?.let { put(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun DataSink<PageFragment>.update(source: DataTree<Binary>, meta: Meta, namedData: NamedData<Binary>) {
|
||||
parseOne(namedData,meta)?.let { put(it) }
|
||||
parseOne(namedData)?.let { put(it) }
|
||||
}
|
||||
}
|
@ -77,9 +77,12 @@ public class Postprocessor<out R>(
|
||||
}
|
||||
|
||||
context(PageContext)
|
||||
public inline fun FlowContent.postprocess(block: FlowContent.() -> Unit) {
|
||||
public inline fun FlowContent.postprocess(
|
||||
processor: TextProcessor = WebPageTextProcessor(page),
|
||||
block: FlowContent.() -> Unit,
|
||||
) {
|
||||
val fc = object : FlowContent by this {
|
||||
override val consumer: TagConsumer<*> = Postprocessor(page, this@postprocess.consumer)
|
||||
override val consumer: TagConsumer<*> = Postprocessor(page, this@postprocess.consumer, processor)
|
||||
}
|
||||
fc.block()
|
||||
}
|
@ -15,11 +15,15 @@ import space.kscience.snark.SnarkContext
|
||||
*/
|
||||
@SnarkBuilder
|
||||
public interface SiteContext : SnarkContext {
|
||||
/**
|
||||
* A context path segments for this site
|
||||
*/
|
||||
public val path: List<String>
|
||||
|
||||
/**
|
||||
* Route name of this [SiteContext] relative to the site root
|
||||
*/
|
||||
public val route: Name
|
||||
public val siteRoute: Name
|
||||
|
||||
/**
|
||||
* Site configuration
|
||||
@ -49,7 +53,7 @@ public interface SiteContext : SnarkContext {
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a route block with its own data. Does not change base url
|
||||
* Create a route block with its own data. Does not change the context path
|
||||
*/
|
||||
@SnarkBuilder
|
||||
public fun route(
|
||||
@ -60,7 +64,7 @@ public interface SiteContext : SnarkContext {
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a sub-site and sets it as site base url
|
||||
* Creates a sub-site and changes context path to match [name]
|
||||
* @param route mount site at [rootName]
|
||||
*/
|
||||
@SnarkBuilder
|
||||
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.snark.html.static
|
||||
|
||||
import io.ktor.http.Url
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.io.asSink
|
||||
@ -10,7 +11,6 @@ import space.kscience.dataforge.io.writeBinary
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.workspace.FileData
|
||||
import space.kscience.snark.html.*
|
||||
import java.nio.file.Path
|
||||
@ -23,8 +23,9 @@ import kotlin.reflect.typeOf
|
||||
*/
|
||||
internal class StaticSiteContext(
|
||||
override val siteMeta: Meta,
|
||||
private val baseUrl: String,
|
||||
override val route: Name,
|
||||
private val baseUrl: Url,
|
||||
override val path: List<String>,
|
||||
override val siteRoute: Name,
|
||||
private val outputPath: Path,
|
||||
) : SiteContext {
|
||||
|
||||
@ -82,21 +83,23 @@ internal class StaticSiteContext(
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveRef(baseUrl: String, ref: String) = if (baseUrl.isEmpty()) {
|
||||
ref
|
||||
} else if (ref.isEmpty()) {
|
||||
baseUrl
|
||||
} else {
|
||||
"${baseUrl.removeSuffix("/")}/$ref"
|
||||
}
|
||||
// private fun resolveRef(baseUrl: String, ref: String) = if (baseUrl.isEmpty()) {
|
||||
// ref
|
||||
// } else if (ref.isEmpty()) {
|
||||
// baseUrl
|
||||
// } else {
|
||||
// "${baseUrl.removeSuffix("/")}/$ref"
|
||||
// }
|
||||
|
||||
class StaticPageContext(override val site: StaticSiteContext, override val pageMeta: Meta) : PageContext {
|
||||
class StaticPageContext(
|
||||
override val site: StaticSiteContext,
|
||||
override val host: Url,
|
||||
override val pageRoute: Name,
|
||||
override val pageMeta: Meta,
|
||||
) : PageContext {
|
||||
|
||||
override fun resolveRef(ref: String): String =
|
||||
site.resolveRef(site.baseUrl, ref)
|
||||
|
||||
override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef(
|
||||
(if (relative) site.route + pageName else pageName).toWebPath() + ".html"
|
||||
override fun resolvePageRef(pageName: Name): String = resolveRef(
|
||||
pageName.toWebPath() + ".html"
|
||||
)
|
||||
}
|
||||
|
||||
@ -115,7 +118,7 @@ internal class StaticSiteContext(
|
||||
|
||||
newPath.parent.createDirectories()
|
||||
|
||||
val pageContext = StaticPageContext(this, Laminate(modifiedPageMeta, siteMeta))
|
||||
val pageContext = StaticPageContext(this, baseUrl, route, Laminate(modifiedPageMeta, siteMeta))
|
||||
newPath.writeText(HtmlPage.createHtmlString(pageContext, data, content))
|
||||
}
|
||||
|
||||
@ -124,7 +127,8 @@ internal class StaticSiteContext(
|
||||
StaticSiteContext(
|
||||
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
||||
baseUrl = baseUrl,
|
||||
route = route,
|
||||
path = emptyList(),
|
||||
siteRoute = route,
|
||||
outputPath = outputPath.resolve(route.toWebPath())
|
||||
),
|
||||
data ?: DataTree.EMPTY
|
||||
@ -140,8 +144,9 @@ internal class StaticSiteContext(
|
||||
val siteContextWithData = SiteContextWithData(
|
||||
StaticSiteContext(
|
||||
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
||||
baseUrl = if (baseUrl == "") "" else resolveRef(baseUrl, route.toWebPath()),
|
||||
route = Name.EMPTY,
|
||||
baseUrl = baseUrl,
|
||||
path = emptyList(),
|
||||
siteRoute = route,
|
||||
outputPath = outputPath.resolve(route.toWebPath())
|
||||
),
|
||||
data ?: DataTree.EMPTY
|
||||
@ -164,12 +169,12 @@ internal class StaticSiteContext(
|
||||
public suspend fun SnarkHtml.staticSite(
|
||||
data: DataTree<*>?,
|
||||
outputPath: Path,
|
||||
siteUrl: String = outputPath.absolutePathString().replace("\\", "/"),
|
||||
siteUrl: Url = Url(outputPath.absolutePathString()),
|
||||
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
||||
content: HtmlSite,
|
||||
) {
|
||||
val siteContextWithData = SiteContextWithData(
|
||||
StaticSiteContext(siteMeta, siteUrl, Name.EMPTY, outputPath),
|
||||
StaticSiteContext(siteMeta, siteUrl, emptyList(), Name.EMPTY, outputPath),
|
||||
data ?: DataTree.EMPTY
|
||||
)
|
||||
with(content) {
|
||||
|
@ -20,10 +20,7 @@ import space.kscience.dataforge.data.await
|
||||
import space.kscience.dataforge.data.meta
|
||||
import space.kscience.dataforge.io.Binary
|
||||
import space.kscience.dataforge.io.toByteArray
|
||||
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.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.cutLast
|
||||
import space.kscience.dataforge.names.endsWith
|
||||
@ -37,11 +34,11 @@ import kotlin.reflect.typeOf
|
||||
// style = CssBuilder().block().toString()
|
||||
//}
|
||||
|
||||
public class KtorSiteContext(
|
||||
internal class KtorSiteContext(
|
||||
override val context: Context,
|
||||
override val siteMeta: Meta,
|
||||
private val baseUrl: String,
|
||||
override val route: Name,
|
||||
override val path: List<String>,
|
||||
override val siteRoute: Name,
|
||||
private val ktorRoute: Route,
|
||||
) : SiteContext, ContextAware {
|
||||
|
||||
@ -77,32 +74,21 @@ public class KtorSiteContext(
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveRef(baseUrl: String, ref: String) = if (baseUrl.isEmpty()) {
|
||||
ref
|
||||
} else if (ref.isEmpty()) {
|
||||
baseUrl
|
||||
} else {
|
||||
"${baseUrl.removeSuffix("/")}/$ref"
|
||||
}
|
||||
|
||||
|
||||
private class KtorPageContext(
|
||||
override val site: KtorSiteContext,
|
||||
val pageBaseUrl: String,
|
||||
override val host: Url,
|
||||
override val pageRoute: Name,
|
||||
override val pageMeta: Meta,
|
||||
) : PageContext {
|
||||
|
||||
override fun resolveRef(ref: String): String = site.resolveRef(pageBaseUrl, ref)
|
||||
|
||||
override fun resolvePageRef(
|
||||
pageName: Name,
|
||||
relative: Boolean,
|
||||
): String {
|
||||
val fullPageName = if (relative) site.route + pageName else pageName
|
||||
return if (fullPageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) {
|
||||
resolveRef(fullPageName.cutLast().toWebPath())
|
||||
return if (pageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) {
|
||||
resolveRef(pageName.cutLast().toWebPath())
|
||||
} else {
|
||||
resolveRef(fullPageName.toWebPath())
|
||||
resolveRef(pageName.toWebPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,18 +97,30 @@ public class KtorSiteContext(
|
||||
ktorRoute.get(route.toWebPath()) {
|
||||
val request = call.request
|
||||
//substitute host for url for backwards calls
|
||||
val url = URLBuilder(baseUrl).apply {
|
||||
// val url = URLBuilder(baseUrl).apply {
|
||||
// protocol = URLProtocol.createOrDefault(request.origin.scheme)
|
||||
// host = request.origin.serverHost
|
||||
// port = request.origin.serverPort
|
||||
// }
|
||||
|
||||
val hostUrl = URLBuilder().apply {
|
||||
protocol = URLProtocol.createOrDefault(request.origin.scheme)
|
||||
host = request.origin.serverHost
|
||||
port = request.origin.serverPort
|
||||
}
|
||||
|
||||
val modifiedPageMeta = pageMeta.toMutableMeta().apply {
|
||||
"name" put route.toString()
|
||||
"url" put url.buildString()
|
||||
val modifiedPageMeta = pageMeta.copy {
|
||||
"host" put hostUrl.buildString()
|
||||
"path" put path.map { it.asValue() }.asValue()
|
||||
"route" put (siteRoute + route).toString()
|
||||
}
|
||||
val pageContext =
|
||||
KtorPageContext(this@KtorSiteContext, url.buildString(), Laminate(modifiedPageMeta, siteMeta))
|
||||
|
||||
val pageContext = KtorPageContext(
|
||||
site = this@KtorSiteContext,
|
||||
host = hostUrl.build(),
|
||||
pageRoute = siteRoute + route,
|
||||
pageMeta = Laminate(modifiedPageMeta, siteMeta)
|
||||
)
|
||||
//render page in suspend environment
|
||||
val html = HtmlPage.createHtmlString(pageContext, data, content)
|
||||
|
||||
@ -135,8 +133,8 @@ public class KtorSiteContext(
|
||||
KtorSiteContext(
|
||||
context,
|
||||
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
||||
baseUrl = baseUrl,
|
||||
route = route,
|
||||
path = path,
|
||||
siteRoute = route,
|
||||
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
||||
),
|
||||
data ?: DataTree.EMPTY
|
||||
@ -153,11 +151,11 @@ public class KtorSiteContext(
|
||||
KtorSiteContext(
|
||||
context,
|
||||
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
||||
baseUrl = resolveRef(baseUrl, route.toWebPath()),
|
||||
route = Name.EMPTY,
|
||||
path = path + route.tokens.map { it.toStringUnescaped() },
|
||||
siteRoute = Name.EMPTY,
|
||||
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
||||
),
|
||||
data?: DataTree.EMPTY
|
||||
data ?: DataTree.EMPTY
|
||||
)
|
||||
with(content) {
|
||||
with(siteContext) {
|
||||
@ -171,13 +169,13 @@ public class KtorSiteContext(
|
||||
public fun Route.site(
|
||||
context: Context,
|
||||
data: DataTree<*>?,
|
||||
baseUrl: String = "",
|
||||
path: List<String> = emptyList(),
|
||||
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
||||
content: HtmlSite,
|
||||
) {
|
||||
val siteContext = SiteContextWithData(
|
||||
KtorSiteContext(context, siteMeta, baseUrl, route = Name.EMPTY, this@Route),
|
||||
data?: DataTree.EMPTY
|
||||
KtorSiteContext(context, siteMeta, path = path, siteRoute = Name.EMPTY, this@Route),
|
||||
data ?: DataTree.EMPTY
|
||||
)
|
||||
with(content) {
|
||||
with(siteContext) {
|
||||
|
Loading…
Reference in New Issue
Block a user