diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt index f1586a2c..2ca85e72 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt @@ -1164,7 +1164,7 @@ result = someLib.x """ .trimIndent() ) - assertThat(tempDir.resolve("package-1")).doesNotExist() + assertThat(tempDir.resolve("package-2")).doesNotExist() } @Test diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliPackageDownloaderTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliPackageDownloaderTest.kt index 08ebcf13..faf8522e 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliPackageDownloaderTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliPackageDownloaderTest.kt @@ -56,12 +56,12 @@ class CliPackageDownloaderTest { noTransitive = true ) cmd.run() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.json")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.0.5/fruit@1.0.5.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.0.5/fruit@1.0.5.json")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.1.0/fruit@1.1.0.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.1.0/fruit@1.1.0.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.1.0/fruit@1.1.0.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.1.0/fruit@1.1.0.json")).exists() } @Test @@ -90,9 +90,9 @@ class CliPackageDownloaderTest { noTransitive = true ) cmd.run() - assertThat(tempDir.resolve(".my-cache/package-1/localhost:0/birds@0.5.0/birds@0.5.0.zip")) + assertThat(tempDir.resolve(".my-cache/package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")) .exists() - assertThat(tempDir.resolve(".my-cache/package-1/localhost:0/birds@0.5.0/birds@0.5.0.json")) + assertThat(tempDir.resolve(".my-cache/package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")) .exists() } @@ -113,8 +113,8 @@ class CliPackageDownloaderTest { noTransitive = true ) cmd.run() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() } @Test @@ -228,9 +228,9 @@ class CliPackageDownloaderTest { noTransitive = false ) .run() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/birds@0.5.0/birds@0.5.0.json")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.0.5/fruit@1.0.5.zip")).exists() - assertThat(tempDir.resolve("package-1/localhost:0/fruit@1.0.5/fruit@1.0.5.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.zip")).exists() + assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.json")).exists() } } diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt index f2699e7e..8de61c5d 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt @@ -22,6 +22,7 @@ import java.net.URI import java.util.* import org.pkl.core.* import org.pkl.core.util.CodeGeneratorUtils +import org.pkl.core.util.IoUtils data class KotlinCodegenOptions( /** The characters to use for indenting generated Kotlin code. */ @@ -89,7 +90,7 @@ class KotlinCodeGenerator( private val propertyFileName: String get() = - "resources/META-INF/org/pkl/config/java/mapper/classes/${moduleSchema.moduleName}.properties" + "resources/META-INF/org/pkl/config/java/mapper/classes/${IoUtils.encodePath(moduleSchema.moduleName)}.properties" private val propertiesFile: String get() { @@ -195,7 +196,7 @@ class KotlinCodeGenerator( } private fun relativeOutputPathFor(moduleName: String): String { - val nameParts = moduleName.split(".") + val nameParts = moduleName.split(".").map(IoUtils::encodePath) val dirPath = nameParts.dropLast(1).joinToString("/") val fileName = nameParts.last().replaceFirstChar { it.titlecaseChar() } return if (dirPath.isEmpty()) { diff --git a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt index e8fd0319..64c8ef4f 100644 --- a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt +++ b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt @@ -1501,6 +1501,24 @@ class KotlinCodeGeneratorTest { confirmSerDe(bigStruct) } + @Test + fun `encoded file paths`(@TempDir path: Path) { + val kotlinCode = + generateKotlinFiles( + path, + PklModule( + "FooBar.pkl", + """ + module `Foo*Bar` + + someProp: String + """ + .trimIndent() + ) + ) + assertThat(kotlinCode).containsKey("kotlin/Foo(2a)Bar.kt") + } + private fun generateFiles(tempDir: Path, vararg pklModules: PklModule): Map { val pklFiles = pklModules.map { it.writeToDisk(tempDir.resolve("pkl/${it.name}.pkl")) } val evaluator = Evaluator.preconfigured() diff --git a/pkl-commons-test/src/main/kotlin/org/pkl/commons/test/PackageServer.kt b/pkl-commons-test/src/main/kotlin/org/pkl/commons/test/PackageServer.kt index 0a9a279c..e5c26a04 100644 --- a/pkl-commons-test/src/main/kotlin/org/pkl/commons/test/PackageServer.kt +++ b/pkl-commons-test/src/main/kotlin/org/pkl/commons/test/PackageServer.kt @@ -51,7 +51,14 @@ class PackageServer : AutoCloseable { const val FRUIT_1_1_SHA = "8d982761d182f2185e4180c82190791d9a60c721cb3393bb2e946fab90131e8c" fun populateCacheDir(cacheDir: Path) { - val basePath = cacheDir.resolve("package-1/localhost:$PORT") + doPopulateCacheDir(cacheDir.resolve("package-2/localhost(3a)$PORT")) + } + + fun populateLegacyCacheDir(cacheDir: Path) { + doPopulateCacheDir(cacheDir.resolve("package-1/localhost:$PORT")) + } + + private fun doPopulateCacheDir(basePath: Path) { basePath.deleteRecursively() Files.walk(packagesDir).use { stream -> stream.forEach { source -> diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/mapper/ClassRegistry.java b/pkl-config-java/src/main/java/org/pkl/config/java/mapper/ClassRegistry.java index e1e44786..25972842 100644 --- a/pkl-config-java/src/main/java/org/pkl/config/java/mapper/ClassRegistry.java +++ b/pkl-config-java/src/main/java/org/pkl/config/java/mapper/ClassRegistry.java @@ -22,6 +22,7 @@ import java.util.Properties; import java.util.Set; import org.pkl.config.java.InvalidMappingException; import org.pkl.core.PClassInfo; +import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; /** @@ -77,7 +78,7 @@ public class ClassRegistry { loadedModules.add(pklModuleName); var url = ClassRegistry.class.getResourceAsStream( - CLASSES_DIRECTORY + "/" + pklModuleName + ".properties"); + CLASSES_DIRECTORY + "/" + IoUtils.encodePath(pklModuleName) + ".properties"); if (url == null) { return; } diff --git a/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java b/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java index 7f645eb4..dbc3fd49 100644 --- a/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java +++ b/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java @@ -413,7 +413,7 @@ final class PackageResolvers { private final Path tmpDir; - private static final String CACHE_DIR_PREFIX = "package-1"; + private static final String CACHE_DIR_PREFIX = "package-2"; @GuardedBy("lock") private final EconomicMap fileSystems = EconomicMaps.create(); @@ -438,12 +438,14 @@ final class PackageResolvers { return path; } var checksumIdx = path.lastIndexOf("::"); - return path.substring(0, checksumIdx); + return IoUtils.encodePath(path.substring(0, checksumIdx)); } private Path getRelativePath(PackageUri uri) { return Path.of( - CACHE_DIR_PREFIX, uri.getUri().getAuthority(), getEffectivePackageUriPath(uri)); + CACHE_DIR_PREFIX, + IoUtils.encodePath(uri.getUri().getAuthority()), + getEffectivePackageUriPath(uri)); } private String getLastSegmentName(PackageUri packageUri) { diff --git a/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java b/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java index 9849b8d8..a5da3df2 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java @@ -597,6 +597,30 @@ public final class IoUtils { return newUri; } + /** + * Windows reserves characters {@code <>:"\|?*} in filenames. + * + *

For any such characters, enclose their decimal character code with parentheses. Verbatim + * {@code (} is encoded as {@code ((}. + */ + public static String encodePath(String path) { + if (path.isEmpty()) return path; + var sb = new StringBuilder(); + for (var i = 0; i < path.length(); i++) { + var character = path.charAt(i); + switch (character) { + case '<', '>', ':', '"', '\\', '|', '?', '*' -> { + sb.append('('); + sb.append(ByteArrayUtils.toHex(new byte[] {(byte) character})); + sb.append(")"); + } + case '(' -> sb.append("(("); + default -> sb.append(path.charAt(i)); + } + } + return sb.toString(); + } + private static int getExclamationMarkIndex(String jarUri) { var index = jarUri.indexOf('!'); if (index == -1) { diff --git a/pkl-core/src/test/kotlin/org/pkl/core/util/IoUtilsTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/util/IoUtilsTest.kt index c795cc2c..1e169df8 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/util/IoUtilsTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/util/IoUtilsTest.kt @@ -431,4 +431,14 @@ class IoUtilsTest { IoUtils.readString(URI("http://example.com").toURL()) } } + + @Test + fun `encodePath encodes characters reserved on windows`() { + assertThat(IoUtils.encodePath("foo:bar")).isEqualTo("foo(3a)bar") + assertThat(IoUtils.encodePath("<>:\"\\|?*")).isEqualTo("(3c)(3e)(3a)(22)(5c)(7c)(3f)(2a)") + assertThat(IoUtils.encodePath("foo(3a)bar")).isEqualTo("foo((3a)bar") + assertThat(IoUtils.encodePath("(")).isEqualTo("((") + assertThat(IoUtils.encodePath("3a)")).isEqualTo("3a)") + assertThat(IoUtils.encodePath("foo/bar/baz")).isEqualTo("foo/bar/baz") + } } diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/DocGenerator.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/DocGenerator.kt index 675727e6..05bd25a7 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/DocGenerator.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/DocGenerator.kt @@ -121,7 +121,7 @@ class DocGenerator( private fun createSymlinks(currentPackagesData: List) { for (packageData in currentPackagesData) { - val basePath = outputDir.resolve(packageData.ref.pkg) + 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 diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/DocPackageInfo.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/DocPackageInfo.kt index 24d64836..a4d343c0 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/DocPackageInfo.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/DocPackageInfo.kt @@ -203,9 +203,9 @@ data class DocPackageInfo( when { !moduleName.startsWith(prefix) -> null else -> { - val modulePath = moduleName.substring(prefix.length).replace('.', '/') + val modulePath = moduleName.substring(prefix.length).replace('.', '/').pathEncoded if (documentation == null) { - "$name/$version/$modulePath/index.html".toUri() + "${name.pathEncoded}/$version/$modulePath/index.html".toUri() } else { documentation.resolve("$modulePath/index.html") } diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/DocScope.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/DocScope.kt index ed799cae..30ca4b7f 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/DocScope.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/DocScope.kt @@ -71,10 +71,7 @@ internal sealed class DocScope { fun resolveModuleNameToRelativeDocUrl(name: String): URI? = resolveModuleNameToDocUrl(name)?.let { IoUtils.relativize(it, url) } - abstract fun resolveModuleNameToSourceUrl( - name: String, - sourceLocation: Member.SourceLocation - ): URI? + abstract fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation): URI? /** Resolves the given method name relative to this scope. */ abstract fun resolveMethod(name: String): MethodScope? @@ -207,10 +204,7 @@ internal class SiteScope( else -> null } - override fun resolveModuleNameToSourceUrl( - name: String, - sourceLocation: Member.SourceLocation - ): URI? = + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation): URI? = when { name.startsWith("pkl.") -> { val path = "/stdlib/${name.substring(4)}.pkl" @@ -253,7 +247,9 @@ internal class PackageScope( private val moduleScopes: Map by lazy { modules.associate { module -> val docUrl = - url.resolve(getModulePath(module.moduleName, modulePrefix).uriEncoded + "/index.html") + url.resolve( + getModulePath(module.moduleName, modulePrefix).pathEncoded.uriEncoded + "/index.html" + ) module.moduleName to ModuleScope(module, docUrl, this) } } @@ -262,9 +258,11 @@ internal class PackageScope( ModuleScope(pklBaseModule, resolveModuleNameToDocUrl("pkl.base")!!, null) } - override val url: URI by lazy { parent.url.resolve("./$name/$version/index.html") } + override val url: URI by lazy { parent.url.resolve("./${name.pathEncoded}/$version/index.html") } - override val dataUrl: URI by lazy { parent.url.resolve("./data/$name/$version/index.js") } + override val dataUrl: URI by lazy { + parent.url.resolve("./data/${name.pathEncoded}/$version/index.js") + } fun getModule(name: String): ModuleScope = moduleScopes.getValue(name) @@ -387,11 +385,11 @@ internal class ClassScope( override val url: URI by lazy { // `isModuleClass` distinction is relevant when this scope is a link target if (clazz.isModuleClass) parentUrl - else parentUrl.resolve("${clazz.simpleName.uriEncodedComponent}.html") + else parentUrl.resolve("${clazz.simpleName.pathEncoded.uriEncodedComponent}.html") } override val dataUrl: URI by lazy { - parent!!.dataUrl.resolve("${clazz.simpleName.uriEncodedComponent}.js") + parent!!.dataUrl.resolve("${clazz.simpleName.pathEncoded.uriEncodedComponent}.js") } override fun getMethod(name: String): MethodScope? = @@ -403,10 +401,8 @@ internal class ClassScope( override fun resolveModuleNameToDocUrl(name: String): URI? = parent!!.resolveModuleNameToDocUrl(name) - override fun resolveModuleNameToSourceUrl( - name: String, - sourceLocation: Member.SourceLocation - ): URI? = parent!!.resolveModuleNameToSourceUrl(name, sourceLocation) + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation): URI? = + parent!!.resolveModuleNameToSourceUrl(name, sourceLocation) override fun resolveMethod(name: String): MethodScope? = clazz.methods[name]?.let { MethodScope(it, this) } @@ -438,7 +434,7 @@ internal class TypeAliasScope( // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToDocUrl") - override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: Member.SourceLocation) = + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation) = // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToSourceUrl") @@ -464,7 +460,7 @@ internal class MethodScope(val method: PClass.Method, override val parent: DocSc // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToDocUrl") - override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: Member.SourceLocation) = + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation) = // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToSourceUrl") @@ -494,7 +490,7 @@ internal class PropertyScope( // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToDocUrl") - override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: Member.SourceLocation) = + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation) = // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToSourceUrl") @@ -525,7 +521,7 @@ internal class ParameterScope(val name: String, override val parent: DocScope) : // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToDocUrl") - override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: Member.SourceLocation) = + override fun resolveModuleNameToSourceUrl(name: String, sourceLocation: SourceLocation) = // only used for page scopes throw UnsupportedOperationException("resolveModuleNameToSourceUrl") diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/PackageDataGenerator.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/PackageDataGenerator.kt index bbeaf7f3..7b7b7e43 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/PackageDataGenerator.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/PackageDataGenerator.kt @@ -37,9 +37,11 @@ import org.pkl.core.util.IoUtils internal class PackageDataGenerator(private val outputDir: Path) { fun generate(pkg: DocPackage) { val path = - outputDir.resolve(pkg.name).resolve(pkg.version).resolve("package-data.json").apply { - createParentDirectories() - } + outputDir + .resolve(pkg.name.pathEncoded) + .resolve(pkg.version) + .resolve("package-data.json") + .apply { createParentDirectories() } PackageData(pkg).write(path) } diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/RuntimeDataGenerator.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/RuntimeDataGenerator.kt index 0d3e079a..90af5166 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/RuntimeDataGenerator.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/RuntimeDataGenerator.kt @@ -82,60 +82,71 @@ internal class RuntimeDataGenerator( } private fun writePackageFile(ref: PackageRef) { - outputDir.resolve("data/${ref.pkg}/${ref.version}/index.js").jsonWriter().use { writer -> - writer.isLenient = true - writer.writeLinks( - HtmlConstants.KNOWN_VERSIONS, - packageVersions.getOrDefault(ref.pkg, setOf()).sortedWith(descendingVersionComparator), - { it }, - { if (it == ref.version) null else ref.copy(version = it).pageUrlRelativeTo(ref) }, - { if (it == ref.version) CssConstants.CURRENT_VERSION else null } - ) - writer.writeLinks( - HtmlConstants.KNOWN_USAGES, - packageUsages.getOrDefault(ref, setOf()).packagesWithHighestVersions().sortedBy { it.pkg }, - PackageRef::pkg, - { it.pageUrlRelativeTo(ref) }, - { null } - ) - } + outputDir + .resolve("data/${ref.pkg.pathEncoded}/${ref.version.pathEncoded}/index.js") + .jsonWriter() + .use { writer -> + writer.isLenient = true + writer.writeLinks( + HtmlConstants.KNOWN_VERSIONS, + packageVersions.getOrDefault(ref.pkg, setOf()).sortedWith(descendingVersionComparator), + { it }, + { if (it == ref.version) null else ref.copy(version = it).pageUrlRelativeTo(ref) }, + { if (it == ref.version) CssConstants.CURRENT_VERSION else null } + ) + writer.writeLinks( + HtmlConstants.KNOWN_USAGES, + packageUsages.getOrDefault(ref, setOf()).packagesWithHighestVersions().sortedBy { + it.pkg + }, + PackageRef::pkg, + { it.pageUrlRelativeTo(ref) }, + { null } + ) + } } private fun writeModuleFile(ref: ModuleRef) { - outputDir.resolve("data/${ref.pkg}/${ref.version}/${ref.module}/index.js").jsonWriter().use { - writer -> - writer.isLenient = true - writer.writeLinks( - HtmlConstants.KNOWN_VERSIONS, - moduleVersions.getOrDefault(ref.id, setOf()).sortedWith(descendingVersionComparator), - { it }, - { if (it == ref.version) null else ref.copy(version = it).pageUrlRelativeTo(ref) }, - { if (it == ref.version) CssConstants.CURRENT_VERSION else null } + outputDir + .resolve( + "data/${ref.pkg.pathEncoded}/${ref.version.pathEncoded}/${ref.module.pathEncoded}/index.js" ) - writer.writeLinks( - HtmlConstants.KNOWN_USAGES, - typeUsages.getOrDefault(ref.moduleClassRef, setOf()).typesWithHighestVersions().sortedBy { - it.displayName - }, - TypeRef::displayName, - { it.pageUrlRelativeTo(ref) }, - { null } - ) - writer.writeLinks( - HtmlConstants.KNOWN_SUBTYPES, - subtypes.getOrDefault(ref.moduleClassRef, setOf()).typesWithHighestVersions().sortedBy { - it.displayName - }, - TypeRef::displayName, - { it.pageUrlRelativeTo(ref) }, - { null } - ) - } + .jsonWriter() + .use { writer -> + writer.isLenient = true + writer.writeLinks( + HtmlConstants.KNOWN_VERSIONS, + moduleVersions.getOrDefault(ref.id, setOf()).sortedWith(descendingVersionComparator), + { it }, + { if (it == ref.version) null else ref.copy(version = it).pageUrlRelativeTo(ref) }, + { if (it == ref.version) CssConstants.CURRENT_VERSION else null } + ) + writer.writeLinks( + HtmlConstants.KNOWN_USAGES, + typeUsages.getOrDefault(ref.moduleClassRef, setOf()).typesWithHighestVersions().sortedBy { + it.displayName + }, + TypeRef::displayName, + { it.pageUrlRelativeTo(ref) }, + { null } + ) + writer.writeLinks( + HtmlConstants.KNOWN_SUBTYPES, + subtypes.getOrDefault(ref.moduleClassRef, setOf()).typesWithHighestVersions().sortedBy { + it.displayName + }, + TypeRef::displayName, + { it.pageUrlRelativeTo(ref) }, + { null } + ) + } } private fun writeClassFile(ref: TypeRef) { outputDir - .resolve("data/${ref.pkg}/${ref.version}/${ref.module}/${ref.type}.js") + .resolve( + "data/${ref.pkg.pathEncoded}/${ref.version.pathEncoded}/${ref.module.pathEncoded}/${ref.type.pathEncoded}.js" + ) .jsonWriter() .use { writer -> writer.isLenient = true diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/SearchIndexGenerator.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/SearchIndexGenerator.kt index 6ae1b710..d177315c 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/SearchIndexGenerator.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/SearchIndexGenerator.kt @@ -64,7 +64,7 @@ internal class SearchIndexGenerator(private val outputDir: Path) { fun generate(docPackage: DocPackage) { val path = outputDir - .resolve("${docPackage.name}/${docPackage.version}/search-index.js") + .resolve("${docPackage.name.pathEncoded}/${docPackage.version}/search-index.js") .createParentDirectories() JsonWriter(path.bufferedWriter()).use { writer -> writer.apply { diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/Util.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/Util.kt index cb1b1556..63f55ff5 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/Util.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/Util.kt @@ -17,14 +17,13 @@ package org.pkl.doc import java.io.InputStream import java.net.URI -import java.net.URLEncoder -import java.nio.charset.StandardCharsets import java.nio.file.Path import kotlin.io.path.bufferedWriter import kotlin.io.path.outputStream import org.pkl.commons.createParentDirectories import org.pkl.core.* import org.pkl.core.parser.Lexer +import org.pkl.core.util.IoUtils import org.pkl.core.util.json.JsonWriter // overwrites any existing file @@ -148,3 +147,6 @@ internal val String.asModuleName: String internal val String.asIdentifier: String get() = Lexer.maybeQuoteIdentifier(this) + +internal val String.pathEncoded + get(): String = IoUtils.encodePath(this) diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/Bird/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/Bird/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/Bird/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/Bird/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/allFruit/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/allFruit/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/allFruit/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/allFruit/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/catalog/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/catalog/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/catalog/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/catalog/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/birds/0.5.0/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/birds/0.5.0/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/fruit/1.1.0/Fruit/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/fruit/1.1.0/Fruit/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/fruit/1.1.0/Fruit/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/fruit/1.1.0/Fruit/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/fruit/1.1.0/index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/fruit/1.1.0/index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost:0/fruit/1.1.0/index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/data/localhost(3a)0/fruit/1.1.0/index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/index.html index 0227712e..12c3df64 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/index.html @@ -114,7 +114,7 @@ age: Int

package
@@ -125,7 +125,7 @@ age: Int
package
diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/Bird/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/Bird/index.html similarity index 99% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/Bird/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/Bird/index.html index 6cd9c8d3..8329ca76 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/Bird/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/Bird/index.html @@ -4,7 +4,7 @@ Bird (localhost:0/birds:0.5.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/allFruit/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/allFruit/index.html similarity index 99% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/allFruit/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/allFruit/index.html index 6ae6e88a..99a908c7 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/allFruit/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/allFruit/index.html @@ -4,7 +4,7 @@ allFruit (localhost:0/birds:0.5.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/catalog/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/catalog/index.html similarity index 99% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/catalog/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/catalog/index.html index 214dedbe..9216f84c 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/catalog/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/catalog/index.html @@ -4,7 +4,7 @@ catalog (localhost:0/birds:0.5.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/index.html similarity index 98% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/index.html index fa374a60..7ab44551 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/index.html @@ -4,7 +4,7 @@ localhost:0/birds (0.5.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/package-data.json b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/package-data.json similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/package-data.json rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/package-data.json diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/search-index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/search-index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/0.5.0/search-index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/0.5.0/search-index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/current b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/current similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/birds/current rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/birds/current diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/Fruit/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/Fruit/index.html similarity index 99% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/Fruit/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/Fruit/index.html index f41bd986..7c8aeb6a 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/Fruit/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/Fruit/index.html @@ -4,7 +4,7 @@ Fruit (localhost:0/fruit:1.1.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/index.html b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/index.html similarity index 97% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/index.html rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/index.html index 96d1ae8d..f9bd5009 100644 --- a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/index.html +++ b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/index.html @@ -4,7 +4,7 @@ localhost:0/fruit (1.1.0) • Docsite Title - + diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/package-data.json b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/package-data.json similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/package-data.json rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/package-data.json diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/search-index.js b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/search-index.js similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/1.1.0/search-index.js rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/1.1.0/search-index.js diff --git a/pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/current b/pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/current similarity index 100% rename from pkl-doc/src/test/files/DocGeneratorTest/output/localhost:0/fruit/current rename to pkl-doc/src/test/files/DocGeneratorTest/output/localhost(3a)0/fruit/current diff --git a/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt b/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt index 86c57ccf..97b85d0d 100644 --- a/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt +++ b/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt @@ -2,8 +2,11 @@ package org.pkl.executor import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.condition.DisabledOnOs +import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource @@ -424,9 +427,11 @@ class EmbeddedExecutorTest { @ParameterizedTest @MethodSource("getAllTestExecutors") + @DisabledOnOs(OS.WINDOWS, disabledReason = "Can't populate legacy cache dir on Windows") fun `evaluate a project dependency`(executor: TestExecutor, @TempDir tempDir: Path) { val cacheDir = tempDir.resolve("packages") PackageServer.populateCacheDir(cacheDir) + PackageServer.populateLegacyCacheDir(cacheDir) val projectDir = tempDir.resolve("project/") projectDir.createDirectories() projectDir.resolve("PklProject").toFile().writeText("""