Hack to fix language switch
This commit is contained in:
parent
35cd0e828a
commit
eddeea8758
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
rootProject.name = "snark"
|
rootProject.name = "snark"
|
||||||
|
|
||||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
//enableFeaturePreview("VERSION_CATALOGS")
|
|
||||||
|
|
||||||
pluginManagement {
|
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,6 +5,7 @@ import space.kscience.dataforge.actions.transform
|
|||||||
import space.kscience.dataforge.data.*
|
import space.kscience.dataforge.data.*
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
|
import space.kscience.snark.DataTreeWithDefault
|
||||||
import space.kscience.snark.SnarkBuilder
|
import space.kscience.snark.SnarkBuilder
|
||||||
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_KEY
|
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_KEY
|
||||||
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_MAP_KEY
|
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_MAP_KEY
|
||||||
@ -16,26 +17,37 @@ public class Language : Scheme() {
|
|||||||
/**
|
/**
|
||||||
* Language key override
|
* 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
|
// * An override for data path. By default uses [prefix]
|
||||||
*/
|
// */
|
||||||
public var target: Name?
|
// public var dataPath: String? by string()
|
||||||
get() = meta["target"].string?.parseAsName(false)
|
//
|
||||||
set(value) {
|
// /**
|
||||||
meta["target"] = value?.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()
|
||||||
|
// }
|
||||||
|
|
||||||
public companion object : SchemeSpec<Language>(::Language) {
|
public companion object : SchemeSpec<Language>(::Language) {
|
||||||
|
|
||||||
@ -48,32 +60,21 @@ public class Language : Scheme() {
|
|||||||
public val SITE_LANGUAGE_MAP_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"
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val Data<*>.language: String?
|
||||||
|
get() = meta[Language.LANGUAGE_KEY].string?.lowercase()
|
||||||
|
|
||||||
public val SiteContext.languageMap: Map<String, Language>
|
public val SiteContext.languageMap: Map<String, Language>
|
||||||
get() = siteMeta[SITE_LANGUAGE_MAP_KEY]?.items?.map {
|
get() = siteMeta[SITE_LANGUAGE_MAP_KEY]?.items?.map {
|
||||||
@ -83,61 +84,95 @@ public val SiteContext.languageMap: Map<String, Language>
|
|||||||
public val SiteContext.language: String
|
public val SiteContext.language: String
|
||||||
get() = siteMeta[SITE_LANGUAGE_KEY].string ?: Language.DEFAULT_LANGUAGE
|
get() = siteMeta[SITE_LANGUAGE_KEY].string ?: Language.DEFAULT_LANGUAGE
|
||||||
|
|
||||||
public val SiteContext.languagePrefix: Name
|
/**
|
||||||
get() = languageMap[language]?.let { it.prefix ?: language }?.parseAsName() ?: Name.EMPTY
|
* Walk the data tree depth-first
|
||||||
|
*/
|
||||||
|
private fun <T, TR : GenericDataTree<T, TR>> TR.walk(
|
||||||
private class LanguageBranchAction(val prefix: Name) : AbstractAction<Any, Any>(typeOf<Any>()) {
|
namePrefix: Name = Name.EMPTY,
|
||||||
override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) {
|
): Sequence<Pair<Name, TR>> = sequence {
|
||||||
data.forEach { (name, item) ->
|
yield(namePrefix to this@walk)
|
||||||
val nameWithoutPrefix = name.removeFirstOrNull(prefix)
|
items.forEach { (token, tree) ->
|
||||||
if (nameWithoutPrefix != null) {
|
yieldAll(tree.walk(namePrefix + token))
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private class LanguageMapAction(val languages: Set<Language>) : AbstractAction<Any, Any>(typeOf<Any>()) {
|
||||||
* Create a multiple sites for different languages. All sites use the same [content], but rely on different data
|
override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) {
|
||||||
*
|
val languageMapCache = mutableMapOf<Name, MutableMap<Language, DataTree<Any>>>()
|
||||||
* @param data a common data root for all sites
|
|
||||||
*/
|
data.walk().forEach { (name, node) ->
|
||||||
@SnarkBuilder
|
val language = node.data?.language?.let { itemLanguage -> languages.find { it.key == itemLanguage } }
|
||||||
public fun SiteContext.multiLanguageSite(
|
if (language == null) {
|
||||||
data: DataTree<*>,
|
// put data without a language into all buckets
|
||||||
languageMap: Map<String, Language>,
|
languageMapCache[name] = languages.associateWithTo(HashMap()) { node }
|
||||||
content: HtmlSite,
|
} else {
|
||||||
) {
|
// collect data with language markers
|
||||||
languageMap.forEach { (languageKey, language) ->
|
val nameWithoutPrefix = if (name.startsWith(language.dataPath)) name.cutFirst() else name
|
||||||
val prefix = language.prefix ?: languageKey
|
languageMapCache.getOrPut(nameWithoutPrefix) { mutableMapOf() }[language] = node
|
||||||
val languageSiteMeta = Meta {
|
}
|
||||||
SITE_LANGUAGE_KEY put languageKey
|
}
|
||||||
SITE_LANGUAGE_MAP_KEY put Meta {
|
|
||||||
languageMap.forEach {
|
languageMapCache.forEach { (nodeName, languageMap) ->
|
||||||
it.key put it.value
|
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(
|
site(
|
||||||
prefix.parseAsName(),
|
language.route,
|
||||||
data.filterByType<Any>().transform(LanguageBranchAction(Name.parse(language.dataPath ?: prefix))),
|
overlayData,
|
||||||
siteMeta = Laminate(languageSiteMeta, siteMeta),
|
siteMeta = Laminate(languageSiteMeta, siteMeta),
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
@ -156,32 +191,7 @@ public val PageContext.language: String
|
|||||||
public val PageContext.languageMap: Map<String, Meta>
|
public val PageContext.languageMap: Map<String, Meta>
|
||||||
get() = pageMeta[Language.LANGUAGE_MAP_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
get() = pageMeta[Language.LANGUAGE_MAP_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||||
|
|
||||||
public fun PageContext.localisedPageRef(pageName: Name, relative: Boolean = false): String {
|
public fun PageContext.localisedPageRef(pageName: Name): String {
|
||||||
val prefix = languageMap[language]?.get(Language::prefix.name)?.string?.parseAsName() ?: Name.EMPTY
|
val prefix = languageMap[language]?.get(Language::dataPath.name)?.string?.parseAsName() ?: Name.EMPTY
|
||||||
return resolvePageRef(prefix + pageName, relative)
|
return resolvePageRef(prefix + pageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * 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)
|
|
||||||
//}
|
|
@ -17,4 +17,4 @@ public fun <T> Data<T>.withMeta(newMeta: Meta): Data<T> = if (this is MetaMaskDa
|
|||||||
MetaMaskData(this, newMeta)
|
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
|
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.data.DataTree
|
||||||
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.asName
|
|
||||||
import space.kscience.dataforge.names.hasIndex
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import space.kscience.snark.SnarkBuilder
|
import space.kscience.snark.SnarkBuilder
|
||||||
import space.kscience.snark.SnarkContext
|
import space.kscience.snark.SnarkContext
|
||||||
|
|
||||||
@ -27,32 +27,49 @@ public interface PageContext : SnarkContext {
|
|||||||
|
|
||||||
public val site: SiteContext
|
public val site: SiteContext
|
||||||
|
|
||||||
|
public val host: Url
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A metadata for a page. It should include site metadata
|
* A metadata for a page. It should include site metadata
|
||||||
*/
|
*/
|
||||||
public val pageMeta: Meta
|
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].
|
* Resolve absolute url for a page with given [pageName].
|
||||||
*
|
*
|
||||||
* @param relative if true, add [SiteContext] route to the absolute page name
|
* @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)
|
context(PageContext)
|
||||||
public val page: PageContext
|
public val page: PageContext
|
||||||
get() = this@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()
|
public val PageContext.name: Name? get() = pageMeta["name"].string?.parseAsName()
|
||||||
|
|
||||||
|
@ -36,10 +36,6 @@ context(SnarkContext)
|
|||||||
public val Data<*>.id: String
|
public val Data<*>.id: String
|
||||||
get() = meta["id"]?.string ?: "block[${hashCode()}]"
|
get() = meta["id"]?.string ?: "block[${hashCode()}]"
|
||||||
|
|
||||||
context(SnarkContext)
|
|
||||||
public val Data<*>.language: String?
|
|
||||||
get() = meta["language"].string?.lowercase()
|
|
||||||
|
|
||||||
context(SnarkContext)
|
context(SnarkContext)
|
||||||
public val Data<*>.order: Int?
|
public val Data<*>.order: Int?
|
||||||
get() = meta["order"]?.int
|
get() = meta["order"]?.int
|
||||||
|
@ -12,7 +12,7 @@ import kotlin.reflect.typeOf
|
|||||||
public class ParseAction(private val snarkHtml: SnarkHtml) :
|
public class ParseAction(private val snarkHtml: SnarkHtml) :
|
||||||
AbstractAction<Binary, PageFragment>(typeOf<PageFragment>()) {
|
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 contentType = getContentType(data.name, data.meta)
|
||||||
|
|
||||||
val parser = snark.readers.values.filterIsInstance<SnarkHtmlReader>().filter { parser ->
|
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) {
|
override fun DataSink<PageFragment>.generate(data: DataTree<Binary>, meta: Meta) {
|
||||||
data.forEach {
|
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>) {
|
override fun DataSink<PageFragment>.update(source: DataTree<Binary>, meta: Meta, namedData: NamedData<Binary>) {
|
||||||
parseOne(namedData,meta)?.let { put(it) }
|
parseOne(namedData)?.let { put(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,11 +15,15 @@ import space.kscience.snark.SnarkContext
|
|||||||
*/
|
*/
|
||||||
@SnarkBuilder
|
@SnarkBuilder
|
||||||
public interface SiteContext : SnarkContext {
|
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
|
* Route name of this [SiteContext] relative to the site root
|
||||||
*/
|
*/
|
||||||
public val route: Name
|
public val siteRoute: Name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Site configuration
|
* 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
|
@SnarkBuilder
|
||||||
public fun route(
|
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]
|
* @param route mount site at [rootName]
|
||||||
*/
|
*/
|
||||||
@SnarkBuilder
|
@SnarkBuilder
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package space.kscience.snark.html.static
|
package space.kscience.snark.html.static
|
||||||
|
|
||||||
|
import io.ktor.http.Url
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.io.asSink
|
import kotlinx.io.asSink
|
||||||
@ -10,7 +11,6 @@ import space.kscience.dataforge.io.writeBinary
|
|||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.names.Name
|
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.workspace.FileData
|
import space.kscience.dataforge.workspace.FileData
|
||||||
import space.kscience.snark.html.*
|
import space.kscience.snark.html.*
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -23,8 +23,9 @@ import kotlin.reflect.typeOf
|
|||||||
*/
|
*/
|
||||||
internal class StaticSiteContext(
|
internal class StaticSiteContext(
|
||||||
override val siteMeta: Meta,
|
override val siteMeta: Meta,
|
||||||
private val baseUrl: String,
|
private val baseUrl: Url,
|
||||||
override val route: Name,
|
override val path: List<String>,
|
||||||
|
override val siteRoute: Name,
|
||||||
private val outputPath: Path,
|
private val outputPath: Path,
|
||||||
) : SiteContext {
|
) : SiteContext {
|
||||||
|
|
||||||
@ -82,21 +83,23 @@ internal class StaticSiteContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveRef(baseUrl: String, ref: String) = if (baseUrl.isEmpty()) {
|
// private fun resolveRef(baseUrl: String, ref: String) = if (baseUrl.isEmpty()) {
|
||||||
ref
|
// ref
|
||||||
} else if (ref.isEmpty()) {
|
// } else if (ref.isEmpty()) {
|
||||||
baseUrl
|
// baseUrl
|
||||||
} else {
|
// } else {
|
||||||
"${baseUrl.removeSuffix("/")}/$ref"
|
// "${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 =
|
override fun resolvePageRef(pageName: Name): String = resolveRef(
|
||||||
site.resolveRef(site.baseUrl, ref)
|
pageName.toWebPath() + ".html"
|
||||||
|
|
||||||
override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef(
|
|
||||||
(if (relative) site.route + pageName else pageName).toWebPath() + ".html"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +118,7 @@ internal class StaticSiteContext(
|
|||||||
|
|
||||||
newPath.parent.createDirectories()
|
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))
|
newPath.writeText(HtmlPage.createHtmlString(pageContext, data, content))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +127,8 @@ internal class StaticSiteContext(
|
|||||||
StaticSiteContext(
|
StaticSiteContext(
|
||||||
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
||||||
baseUrl = baseUrl,
|
baseUrl = baseUrl,
|
||||||
route = route,
|
path = emptyList(),
|
||||||
|
siteRoute = route,
|
||||||
outputPath = outputPath.resolve(route.toWebPath())
|
outputPath = outputPath.resolve(route.toWebPath())
|
||||||
),
|
),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
@ -140,8 +144,9 @@ internal class StaticSiteContext(
|
|||||||
val siteContextWithData = SiteContextWithData(
|
val siteContextWithData = SiteContextWithData(
|
||||||
StaticSiteContext(
|
StaticSiteContext(
|
||||||
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
|
||||||
baseUrl = if (baseUrl == "") "" else resolveRef(baseUrl, route.toWebPath()),
|
baseUrl = baseUrl,
|
||||||
route = Name.EMPTY,
|
path = emptyList(),
|
||||||
|
siteRoute = route,
|
||||||
outputPath = outputPath.resolve(route.toWebPath())
|
outputPath = outputPath.resolve(route.toWebPath())
|
||||||
),
|
),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
@ -164,12 +169,12 @@ internal class StaticSiteContext(
|
|||||||
public suspend fun SnarkHtml.staticSite(
|
public suspend fun SnarkHtml.staticSite(
|
||||||
data: DataTree<*>?,
|
data: DataTree<*>?,
|
||||||
outputPath: Path,
|
outputPath: Path,
|
||||||
siteUrl: String = outputPath.absolutePathString().replace("\\", "/"),
|
siteUrl: Url = Url(outputPath.absolutePathString()),
|
||||||
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
||||||
content: HtmlSite,
|
content: HtmlSite,
|
||||||
) {
|
) {
|
||||||
val siteContextWithData = SiteContextWithData(
|
val siteContextWithData = SiteContextWithData(
|
||||||
StaticSiteContext(siteMeta, siteUrl, Name.EMPTY, outputPath),
|
StaticSiteContext(siteMeta, siteUrl, emptyList(), Name.EMPTY, outputPath),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
)
|
)
|
||||||
with(content) {
|
with(content) {
|
||||||
|
@ -20,10 +20,7 @@ import space.kscience.dataforge.data.await
|
|||||||
import space.kscience.dataforge.data.meta
|
import space.kscience.dataforge.data.meta
|
||||||
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.Laminate
|
import space.kscience.dataforge.meta.*
|
||||||
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
|
||||||
@ -37,11 +34,11 @@ import kotlin.reflect.typeOf
|
|||||||
// style = CssBuilder().block().toString()
|
// style = CssBuilder().block().toString()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
public class KtorSiteContext(
|
internal class KtorSiteContext(
|
||||||
override val context: Context,
|
override val context: Context,
|
||||||
override val siteMeta: Meta,
|
override val siteMeta: Meta,
|
||||||
private val baseUrl: String,
|
override val path: List<String>,
|
||||||
override val route: Name,
|
override val siteRoute: Name,
|
||||||
private val ktorRoute: Route,
|
private val ktorRoute: Route,
|
||||||
) : SiteContext, ContextAware {
|
) : 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(
|
private class KtorPageContext(
|
||||||
override val site: KtorSiteContext,
|
override val site: KtorSiteContext,
|
||||||
val pageBaseUrl: String,
|
override val host: Url,
|
||||||
|
override val pageRoute: Name,
|
||||||
override val pageMeta: Meta,
|
override val pageMeta: Meta,
|
||||||
) : PageContext {
|
) : PageContext {
|
||||||
|
|
||||||
override fun resolveRef(ref: String): String = site.resolveRef(pageBaseUrl, ref)
|
|
||||||
|
|
||||||
override fun resolvePageRef(
|
override fun resolvePageRef(
|
||||||
pageName: Name,
|
pageName: Name,
|
||||||
relative: Boolean,
|
|
||||||
): String {
|
): String {
|
||||||
val fullPageName = if (relative) site.route + pageName else pageName
|
return if (pageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) {
|
||||||
return if (fullPageName.endsWith(SiteContext.INDEX_PAGE_TOKEN)) {
|
resolveRef(pageName.cutLast().toWebPath())
|
||||||
resolveRef(fullPageName.cutLast().toWebPath())
|
|
||||||
} else {
|
} else {
|
||||||
resolveRef(fullPageName.toWebPath())
|
resolveRef(pageName.toWebPath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,19 +97,28 @@ public class KtorSiteContext(
|
|||||||
ktorRoute.get(route.toWebPath()) {
|
ktorRoute.get(route.toWebPath()) {
|
||||||
val request = call.request
|
val request = call.request
|
||||||
//substitute host for url for backwards calls
|
//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)
|
protocol = URLProtocol.createOrDefault(request.origin.scheme)
|
||||||
host = request.origin.serverHost
|
host = request.origin.serverHost
|
||||||
port = request.origin.serverPort
|
port = request.origin.serverPort
|
||||||
}
|
}
|
||||||
|
|
||||||
val modifiedPageMeta = pageMeta.toMutableMeta().apply {
|
val modifiedPageMeta = pageMeta.copy {
|
||||||
"name" put route.toString()
|
"host" put hostUrl.buildString()
|
||||||
"url" put url.buildString()
|
"path" put path.map { it.asValue() }.asValue()
|
||||||
|
"route" put (siteRoute + route).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val pageContext = KtorPageContext(
|
val pageContext = KtorPageContext(
|
||||||
site = this@KtorSiteContext,
|
site = this@KtorSiteContext,
|
||||||
pageBaseUrl = url.buildString(),
|
host = hostUrl.build(),
|
||||||
|
pageRoute = siteRoute + route,
|
||||||
pageMeta = Laminate(modifiedPageMeta, siteMeta)
|
pageMeta = Laminate(modifiedPageMeta, siteMeta)
|
||||||
)
|
)
|
||||||
//render page in suspend environment
|
//render page in suspend environment
|
||||||
@ -138,8 +133,8 @@ public class KtorSiteContext(
|
|||||||
KtorSiteContext(
|
KtorSiteContext(
|
||||||
context,
|
context,
|
||||||
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
||||||
baseUrl = baseUrl,
|
path = path,
|
||||||
route = route,
|
siteRoute = route,
|
||||||
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
||||||
),
|
),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
@ -156,8 +151,8 @@ public class KtorSiteContext(
|
|||||||
KtorSiteContext(
|
KtorSiteContext(
|
||||||
context,
|
context,
|
||||||
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
siteMeta = Laminate(siteMeta, this@KtorSiteContext.siteMeta),
|
||||||
baseUrl = resolveRef(baseUrl, route.toWebPath()),
|
path = path + route.tokens.map { it.toStringUnescaped() },
|
||||||
route = Name.EMPTY,
|
siteRoute = Name.EMPTY,
|
||||||
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
ktorRoute = ktorRoute.createRouteFromPath(route.toWebPath())
|
||||||
),
|
),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
@ -174,12 +169,12 @@ public class KtorSiteContext(
|
|||||||
public fun Route.site(
|
public fun Route.site(
|
||||||
context: Context,
|
context: Context,
|
||||||
data: DataTree<*>?,
|
data: DataTree<*>?,
|
||||||
baseUrl: String = "",
|
path: List<String> = emptyList(),
|
||||||
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
siteMeta: Meta = data?.meta ?: Meta.EMPTY,
|
||||||
content: HtmlSite,
|
content: HtmlSite,
|
||||||
) {
|
) {
|
||||||
val siteContext = SiteContextWithData(
|
val siteContext = SiteContextWithData(
|
||||||
KtorSiteContext(context, siteMeta, baseUrl, route = Name.EMPTY, this@Route),
|
KtorSiteContext(context, siteMeta, path = path, siteRoute = Name.EMPTY, this@Route),
|
||||||
data ?: DataTree.EMPTY
|
data ?: DataTree.EMPTY
|
||||||
)
|
)
|
||||||
with(content) {
|
with(content) {
|
||||||
|
Loading…
Reference in New Issue
Block a user