diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..17655d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 04380ea..1ee2748 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,6 @@ rootProject.name = "snark" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -//enableFeaturePreview("VERSION_CATALOGS") pluginManagement { diff --git a/snark-core/src/commonMain/kotlin/space/kscience/snark/DataTreeWithDefault.kt b/snark-core/src/commonMain/kotlin/space/kscience/snark/DataTreeWithDefault.kt new file mode 100644 index 0000000..2fc5aca --- /dev/null +++ b/snark-core/src/commonMain/kotlin/space/kscience/snark/DataTreeWithDefault.kt @@ -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(public val tree: DataTree, val default: DataTree) : + DataTree { + override val dataType: KType get() = tree.dataType + + override val self: DataTreeWithDefault get() = this + + override val data: Data? get() = tree.data ?: default.data + override val items: Map> get() = default.items + tree.items +} \ No newline at end of file diff --git a/snark-core/src/jvmMain/kotlin/space/kscience/snark/extensions.kt b/snark-core/src/jvmMain/kotlin/space/kscience/snark/extensions.kt deleted file mode 100644 index 865519f..0000000 --- a/snark-core/src/jvmMain/kotlin/space/kscience/snark/extensions.kt +++ /dev/null @@ -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 DataTree<*>.branch( - branchName: Name, -): DataSource = filterByType { name, _, _ -> name.startsWith(branchName) } diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/Language.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/Language.kt index ea09852..d18cd9a 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/Language.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/Language.kt @@ -5,6 +5,7 @@ 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_LANGUAGE_KEY import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_MAP_KEY @@ -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( + 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( + 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) { @@ -48,32 +60,21 @@ public class Language : Scheme() { 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() +} + +internal val Data<*>.language: String? + get() = meta[Language.LANGUAGE_KEY].string?.lowercase() public val SiteContext.languageMap: Map get() = siteMeta[SITE_LANGUAGE_MAP_KEY]?.items?.map { @@ -83,61 +84,95 @@ public val SiteContext.languageMap: Map public val SiteContext.language: String get() = siteMeta[SITE_LANGUAGE_KEY].string ?: Language.DEFAULT_LANGUAGE -public val SiteContext.languagePrefix: Name - get() = languageMap[language]?.let { it.prefix ?: language }?.parseAsName() ?: Name.EMPTY - - -private class LanguageBranchAction(val prefix: Name) : AbstractAction(typeOf()) { - override fun DataSink.generate(data: DataTree, 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.update(source: DataTree, meta: Meta, namedData: NamedData) { - 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 > TR.walk( + namePrefix: Name = Name.EMPTY, +): Sequence> = 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, - 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 +private class LanguageMapAction(val languages: Set) : AbstractAction(typeOf()) { + override fun DataSink.generate(data: DataTree, meta: Meta) { + val languageMapCache = mutableMapOf>>() + + 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.update(source: DataTree, meta: Meta, namedData: NamedData) { + 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().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( + languageMappedData.branch(language.dataPath)!!, + languageMappedData.branch(defaultLanguage.dataPath)!! + ) + site( - prefix.parseAsName(), - data.filterByType().transform(LanguageBranchAction(Name.parse(language.dataPath ?: prefix))), + language.route, + overlayData, siteMeta = Laminate(languageSiteMeta, siteMeta), content ) @@ -156,32 +191,7 @@ public val PageContext.language: String public val PageContext.languageMap: Map get() = pageMeta[Language.LANGUAGE_MAP_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap() -public fun PageContext.localisedPageRef(pageName: Name, relative: Boolean = false): String { - val prefix = languageMap[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) -//} \ No newline at end of file +public fun PageContext.localisedPageRef(pageName: Name): String { + val prefix = languageMap[language]?.get(Language::dataPath.name)?.string?.parseAsName() ?: Name.EMPTY + return resolvePageRef(prefix + pageName) +} \ No newline at end of file diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt index 46d0a75..f5e65d3 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt @@ -17,4 +17,4 @@ public fun Data.withMeta(newMeta: Meta): Data = if (this is MetaMaskDa MetaMaskData(this, newMeta) } -public inline fun Data.mapMeta(block: MutableMeta.() -> Unit): Data = withMeta(meta.copy(block)) \ No newline at end of file +public inline fun Data.withMeta(block: MutableMeta.() -> Unit): Data = withMeta(meta.copy(block)) \ No newline at end of file diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt index ea8f124..32af306 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt @@ -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() diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageFragment.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageFragment.kt index 0d37bc3..a0cb705 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageFragment.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageFragment.kt @@ -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 diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/ParseAction.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/ParseAction.kt index 1b662de..6d4abf5 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/ParseAction.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/ParseAction.kt @@ -12,7 +12,7 @@ import kotlin.reflect.typeOf public class ParseAction(private val snarkHtml: SnarkHtml) : AbstractAction(typeOf()) { - private fun parseOne(data: NamedData, actionMeta: Meta): NamedData? = with(snarkHtml) { + private fun parseOne(data: NamedData): NamedData? = with(snarkHtml) { val contentType = getContentType(data.name, data.meta) val parser = snark.readers.values.filterIsInstance().filter { parser -> @@ -40,11 +40,11 @@ public class ParseAction(private val snarkHtml: SnarkHtml) : override fun DataSink.generate(data: DataTree, meta: Meta) { data.forEach { - parseOne(it, meta)?.let { put(it) } + parseOne(it)?.let { put(it) } } } override fun DataSink.update(source: DataTree, meta: Meta, namedData: NamedData) { - parseOne(namedData,meta)?.let { put(it) } + parseOne(namedData)?.let { put(it) } } } \ No newline at end of file diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt index 96d92c1..4cba079 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt @@ -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 /** * 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 diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt index a001393..89b6cce 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt @@ -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, + 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) { diff --git a/snark-ktor/src/jvmMain/kotlin/space/kscience/snark/ktor/KtorSiteContext.kt b/snark-ktor/src/jvmMain/kotlin/space/kscience/snark/ktor/KtorSiteContext.kt index cad02fb..ae753d4 100644 --- a/snark-ktor/src/jvmMain/kotlin/space/kscience/snark/ktor/KtorSiteContext.kt +++ b/snark-ktor/src/jvmMain/kotlin/space/kscience/snark/ktor/KtorSiteContext.kt @@ -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, + 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,19 +97,28 @@ 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( site = this@KtorSiteContext, - pageBaseUrl = url.buildString(), + host = hostUrl.build(), + pageRoute = siteRoute + route, pageMeta = Laminate(modifiedPageMeta, siteMeta) ) //render page in suspend environment @@ -138,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 @@ -156,8 +151,8 @@ 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 @@ -174,12 +169,12 @@ public class KtorSiteContext( public fun Route.site( context: Context, data: DataTree<*>?, - baseUrl: String = "", + path: List = emptyList(), siteMeta: Meta = data?.meta ?: Meta.EMPTY, content: HtmlSite, ) { 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 ) with(content) {