mirror of
https://github.com/apple/pkl.git
synced 2026-07-04 04:01:48 +02:00
pkl-doc: Support single-package docsite mode (#1592)
When a docsite has only one package name and no DocsiteInfo.overview, treat it like Javadoc's single-module output: redirect the top-level index to the package page and omit the site-title breadcrumb segment from generated pages. Add src/test/files/SinglePackageTest fixtures to cover multiple package versions, redirect behavior, breadcrumb behavior, and unchanged site structure. Also: - Shut down Executor used in test. - Declare expected output fixtures of DocGenerator as test inputs, not outputs. - Fix IntelliJ warning by using a Set for the right-hand side of collection subtraction.
This commit is contained in:
@@ -26,8 +26,17 @@ internal class ClassPageGenerator(
|
||||
clazz: PClass,
|
||||
pageScope: ClassScope,
|
||||
isTestMode: Boolean,
|
||||
isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : ModuleOrClassPageGenerator<ClassScope>(docsiteInfo, clazz, pageScope, isTestMode, consoleOut) {
|
||||
) :
|
||||
ModuleOrClassPageGenerator<ClassScope>(
|
||||
docsiteInfo,
|
||||
clazz,
|
||||
pageScope,
|
||||
isTestMode,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
) {
|
||||
override val html: HTML.() -> Unit = {
|
||||
renderHtmlHead()
|
||||
|
||||
|
||||
@@ -190,28 +190,11 @@ class DocGenerator(
|
||||
"Docsite is not up to date. Expected: ${DocMigrator.CURRENT_VERSION}. Found: ${docMigrator.docsiteVersion}. Use DocMigrator to migrate the site."
|
||||
)
|
||||
}
|
||||
val htmlGenerator =
|
||||
HtmlGenerator(docsiteInfo, docPackages, importResolver, outputDir, isTestMode, consoleOut)
|
||||
val searchIndexGenerator = SearchIndexGenerator(outputDir, consoleOut)
|
||||
val packageDataGenerator = PackageDataGenerator(outputDir, consoleOut)
|
||||
val runtimeDataGenerator =
|
||||
RuntimeDataGenerator(descendingVersionComparator, outputDir, consoleOut)
|
||||
|
||||
coroutineScope {
|
||||
for (docPackage in docPackages) {
|
||||
launch {
|
||||
docPackage.deletePackageDir()
|
||||
coroutineScope {
|
||||
launch { htmlGenerator.generate(docPackage) }
|
||||
launch { searchIndexGenerator.generate(docPackage) }
|
||||
launch { packageDataGenerator.generate(docPackage) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeOutputLine("Generated HTML for packages")
|
||||
|
||||
val newlyGeneratedPackages = docPackages.map(::PackageData).sortedBy { it.ref.pkg }
|
||||
val currentSearchIndex = searchIndexGenerator.getCurrentSearchIndex()
|
||||
|
||||
@@ -226,6 +209,32 @@ class DocGenerator(
|
||||
newlyGeneratedPackages + existingCurrentPackages,
|
||||
descendingVersionComparator,
|
||||
)
|
||||
val isSinglePackageSite = docsiteInfo.overview == null && currentPackages.size == 1
|
||||
val htmlGenerator =
|
||||
HtmlGenerator(
|
||||
docsiteInfo,
|
||||
docPackages,
|
||||
importResolver,
|
||||
outputDir,
|
||||
isTestMode,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
)
|
||||
|
||||
coroutineScope {
|
||||
for (docPackage in docPackages) {
|
||||
launch {
|
||||
docPackage.deletePackageDir()
|
||||
coroutineScope {
|
||||
launch { htmlGenerator.generate(docPackage) }
|
||||
launch { searchIndexGenerator.generate(docPackage) }
|
||||
launch { packageDataGenerator.generate(docPackage) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeOutputLine("Generated HTML for packages")
|
||||
|
||||
createCurrentDirectories(currentPackages, existingCurrentPackages)
|
||||
searchIndexGenerator.generateSiteIndex(currentPackages)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,10 @@ data class DocsiteInfo(
|
||||
*
|
||||
* Uses the same Markdown format as Pkldoc comments. Unless expanded, only the first paragraph is
|
||||
* shown.
|
||||
*
|
||||
* If [overview] is `null` and the generated site has only one distinct package name, the main
|
||||
* page redirects to that package page and generated breadcrumbs omit the site title segment. The
|
||||
* structure of the generated site is unchanged.
|
||||
*/
|
||||
val overview: String?,
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ internal class HtmlGenerator(
|
||||
importResolver: (URI) -> ModuleSchema,
|
||||
private val outputDir: Path,
|
||||
private val isTestMode: Boolean,
|
||||
private val isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : AbstractGenerator(consoleOut) {
|
||||
private val siteScope =
|
||||
@@ -35,14 +36,25 @@ internal class HtmlGenerator(
|
||||
|
||||
suspend fun generate(docPackage: DocPackage) = coroutineScope {
|
||||
val packageScope = siteScope.getPackage(docPackage.docPackageInfo)
|
||||
launch { PackagePageGenerator(docsiteInfo, docPackage, packageScope, consoleOut).run() }
|
||||
launch {
|
||||
PackagePageGenerator(docsiteInfo, docPackage, packageScope, isSinglePackageSite, consoleOut)
|
||||
.run()
|
||||
}
|
||||
|
||||
for (docModule in docPackage.docModules) {
|
||||
if (docModule.isUnlisted) continue
|
||||
|
||||
val moduleScope = packageScope.getModule(docModule.name)
|
||||
launch {
|
||||
ModulePageGenerator(docsiteInfo, docPackage, docModule, moduleScope, isTestMode, consoleOut)
|
||||
ModulePageGenerator(
|
||||
docsiteInfo,
|
||||
docPackage,
|
||||
docModule,
|
||||
moduleScope,
|
||||
isTestMode,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -56,6 +68,7 @@ internal class HtmlGenerator(
|
||||
clazz,
|
||||
ClassScope(clazz, moduleScope.url, moduleScope),
|
||||
isTestMode,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
)
|
||||
.run()
|
||||
@@ -65,7 +78,9 @@ internal class HtmlGenerator(
|
||||
}
|
||||
|
||||
suspend fun generateSite(packages: List<PackageData>) = coroutineScope {
|
||||
launch { MainPageGenerator(docsiteInfo, packages, siteScope, consoleOut).run() }
|
||||
launch {
|
||||
MainPageGenerator(docsiteInfo, packages, siteScope, isSinglePackageSite, consoleOut).run()
|
||||
}
|
||||
launch { generateStaticResources() }
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ import kotlinx.html.*
|
||||
internal abstract class MainOrPackagePageGenerator<S>(
|
||||
docsiteInfo: DocsiteInfo,
|
||||
pageScope: S,
|
||||
isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : PageGenerator<S>(docsiteInfo, pageScope, consoleOut) where S : PageScope {
|
||||
) : PageGenerator<S>(docsiteInfo, pageScope, isSinglePackageSite, consoleOut) where S : PageScope {
|
||||
protected fun UL.renderModuleOrPackage(
|
||||
name: String,
|
||||
moduleOrPackageScope: DocScope,
|
||||
|
||||
@@ -17,47 +17,53 @@ package org.pkl.doc
|
||||
|
||||
import java.io.OutputStream
|
||||
import kotlinx.html.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
internal class MainPageGenerator(
|
||||
docsiteInfo: DocsiteInfo,
|
||||
private val packagesData: List<PackageData>,
|
||||
pageScope: SiteScope,
|
||||
private val isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : MainOrPackagePageGenerator<SiteScope>(docsiteInfo, pageScope, consoleOut) {
|
||||
) : MainOrPackagePageGenerator<SiteScope>(docsiteInfo, pageScope, false, consoleOut) {
|
||||
override val html: HTML.() -> Unit = {
|
||||
renderHtmlHead()
|
||||
if (isSinglePackageSite) {
|
||||
renderRedirectPage()
|
||||
} else {
|
||||
renderHtmlHead()
|
||||
body {
|
||||
onLoad = "onLoad()"
|
||||
|
||||
body {
|
||||
onLoad = "onLoad()"
|
||||
renderPageHeader(null, null, null, null)
|
||||
|
||||
renderPageHeader(null, null, null, null)
|
||||
main {
|
||||
h1 {
|
||||
id = "declaration-title"
|
||||
|
||||
main {
|
||||
h1 {
|
||||
id = "declaration-title"
|
||||
|
||||
+(docsiteInfo.title ?: "")
|
||||
}
|
||||
|
||||
val memberDocs = MemberDocs(docsiteInfo.overview, pageScope, listOf(), isDeclaration = true)
|
||||
|
||||
renderMemberGroupLinks(
|
||||
Triple("Overview", "#_overview", memberDocs.isExpandable),
|
||||
Triple("Packages", "#_packages", packagesData.isNotEmpty()),
|
||||
)
|
||||
|
||||
if (docsiteInfo.overview != null) {
|
||||
renderAnchor("_overview")
|
||||
div {
|
||||
id = "_declaration"
|
||||
classes = setOf("member")
|
||||
|
||||
memberDocs.renderExpandIcon(this)
|
||||
memberDocs.renderDocComment(this)
|
||||
+(docsiteInfo.title ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
renderPackages()
|
||||
val memberDocs =
|
||||
MemberDocs(docsiteInfo.overview, pageScope, listOf(), isDeclaration = true)
|
||||
|
||||
renderMemberGroupLinks(
|
||||
Triple("Overview", "#_overview", memberDocs.isExpandable),
|
||||
Triple("Packages", "#_packages", packagesData.isNotEmpty()),
|
||||
)
|
||||
|
||||
if (docsiteInfo.overview != null) {
|
||||
renderAnchor("_overview")
|
||||
div {
|
||||
id = "_declaration"
|
||||
classes = setOf("member")
|
||||
|
||||
memberDocs.renderExpandIcon(this)
|
||||
memberDocs.renderDocComment(this)
|
||||
}
|
||||
}
|
||||
|
||||
renderPackages()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +72,28 @@ internal class MainPageGenerator(
|
||||
+(docsiteInfo.title ?: "Pkldoc")
|
||||
}
|
||||
|
||||
private fun HTML.renderRedirectPage() {
|
||||
val packagePageUrl = "${packagesData.single().ref.basePath}/current/index.html"
|
||||
|
||||
lang = "en-US"
|
||||
|
||||
head {
|
||||
meta { charset = "UTF-8" }
|
||||
title { renderPageTitle() }
|
||||
script { unsafe { raw("window.location.replace(${Json.encodeToString(packagePageUrl)});") } }
|
||||
}
|
||||
body {
|
||||
main {
|
||||
p {
|
||||
a {
|
||||
href = packagePageUrl
|
||||
+packagePageUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun HtmlBlockTag.renderPackages() {
|
||||
if (packagesData.isEmpty()) return
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ internal abstract class ModuleOrClassPageGenerator<S>(
|
||||
protected val clazz: PClass,
|
||||
scope: S,
|
||||
private val isTestMode: Boolean,
|
||||
isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : PageGenerator<S>(docsiteInfo, scope, consoleOut) where S : PageScope {
|
||||
) : PageGenerator<S>(docsiteInfo, scope, isSinglePackageSite, consoleOut) where S : PageScope {
|
||||
protected fun HtmlBlockTag.renderProperties() {
|
||||
if (!clazz.hasListedProperty) return
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ internal class ModulePageGenerator(
|
||||
docModule: DocModule,
|
||||
pageScope: ModuleScope,
|
||||
isTestMode: Boolean,
|
||||
isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) :
|
||||
ModuleOrClassPageGenerator<ModuleScope>(
|
||||
@@ -31,6 +32,7 @@ internal class ModulePageGenerator(
|
||||
docModule.schema.moduleClass,
|
||||
pageScope,
|
||||
isTestMode,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
) {
|
||||
private val module = docModule.schema
|
||||
|
||||
@@ -22,8 +22,15 @@ internal class PackagePageGenerator(
|
||||
docsiteInfo: DocsiteInfo,
|
||||
private val docPackage: DocPackage,
|
||||
pageScope: PackageScope,
|
||||
isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : MainOrPackagePageGenerator<PackageScope>(docsiteInfo, pageScope, consoleOut) {
|
||||
) :
|
||||
MainOrPackagePageGenerator<PackageScope>(
|
||||
docsiteInfo,
|
||||
pageScope,
|
||||
isSinglePackageSite,
|
||||
consoleOut,
|
||||
) {
|
||||
override val html: HTML.() -> Unit = {
|
||||
renderHtmlHead()
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.pkl.core.util.IoUtils
|
||||
internal abstract class PageGenerator<out S>(
|
||||
protected val docsiteInfo: DocsiteInfo,
|
||||
protected val pageScope: S,
|
||||
private val isSinglePackageSite: Boolean,
|
||||
consoleOut: OutputStream,
|
||||
) : AbstractGenerator(consoleOut) where S : PageScope {
|
||||
companion object {
|
||||
@@ -203,13 +204,6 @@ internal abstract class PageGenerator<out S>(
|
||||
}
|
||||
|
||||
protected fun HtmlBlockTag.renderParentLinks() {
|
||||
a {
|
||||
classes = setOf("declaration-parent-link")
|
||||
href = pageScope.relativeSiteUrl.toString()
|
||||
|
||||
+(docsiteInfo.title ?: "Pkldoc")
|
||||
}
|
||||
|
||||
val packageScope =
|
||||
when (pageScope) {
|
||||
is ClassScope -> pageScope.parent!!.parent
|
||||
@@ -217,33 +211,33 @@ internal abstract class PageGenerator<out S>(
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (packageScope != null) {
|
||||
+" > "
|
||||
|
||||
a {
|
||||
classes = setOf("declaration-parent-link")
|
||||
href = packageScope.urlRelativeTo(pageScope).toString()
|
||||
|
||||
+packageScope.name
|
||||
}
|
||||
}
|
||||
|
||||
val moduleScope =
|
||||
when (pageScope) {
|
||||
is ClassScope -> pageScope.parent
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (moduleScope != null) {
|
||||
+" > "
|
||||
var isFirst = true
|
||||
|
||||
fun renderLink(text: String, url: String) {
|
||||
if (isFirst) isFirst = false else +" > "
|
||||
a {
|
||||
classes = setOf("declaration-parent-link")
|
||||
href = moduleScope.urlRelativeTo(pageScope).toString()
|
||||
href = url
|
||||
|
||||
+moduleScope.name
|
||||
+text
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSinglePackageSite) {
|
||||
renderLink(docsiteInfo.title ?: "Pkldoc", pageScope.relativeSiteUrl.toString())
|
||||
}
|
||||
if (packageScope != null) {
|
||||
renderLink(packageScope.name, packageScope.urlRelativeTo(pageScope).toString())
|
||||
}
|
||||
if (moduleScope != null) {
|
||||
renderLink(moduleScope.name, moduleScope.urlRelativeTo(pageScope).toString())
|
||||
}
|
||||
}
|
||||
|
||||
protected fun HtmlBlockTag.renderClassExtendsClause(clazz: PClass, currScope: DocScope) {
|
||||
|
||||
Reference in New Issue
Block a user