Skip to content

Commit 2eca683

Browse files
committed
Added support for an alternative current dir mode in pkldoc
1 parent 7c1604b commit 2eca683

File tree

11 files changed

+168
-20
lines changed

11 files changed

+168
-20
lines changed

docs/modules/pkl-doc/pages/index.adoc

+13-1
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,22 @@ Relative URIs are resolved against the working directory.
232232
[%collapsible]
233233
====
234234
Default: (none) +
235-
Example: `pkldoc`
235+
Example: `pkldoc` +
236236
The directory where generated documentation is placed.
237237
====
238238

239+
.--current-directory-mode
240+
[%collapsible]
241+
====
242+
Default: `symlink` +
243+
Example: `copy` +
244+
How the "current" directories containing documentation content for the last generated version
245+
should be created. By default, in the symlink mode, a symbolic link is created pointing to the
246+
last generated version. In the copy mode, a full copy of the last generated version
247+
is created. Symbolic links are not processed correctly by some systems (e.g. GitHub Pages),
248+
so the copy mode is occasionally useful.
249+
====
250+
239251
Common CLI options:
240252

241253
include::../../pkl-cli/partials/cli-common-options.adoc[]

docs/modules/pkl-gradle/pages/index.adoc

+12
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,18 @@ Example: `outputDir = layout.projectDirectory.dir("pkl-docs")` +
515515
The directory where generated documentation is placed.
516516
====
517517

518+
.currentDirectoryMode: Property<CurrentDirectoryMode>
519+
[%collapsible]
520+
====
521+
Default: `DocsiteGenerator.CurrentDirectoryMode.SYMLINK` +
522+
Example: `currentDirectoryMode = DocsiteGenerator.CurrentDirectoryMode.COPY` +
523+
How the "current" directories containing documentation content for the last generated version
524+
should be created. By default, in the symlink mode, a symbolic link is created pointing to the
525+
last generated version. In the copy mode, a full copy of the last generated version
526+
is created. Symbolic links are not processed correctly by some systems (e.g. GitHub Pages),
527+
so the copy mode is occasionally useful.
528+
====
529+
518530
Common properties:
519531

520532
include::../partials/gradle-modules-properties.adoc[]

pkl-commons/src/main/kotlin/org/pkl/commons/Paths.kt

+14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.nio.charset.Charset
2020
import java.nio.file.*
2121
import java.nio.file.attribute.FileAttribute
2222
import java.util.stream.Stream
23+
import kotlin.io.path.copyTo
2324
import kotlin.io.path.createDirectories
2425
import kotlin.io.path.deleteIfExists
2526
import kotlin.io.path.exists
@@ -72,6 +73,19 @@ fun Path.deleteRecursively() {
7273
}
7374
}
7475

76+
@Throws(IOException::class)
77+
fun Path.copyRecursively(target: Path) {
78+
if (exists()) {
79+
target.createParentDirectories()
80+
walk().use { paths ->
81+
paths.forEach { src ->
82+
val dst = target.resolve(this@copyRecursively.relativize(src))
83+
src.copyTo(dst, overwrite = true)
84+
}
85+
}
86+
}
87+
}
88+
7589
private val isWindows by lazy { System.getProperty("os.name").contains("Windows") }
7690

7791
/** Copy implementation from IoUtils.toNormalizedPathString */

pkl-doc/src/main/kotlin/org/pkl/doc/CliDocGenerator.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ class CliDocGenerator(private val options: CliDocGeneratorOptions) : CliCommand(
269269
importedModules::getValue,
270270
versionComparator,
271271
options.normalizedOutputDir,
272-
options.isTestMode
272+
options.isTestMode,
273+
options.currentDirectoryMode
273274
)
274275
.run()
275276
} catch (e: DocGeneratorException) {

pkl-doc/src/main/kotlin/org/pkl/doc/CliDocGeneratorOptions.kt

+12-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,18 @@ constructor(
3535
* Generates source URLs with fixed line numbers `#L123-L456` to avoid churn in expected output
3636
* files (e.g., when stdlib line numbers change).
3737
*/
38-
val isTestMode: Boolean = false
38+
val isTestMode: Boolean = false,
39+
40+
/**
41+
* Determines how to create the "current" directory which contains documentation for the latest
42+
* version of the package.
43+
*
44+
* [DocGenerator.CurrentDirectoryMode.SYMLINK] will make the current directory into a symlink
45+
* to the actual version directory. [DocGenerator.CurrentDirectoryMode.COPY], however,
46+
* will create a full copy instead.
47+
*/
48+
var currentDirectoryMode: DocGenerator.CurrentDirectoryMode =
49+
DocGenerator.CurrentDirectoryMode.SYMLINK
3950
) {
4051
/** [outputDir] after undergoing normalization. */
4152
val normalizedOutputDir: Path = base.normalizedWorkingDir.resolveSafely(outputDir)

pkl-doc/src/main/kotlin/org/pkl/doc/DocGenerator.kt

+46-8
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import java.io.IOException
1919
import java.net.URI
2020
import java.nio.file.Path
2121
import kotlin.io.path.createSymbolicLinkPointingTo
22-
import kotlin.io.path.deleteIfExists
2322
import kotlin.io.path.exists
2423
import kotlin.io.path.isSameFileAs
24+
import org.pkl.commons.copyRecursively
2525
import org.pkl.commons.deleteRecursively
2626
import org.pkl.core.ModuleSchema
2727
import org.pkl.core.PClassInfo
@@ -53,6 +53,7 @@ class DocGenerator(
5353

5454
/** A comparator for package versions. */
5555
versionComparator: Comparator<String>,
56+
5657
/** The directory where generated documentation is placed. */
5758
private val outputDir: Path,
5859

@@ -62,9 +63,20 @@ class DocGenerator(
6263
* Generates source URLs with fixed line numbers `#L123-L456` to avoid churn in expected output
6364
* files (e.g., when stdlib line numbers change).
6465
*/
65-
private val isTestMode: Boolean = false
66+
private val isTestMode: Boolean = false,
67+
68+
/**
69+
* Determines how to create the "current" directory which contains documentation for the latest
70+
* version of the package.
71+
*
72+
* [CurrentDirectoryMode.SYMLINK] will make the current directory into a symlink to the actual
73+
* version directory. [CurrentDirectoryMode.COPY], however, will create a full copy instead.
74+
*/
75+
private val currentDirectoryMode: CurrentDirectoryMode = CurrentDirectoryMode.SYMLINK
6676
) {
6777
companion object {
78+
const val CURRENT_DIRECTORY_NAME = "current"
79+
6880
internal fun List<PackageData>.current(
6981
versionComparator: Comparator<String>
7082
): List<PackageData> {
@@ -105,7 +117,7 @@ class DocGenerator(
105117
val packagesData = packageDataGenerator.readAll()
106118
val currentPackagesData = packagesData.current(descendingVersionComparator)
107119

108-
createSymlinks(currentPackagesData)
120+
createCurrentDirectories(currentPackagesData)
109121

110122
htmlGenerator.generateSite(currentPackagesData)
111123
searchIndexGenerator.generateSiteIndex(currentPackagesData)
@@ -120,16 +132,42 @@ class DocGenerator(
120132
outputDir.resolve("$name/$version").deleteRecursively()
121133
}
122134

123-
private fun createSymlinks(currentPackagesData: List<PackageData>) {
135+
private fun createCurrentDirectories(currentPackagesData: List<PackageData>) {
124136
for (packageData in currentPackagesData) {
125137
val basePath = outputDir.resolve(packageData.ref.pkg.pathEncoded)
126138
val src = basePath.resolve(packageData.ref.version)
127-
val dest = basePath.resolve("current")
128-
if (dest.exists() && dest.isSameFileAs(src)) continue
129-
dest.deleteIfExists()
130-
dest.createSymbolicLinkPointingTo(IoUtils.relativize(src, basePath))
139+
val dst = basePath.resolve(CURRENT_DIRECTORY_NAME)
140+
141+
when (currentDirectoryMode) {
142+
CurrentDirectoryMode.SYMLINK -> {
143+
if (!dst.exists() || !dst.isSameFileAs(src)) {
144+
dst.deleteRecursively()
145+
dst.createSymbolicLinkPointingTo(IoUtils.relativize(src, basePath))
146+
}
147+
}
148+
CurrentDirectoryMode.COPY -> {
149+
dst.deleteRecursively()
150+
src.copyRecursively(dst)
151+
}
152+
}
131153
}
132154
}
155+
156+
/**
157+
* Determines how to create the "current" directory with the contents of the latest generated
158+
* version of the package docs.
159+
*/
160+
enum class CurrentDirectoryMode {
161+
/**
162+
* Create a symlink pointing to the directory with the latest version of documentation.
163+
*/
164+
SYMLINK,
165+
166+
/**
167+
* Make a full copy of the directory with the latest version of documentation.
168+
*/
169+
COPY
170+
}
133171
}
134172

135173
internal class DocPackage(val docPackageInfo: DocPackageInfo, val modules: List<ModuleSchema>) {

pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import com.github.ajalt.clikt.parameters.arguments.argument
2121
import com.github.ajalt.clikt.parameters.arguments.convert
2222
import com.github.ajalt.clikt.parameters.arguments.multiple
2323
import com.github.ajalt.clikt.parameters.groups.provideDelegate
24+
import com.github.ajalt.clikt.parameters.options.default
2425
import com.github.ajalt.clikt.parameters.options.option
2526
import com.github.ajalt.clikt.parameters.options.required
27+
import com.github.ajalt.clikt.parameters.types.enum
2628
import com.github.ajalt.clikt.parameters.types.path
2729
import java.net.URI
2830
import java.nio.file.Path
@@ -57,6 +59,15 @@ class DocCommand :
5759
.path()
5860
.required()
5961

62+
private val currentDirectoryMode: DocGenerator.CurrentDirectoryMode by
63+
option(
64+
names = arrayOf("--current-directory-mode"),
65+
metavar = "<mode>",
66+
help = "How current directory should be created (as a symlink or as a full copy)"
67+
)
68+
.enum<DocGenerator.CurrentDirectoryMode>()
69+
.default(DocGenerator.CurrentDirectoryMode.SYMLINK)
70+
6071
private val projectOptions by ProjectOptions()
6172

6273
override fun run() {
@@ -67,7 +78,8 @@ class DocCommand :
6778
projectOptions,
6879
),
6980
outputDir,
70-
true
81+
true,
82+
currentDirectoryMode
7183
)
7284
CliDocGenerator(options).run()
7385
}

pkl-doc/src/test/kotlin/org/pkl/doc/CliDocGeneratorTest.kt

+34-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import org.pkl.commons.test.FileTestUtils
3636
import org.pkl.commons.test.PackageServer
3737
import org.pkl.commons.test.listFilesRecursively
3838
import org.pkl.commons.toPath
39+
import org.pkl.commons.walk
3940
import org.pkl.core.Version
4041
import org.pkl.core.util.IoUtils
4142
import org.pkl.doc.DocGenerator.Companion.current
@@ -113,7 +114,12 @@ class CliDocGeneratorTest {
113114
"svg",
114115
)
115116

116-
private fun runDocGenerator(outputDir: Path, cacheDir: Path?) {
117+
private fun runDocGenerator(
118+
outputDir: Path,
119+
cacheDir: Path?,
120+
currentDirectoryMode: DocGenerator.CurrentDirectoryMode =
121+
DocGenerator.CurrentDirectoryMode.SYMLINK
122+
) {
117123
CliDocGenerator(
118124
CliDocGeneratorOptions(
119125
CliBaseOptions(
@@ -130,7 +136,8 @@ class CliDocGeneratorTest {
130136
moduleCacheDir = cacheDir
131137
),
132138
outputDir = outputDir,
133-
isTestMode = true
139+
isTestMode = true,
140+
currentDirectoryMode = currentDirectoryMode
134141
)
135142
)
136143
.run()
@@ -260,15 +267,36 @@ class CliDocGeneratorTest {
260267
}
261268

262269
@Test
263-
fun `creates a symlink called current`(@TempDir tempDir: Path) {
270+
fun `creates a symlink called current by default`(@TempDir tempDir: Path) {
264271
PackageServer.populateCacheDir(tempDir)
265272
runDocGenerator(actualOutputDir, tempDir)
273+
266274
val expectedSymlink = actualOutputDir.resolve("com.package1/current")
267275
val expectedDestination = actualOutputDir.resolve("com.package1/1.2.3")
268-
org.junit.jupiter.api.Assertions.assertTrue(Files.isSymbolicLink(expectedSymlink))
269-
org.junit.jupiter.api.Assertions.assertTrue(
270-
Files.isSameFile(expectedSymlink, expectedDestination)
276+
277+
assertThat(expectedSymlink).isSymbolicLink().matches {
278+
Files.isSameFile(it, expectedDestination)
279+
}
280+
}
281+
282+
@Test
283+
fun `creates a copy of the latest output called current when configured`(@TempDir tempDir: Path) {
284+
PackageServer.populateCacheDir(tempDir)
285+
runDocGenerator(
286+
actualOutputDir,
287+
tempDir,
288+
currentDirectoryMode = DocGenerator.CurrentDirectoryMode.COPY
271289
)
290+
291+
val currentDirectory = actualOutputDir.resolve("com.package1/current")
292+
val sourceDirectory = actualOutputDir.resolve("com.package1/1.2.3")
293+
294+
assertThat(currentDirectory).isDirectory()
295+
296+
val expectedFiles = sourceDirectory.walk().map(sourceDirectory::relativize).toList()
297+
val actualFiles = currentDirectory.walk().map(currentDirectory::relativize).toList()
298+
299+
assertThat(actualFiles).hasSameElementsAs(expectedFiles)
272300
}
273301

274302
@Test

pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.pkl.core.util.IoUtils;
4343
import org.pkl.core.util.LateInit;
4444
import org.pkl.core.util.Nullable;
45+
import org.pkl.doc.DocGenerator.CurrentDirectoryMode;
4546
import org.pkl.gradle.spec.AnalyzeImportsSpec;
4647
import org.pkl.gradle.spec.BasePklSpec;
4748
import org.pkl.gradle.spec.CodeGenSpec;
@@ -249,8 +250,14 @@ private void configurePkldocTasks(NamedDomainObjectContainer<PkldocSpec> specs)
249250
.getBuildDirectory()
250251
.map(it -> it.dir("pkldoc").dir(spec.getName())));
251252

253+
spec.getCurrentDirectoryMode().convention(CurrentDirectoryMode.SYMLINK);
254+
252255
createModulesTask(PkldocTask.class, spec)
253-
.configure(task -> task.getOutputDir().set(spec.getOutputDir()));
256+
.configure(
257+
task -> {
258+
task.getOutputDir().set(spec.getOutputDir());
259+
task.getCurrentDirectoryMode().set(spec.getCurrentDirectoryMode());
260+
});
254261
});
255262
}
256263

pkl-gradle/src/main/java/org/pkl/gradle/spec/PkldocSpec.java

+4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
package org.pkl.gradle.spec;
1717

1818
import org.gradle.api.file.DirectoryProperty;
19+
import org.gradle.api.provider.Property;
20+
import org.pkl.doc.DocGenerator.CurrentDirectoryMode;
1921

2022
/** Configuration options for Pkldoc generators. Documented in user manual. */
2123
public interface PkldocSpec extends ModulesSpec {
2224
DirectoryProperty getOutputDir();
25+
26+
Property<CurrentDirectoryMode> getCurrentDirectoryMode();
2327
}

pkl-gradle/src/main/java/org/pkl/gradle/task/PkldocTask.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,28 @@
1616
package org.pkl.gradle.task;
1717

1818
import org.gradle.api.file.DirectoryProperty;
19+
import org.gradle.api.provider.Property;
20+
import org.gradle.api.tasks.Input;
1921
import org.gradle.api.tasks.OutputDirectory;
2022
import org.pkl.doc.CliDocGenerator;
2123
import org.pkl.doc.CliDocGeneratorOptions;
24+
import org.pkl.doc.DocGenerator.CurrentDirectoryMode;
2225

2326
public abstract class PkldocTask extends ModulesTask {
2427
@OutputDirectory
2528
public abstract DirectoryProperty getOutputDir();
2629

30+
@Input
31+
public abstract Property<CurrentDirectoryMode> getCurrentDirectoryMode();
32+
2733
@Override
2834
protected void doRunTask() {
2935
new CliDocGenerator(
3036
new CliDocGeneratorOptions(
31-
getCliBaseOptions(), getOutputDir().get().getAsFile().toPath()))
37+
getCliBaseOptions(),
38+
getOutputDir().get().getAsFile().toPath(),
39+
false,
40+
getCurrentDirectoryMode().get()))
3241
.run();
3342
}
3443
}

0 commit comments

Comments
 (0)