Added support for an alternative current dir mode in pkldoc (#824)

Some systems have trouble with handling symlinks, which breaks the current directory links created by Pkldoc. In this PR, we add an alternative mode which creates a full copy of the latest published version contents in the current directory instead.

Co-authored-by: Dan Chao <dan.chao@apple.com>
This commit is contained in:
Vladimir Matveev
2025-02-19 08:52:32 -08:00
committed by GitHub
parent 2ffd201172
commit baa34a6dd1
11 changed files with 143 additions and 19 deletions

View File

@@ -270,6 +270,7 @@ class CliDocGenerator(private val options: CliDocGeneratorOptions) : CliCommand(
versionComparator,
options.normalizedOutputDir,
options.isTestMode,
options.noSymlinks,
)
.run()
} catch (e: DocGeneratorException) {

View File

@@ -36,6 +36,17 @@ constructor(
* files (e.g., when stdlib line numbers change).
*/
val isTestMode: Boolean = false,
/**
* Disables creation of symlinks, replacing them with regular files and directories.
*
* In particular, this affects creation of the "current" directory which contains documentation
* for the latest version of the package.
*
* `false` will make the current directory into a symlink to the actual version directory. `true`,
* however, will create a full copy instead.
*/
val noSymlinks: Boolean = false,
) {
/** [outputDir] after undergoing normalization. */
val normalizedOutputDir: Path = base.normalizedWorkingDir.resolveSafely(outputDir)

View File

@@ -19,6 +19,7 @@ import java.io.IOException
import java.net.URI
import java.nio.file.Path
import kotlin.io.path.*
import org.pkl.commons.copyRecursively
import org.pkl.core.ModuleSchema
import org.pkl.core.PClassInfo
import org.pkl.core.Version
@@ -50,6 +51,7 @@ class DocGenerator(
/** A comparator for package versions. */
versionComparator: Comparator<String>,
/** The directory where generated documentation is placed. */
private val outputDir: Path,
@@ -60,8 +62,21 @@ class DocGenerator(
* files (e.g., when stdlib line numbers change).
*/
private val isTestMode: Boolean = false,
/**
* Disables creation of symbolic links, using copies of files and directories instead of them.
*
* In particular, determines how to create the "current" directory which contains documentation
* for the latest version of the package.
*
* `false` will make the current directory into a symlink to the actual version directory. `true`,
* however, will create a full copy instead.
*/
private val noSymlinks: Boolean = false,
) {
companion object {
const val CURRENT_DIRECTORY_NAME = "current"
internal fun List<PackageData>.current(
versionComparator: Comparator<String>
): List<PackageData> {
@@ -102,7 +117,7 @@ class DocGenerator(
val packagesData = packageDataGenerator.readAll()
val currentPackagesData = packagesData.current(descendingVersionComparator)
createSymlinks(currentPackagesData)
createCurrentDirectories(currentPackagesData)
htmlGenerator.generateSite(currentPackagesData)
searchIndexGenerator.generateSiteIndex(currentPackagesData)
@@ -117,14 +132,21 @@ class DocGenerator(
outputDir.resolve(IoUtils.encodePath("$name/$version")).deleteRecursively()
}
private fun createSymlinks(currentPackagesData: List<PackageData>) {
private fun createCurrentDirectories(currentPackagesData: List<PackageData>) {
for (packageData in currentPackagesData) {
val basePath = outputDir.resolve(packageData.ref.pkg.pathEncoded)
val src = basePath.resolve(packageData.ref.version)
val dest = basePath.resolve("current")
if (dest.exists() && dest.isSameFileAs(src)) continue
dest.deleteIfExists()
dest.createSymbolicLinkPointingTo(IoUtils.relativize(src, basePath))
val dst = basePath.resolve(CURRENT_DIRECTORY_NAME)
if (noSymlinks) {
dst.deleteRecursively()
src.copyRecursively(dst)
} else {
if (!dst.exists() || !dst.isSameFileAs(src)) {
dst.deleteRecursively()
dst.createSymbolicLinkPointingTo(IoUtils.relativize(src, basePath))
}
}
}
}
}

View File

@@ -21,6 +21,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.path
@@ -30,6 +31,7 @@ import org.pkl.commons.cli.cliMain
import org.pkl.commons.cli.commands.BaseCommand
import org.pkl.commons.cli.commands.BaseOptions.Companion.parseModuleName
import org.pkl.commons.cli.commands.ProjectOptions
import org.pkl.commons.cli.commands.single
import org.pkl.core.Release
/** Main method for the Pkldoc CLI. */
@@ -57,11 +59,24 @@ class DocCommand :
.path()
.required()
private val noSymlinks: Boolean by
option(
names = arrayOf("--no-symlinks"),
help = "Create copies of directories and files instead of symbolic links.",
)
.single()
.flag(default = false)
private val projectOptions by ProjectOptions()
override fun run() {
val options =
CliDocGeneratorOptions(baseOptions.baseOptions(modules, projectOptions), outputDir, true)
CliDocGeneratorOptions(
baseOptions.baseOptions(modules, projectOptions),
outputDir,
true,
noSymlinks,
)
CliDocGenerator(options).run()
}
}