Hack to fix language switch

This commit is contained in:
Alexander Nozik 2024-02-28 22:06:39 +03:00
parent 35cd0e828a
commit eddeea8758
12 changed files with 234 additions and 203 deletions

View File

@ -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

View File

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

View File

@ -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
}

View File

@ -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) }

View File

@ -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(
namePrefix: Name = Name.EMPTY,
): Sequence<Pair<Name, TR>> = sequence {
yield(namePrefix to this@walk)
items.forEach { (token, tree) ->
yieldAll(tree.walk(namePrefix + token))
}
}
private class LanguageMapAction(val languages: Set<Language>) : AbstractAction<Any, Any>(typeOf<Any>()) {
private class LanguageBranchAction(val prefix: Name) : AbstractAction<Any, Any>(typeOf<Any>()) {
override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) { override fun DataSink<Any>.generate(data: DataTree<Any>, meta: Meta) {
data.forEach { (name, item) -> val languageMapCache = mutableMapOf<Name, MutableMap<Language, DataTree<Any>>>()
val nameWithoutPrefix = name.removeFirstOrNull(prefix)
if (nameWithoutPrefix != null) { data.walk().forEach { (name, node) ->
// put language item as is val language = node.data?.language?.let { itemLanguage -> languages.find { it.key == itemLanguage } }
put(nameWithoutPrefix, item) if (language == null) {
} else if (data[name + prefix] == null) { // put data without a language into all buckets
// put if language item is missing languageMapCache[name] = languages.associateWithTo(HashMap()) { node }
put(name, item) } 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>) { override fun DataSink<Any>.update(source: DataTree<Any>, meta: Meta, namedData: NamedData<Any>) {
val name = namedData.name TODO("Not yet implemented")
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 * Create a multiple sites for different languages. All sites use the same [content], but rely on different data
put(name, namedData.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>(
* Create a multiple sites for different languages. All sites use the same [content], but rely on different data languageMappedData.branch(language.dataPath)!!,
* languageMappedData.branch(defaultLanguage.dataPath)!!
* @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_LANGUAGE_MAP_KEY put Meta {
languageMap.forEach {
it.key put it.value
}
}
}
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)
//}

View File

@ -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))

View File

@ -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()

View File

@ -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

View File

@ -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) }
} }
} }

View File

@ -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

View File

@ -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) {

View File

@ -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) {