Compare commits

..

11 Commits

Author SHA1 Message Date
Jen Basch 6a3722a471 Prepare 0.29.1 release 2025-08-27 13:46:31 -07:00
Daniel Chao 2a7195125c Prepare 0.29.1 release (#1191) 2025-08-27 13:32:08 -07:00
Artem Yarmoliuk f0896ba16f Fix shell completion for paths (#1161) 2025-08-26 10:50:08 -07:00
Daniel Chao 9f7997bbc4 Fix missing resources in native pkldoc, and disable test mode (#1175)
This fixes two issues:

1. Test mode is enabled in pkldoc without the ability to turn it off
2. Native pkldoc is missing required resources

This also adds tests for both `jpkldoc` and `pkldoc`.
2025-08-26 10:49:56 -07:00
Daniel Chao 245b53bc8a Add docs for installing via winget (#1171) 2025-08-26 10:49:45 -07:00
Daniel Chao 9a92d75fcb Fix escaping in yaml strings (#1165)
The backslash needs to be escaped when rendering double-quoted YAML strings.

In addition, this escapes the following characters:

* next line (0x85)
* nbsp (0xa0)
2025-08-26 10:49:39 -07:00
Daniel Chao 419127fb0d Fix encoding for mapping with local members (#1152)
Fixes an issue where local members are included in
the mapping entry count.

Also: avoid re-computing Mapping.length
2025-08-26 10:49:20 -07:00
Jen Basch f7951510b9 Correctly handle EOF after unmatched backtick (#1187) 2025-08-25 14:35:12 -07:00
Daniel Chao 1b49ec9422 Fix download links (#1162)
* Snapshot repo has changed
* Fix a bug where pkl-codegen-java download link points to Sonatype instead of GitHub

Not in the PR: we also need to fix the snapshot download location;
but haven't figured out yet what the correct link is.
2025-07-31 08:44:22 -07:00
Dan Chao bdabcea216 Add link to 0.29 release notes 2025-07-24 10:55:05 -07:00
Islon Scherer 5f00f9c82e Prepare 0.29.0 release 2025-07-24 19:07:40 +02:00
64 changed files with 852 additions and 268 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
name: main name: main
title: Main Project title: Main Project
version: 0.29.0 version: 0.29.1
prerelease: false prerelease: false
nav: nav:
- nav.adoc - nav.adoc
@@ -3,7 +3,7 @@
// the following attributes must be updated immediately before a release // the following attributes must be updated immediately before a release
// pkl version corresponding to current git commit without -dev suffix or git hash // pkl version corresponding to current git commit without -dev suffix or git hash
:pkl-version-no-suffix: 0.29.0 :pkl-version-no-suffix: 0.29.1
// tells whether pkl version corresponding to current git commit // tells whether pkl version corresponding to current git commit
// is a release version (:is-release-version: '') or dev version (:!is-release-version:) // is a release version (:is-release-version: '') or dev version (:!is-release-version:)
:is-release-version: '' :is-release-version: ''
@@ -23,9 +23,9 @@ endif::[]
:uri-maven-docsite: https://central.sonatype.com :uri-maven-docsite: https://central.sonatype.com
:uri-sonatype: https://s01.oss.sonatype.org/content/groups/public :uri-snapshot-repo: https://central.sonatype.com/repository/maven-snapshots
:uri-maven-repo: https://s01.oss.sonatype.org/content/groups/public :uri-maven-repo: https://central.sonatype.com/repository/maven-snapshots
ifdef::is-release-version[] ifdef::is-release-version[]
:uri-maven-repo: https://repo1.maven.org/maven2 :uri-maven-repo: https://repo1.maven.org/maven2
endif::[] endif::[]
@@ -150,4 +150,5 @@ endif::[]
:uri-pkl-roadmap: https://github.com/orgs/apple/projects/12/views/1 :uri-pkl-roadmap: https://github.com/orgs/apple/projects/12/views/1
// TODO: figure out what the correct URL should be
:uri-sonatype-snapshot-download: https://s01.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.pkl-lang&v={pkl-artifact-version} :uri-sonatype-snapshot-download: https://s01.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.pkl-lang&v={pkl-artifact-version}
+2 -2
View File
@@ -5,7 +5,7 @@ include::ROOT:partial$component-attributes.adoc[]
:uri-pkl-codegen-java-download: {uri-sonatype-snapshot-download}&a=pkl-cli-codegen-java&e=jar :uri-pkl-codegen-java-download: {uri-sonatype-snapshot-download}&a=pkl-cli-codegen-java&e=jar
ifdef::is-release-version[] ifdef::is-release-version[]
:uri-pkl-cli-codegen-java-download: {github-releases}/pkl-codegen-java :uri-pkl-codegen-java-download: {github-releases}/pkl-codegen-java
endif::[] endif::[]
The Java source code generator takes Pkl class definitions as an input, and generates corresponding Java classes with equally named properties. The Java source code generator takes Pkl class definitions as an input, and generates corresponding Java classes with equally named properties.
@@ -33,7 +33,7 @@ The `pkl-codegen-java` library is available {uri-pkl-codegen-java-maven-module}[
It requires Java 17 or higher. It requires Java 17 or higher.
ifndef::is-release-version[] ifndef::is-release-version[]
NOTE: Snapshots are published to repository `{uri-sonatype}`. NOTE: Snapshots are published to repository `{uri-snapshot-repo}`.
endif::[] endif::[]
==== Gradle ==== Gradle
+26
View File
@@ -2,6 +2,7 @@
include::ROOT:partial$component-attributes.adoc[] include::ROOT:partial$component-attributes.adoc[]
:uri-homebrew: https://brew.sh :uri-homebrew: https://brew.sh
:uri-mise: https://mise.jdx.dev :uri-mise: https://mise.jdx.dev
:uri-winget: https://learn.microsoft.com/en-us/windows/package-manager/
:uri-pkl-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-amd64&e=bin :uri-pkl-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-amd64&e=bin
:uri-pkl-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-aarch64&e=bin :uri-pkl-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-aarch64&e=bin
@@ -107,6 +108,31 @@ ifndef::is-release-version[]
For instructions, switch to a release version of this page. For instructions, switch to a release version of this page.
endif::[] endif::[]
[[winget]]
=== Windows Package Manager
On Windows, release versions can be installed with {uri-winget}[Windows Package Manager].
ifdef::is-release-version[]
To install Pkl, run:
[source,shell]
----
winget install Apple.Pkl
----
To update Pkl, run:
[source,shell]
----
winget upgrade Apple.Pkl
----
endif::[]
ifndef::is-release-version[]
For instructions, switch to a release version of this page.
endif::[]
[[download]] [[download]]
=== Download === Download
+1 -1
View File
@@ -94,7 +94,7 @@ The `pkl-doc` library is available {uri-pkl-doc-maven}[from Maven Central].
It requires Java 17 or higher. It requires Java 17 or higher.
ifndef::is-release-version[] ifndef::is-release-version[]
NOTE: Snapshots are published to repository `{uri-sonatype}`. NOTE: Snapshots are published to repository `{uri-snapshot-repo}`.
endif::[] endif::[]
==== Gradle ==== Gradle
+1 -1
View File
@@ -25,7 +25,7 @@ It requires Java 17 or higher and Gradle 8.1 or higher.
Earlier Gradle versions are not supported. Earlier Gradle versions are not supported.
ifndef::is-release-version[] ifndef::is-release-version[]
NOTE: Snapshots are published to repository `{uri-sonatype}`. NOTE: Snapshots are published to repository `{uri-snapshot-repo}`.
endif::[] endif::[]
The plugin is applied as follows: The plugin is applied as follows:
+1 -1
View File
@@ -1,6 +1,6 @@
= Pkl 0.29 Release Notes = Pkl 0.29 Release Notes
:version: 0.29 :version: 0.29
:version-minor: 0.29.0 :version-minor: 0.29.1
:release-date: July 24th, 2025 :release-date: July 24th, 2025
include::ROOT:partial$component-attributes.adoc[] include::ROOT:partial$component-attributes.adoc[]
@@ -1,6 +1,26 @@
= Changelog = Changelog
include::ROOT:partial$component-attributes.adoc[] include::ROOT:partial$component-attributes.adoc[]
[[release-0.29.1]]
== 0.29.1 (2025-08-27)
=== Fixes
* Fixes an issue where autocompletion in Bash and ZSH do noes not suggest filenames (https://github.com/apple/pkl/pull/1161[#1161]).
* Fixes an issue where `pkldoc` throws a runtime error about failing to load class path resources (https://github.com/apple/pkl/issues/1174[#1174]).
* Fixes an issue where `pkldoc` always runs with `testMode` set to true.
* Fixes an issue where evaluating a module that ends with an unmatched backtick throws `ArrayIndexOutOfBoundsException` (https://github.com/apple/pkl/issues/1182[#1182]).
* Fixes the formatting of YAML strings when emitting backslash characters within quoted strings (https://github.com/apple/pkl/pull/1165[#1165]).
* Fixes an issue where `local` members inside `Mapping` objects are incorrectly encoded into binary format (https://github.com/apple/pkl/issues/1151[#1151]).
=== Contributors ❤️
Thank you to all the contributors for this release!
* https://github.com/bioball[@bioball]
* https://github.com/gordonbondon[@gordonbondon]
* https://github.com/HT154[@HT154]
[[release-0.29.0]] [[release-0.29.0]]
== 0.29.0 (2025-07-24) == 0.29.0 (2025-07-24)
@@ -2,6 +2,7 @@
The Pkl team aims to release a new version of Pkl in February, June, and October of each year. The Pkl team aims to release a new version of Pkl in February, June, and October of each year.
* xref:0.29.adoc[0.29 Release Notes]
* xref:0.28.adoc[0.28 Release Notes] * xref:0.28.adoc[0.28 Release Notes]
* xref:0.27.adoc[0.27 Release Notes] * xref:0.27.adoc[0.27 Release Notes]
* xref:0.26.adoc[0.26 Release Notes] * xref:0.26.adoc[0.26 Release Notes]
+1 -1
View File
@@ -1,7 +1,7 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
group=org.pkl-lang group=org.pkl-lang
version=0.29.0 version=0.29.1
# google-java-format requires jdk.compiler exports # google-java-format requires jdk.compiler exports
org.gradle.jvmargs= \ org.gradle.jvmargs= \
+3 -3
View File
@@ -2,15 +2,15 @@
"catalogs": {}, "catalogs": {},
"aliases": { "aliases": {
"pkl": { "pkl": {
"script-ref": "org.pkl-lang:pkl-cli-java:0.29.0", "script-ref": "org.pkl-lang:pkl-cli-java:0.29.1",
"java-agents": [] "java-agents": []
}, },
"pkl-codegen-java": { "pkl-codegen-java": {
"script-ref": "org.pkl-lang:pkl-codegen-java:0.29.0", "script-ref": "org.pkl-lang:pkl-codegen-java:0.29.1",
"java-agents": [] "java-agents": []
}, },
"pkl-codegen-kotlin": { "pkl-codegen-kotlin": {
"script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.29.0", "script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.29.1",
"java-agents": [] "java-agents": []
} }
}, },
@@ -15,6 +15,7 @@
*/ */
package org.pkl.cli.commands package org.pkl.cli.commands
import com.github.ajalt.clikt.completion.CompletionCandidates
import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.option
@@ -33,6 +34,7 @@ class EvalCommand : ModulesCommand(name = "eval", helpLink = helpLink) {
names = arrayOf("-o", "--output-path"), names = arrayOf("-o", "--output-path"),
metavar = "path", metavar = "path",
help = "File path where the output file is placed.", help = "File path where the output file is placed.",
completionCandidates = CompletionCandidates.Path,
) )
.single() .single()
@@ -59,6 +61,7 @@ class EvalCommand : ModulesCommand(name = "eval", helpLink = helpLink) {
names = arrayOf("-m", "--multiple-file-output-path"), names = arrayOf("-m", "--multiple-file-output-path"),
metavar = "path", metavar = "path",
help = "Directory where a module's multiple file output is placed.", help = "Directory where a module's multiple file output is placed.",
completionCandidates = CompletionCandidates.Path,
) )
.single() .single()
.validate { .validate {
@@ -15,6 +15,7 @@
*/ */
package org.pkl.cli.commands package org.pkl.cli.commands
import com.github.ajalt.clikt.completion.CompletionCandidates
import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.NoOpCliktCommand import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands import com.github.ajalt.clikt.core.subcommands
@@ -116,6 +117,7 @@ class PackageCommand : BaseCommand(name = "package", helpLink = helpLink) {
names = arrayOf("--output-path"), names = arrayOf("--output-path"),
help = "The directory to write artifacts to", help = "The directory to write artifacts to",
metavar = "path", metavar = "path",
completionCandidates = CompletionCandidates.Path,
) )
.single() .single()
.default(".out/%{name}@%{version}") .default(".out/%{name}@%{version}")
@@ -15,6 +15,7 @@
*/ */
package org.pkl.cli.commands package org.pkl.cli.commands
import com.github.ajalt.clikt.completion.CompletionCandidates
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.multiple
@@ -30,7 +31,11 @@ class TestCommand : BaseCommand(name = "test", helpLink = helpLink) {
override val helpString = "Run tests within the given module(s)" override val helpString = "Run tests within the given module(s)"
val modules: List<URI> by val modules: List<URI> by
argument(name = "modules", help = "Module paths or URIs to evaluate.") argument(
name = "modules",
help = "Module paths or URIs to evaluate.",
completionCandidates = CompletionCandidates.Path,
)
.convert { BaseOptions.parseModuleName(it) } .convert { BaseOptions.parseModuleName(it) }
.multiple() .multiple()
@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -64,4 +64,11 @@ class ReplMessagesTest {
startIndex = examples.indexOf("```", endIndex + 3) startIndex = examples.indexOf("```", endIndex + 3)
} }
} }
@Test
fun `handle single backtick`() {
val responses = server.handleRequest(ReplRequest.Eval("1", "`", true, true))
assertThat(responses.size).isEqualTo(1)
assertThat(responses).hasOnlyElementsOfType(ReplResponse.EvalError::class.java)
}
} }
@@ -15,6 +15,7 @@
*/ */
package org.pkl.commons.cli.commands package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.completion.CompletionCandidates
import com.github.ajalt.clikt.parameters.groups.OptionGroup import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.enum
@@ -165,6 +166,7 @@ class BaseOptions : OptionGroup() {
option( option(
names = arrayOf("-f", "--format"), names = arrayOf("-f", "--format"),
help = "Output format to generate. <${output.joinToString()}>", help = "Output format to generate. <${output.joinToString()}>",
completionCandidates = CompletionCandidates.Fixed(output.toSet()),
) )
.single() .single()
@@ -187,9 +189,13 @@ class BaseOptions : OptionGroup() {
.splitAll(File.pathSeparator) .splitAll(File.pathSeparator)
val settings: URI? by val settings: URI? by
option(names = arrayOf("--settings"), help = "Pkl settings module to use.").single().convert { option(
parseModuleName(it) names = arrayOf("--settings"),
} help = "Pkl settings module to use.",
completionCandidates = CompletionCandidates.Path,
)
.single()
.convert { parseModuleName(it) }
val timeout: Duration? by val timeout: Duration? by
option( option(
@@ -15,6 +15,7 @@
*/ */
package org.pkl.commons.cli.commands package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.completion.CompletionCandidates
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.multiple
@@ -24,7 +25,11 @@ import java.net.URI
abstract class ModulesCommand(name: String, helpLink: String) : abstract class ModulesCommand(name: String, helpLink: String) :
BaseCommand(name = name, helpLink = helpLink) { BaseCommand(name = name, helpLink = helpLink) {
open val modules: List<URI> by open val modules: List<URI> by
argument(name = "modules", help = "Module paths or URIs to evaluate.") argument(
name = "modules",
help = "Module paths or URIs to evaluate.",
completionCandidates = CompletionCandidates.Path,
)
.convert { BaseOptions.parseModuleName(it) } .convert { BaseOptions.parseModuleName(it) }
.multiple(required = true) .multiple(required = true)
@@ -0,0 +1,85 @@
/*
* Copyright © 2024-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.commons.test
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.exists
import org.pkl.commons.test.FileTestUtils.rootProjectDir
sealed class ExecutablePaths(protected val gradleProject: String) {
abstract val allNative: List<Path>
val existingNative: List<Path>
get() = allNative.filter(Files::exists)
val firstExistingNative: Path
get() =
existingNative.firstOrNull()
?: throw AssertionError(
"Native executable not found on system. " +
"To fix this problem, run `./gradlew $gradleProject:assembleNative`."
)
protected fun executable(name: String): Path =
rootProjectDir.resolve("$gradleProject/build/executable").resolve(name)
protected fun javaExecutable(name: String): Path {
val isWindows = System.getProperty("os.name").startsWith("Windows")
val effectiveName = if (isWindows) "$name.bat" else name
return rootProjectDir.resolve("$gradleProject/build/executable").resolve(effectiveName).also {
path ->
if (!path.exists()) {
throw AssertionError(
"Java executable not found on system. " +
"To fix this problem, run `./gradlew $gradleProject:javaExecutable`."
)
}
}
}
}
@Suppress("ClassName")
object Executables {
object pkl : ExecutablePaths("pkl-cli") {
val macAarch64: Path = executable("pkl-macos-aarch64")
val macAmd64: Path = executable("pkl-macos-amd64")
val linuxAarch64: Path = executable("pkl-linux-aarch64")
val linuxAmd64: Path = executable("pkl-linux-amd64")
val alpineAmd64: Path = executable("pkl-alpine-linux-amd64")
val windowsAmd64: Path = executable("pkl-windows-amd64.exe")
// order (aarch64 before amd64, linux before alpine) affects [firstExisting]
override val allNative: List<Path> =
listOf(macAarch64, macAmd64, linuxAarch64, linuxAmd64, alpineAmd64, windowsAmd64)
}
object pkldoc : ExecutablePaths("pkl-doc") {
val macAarch64: Path = executable("pkldoc-macos-aarch64")
val macAmd64: Path = executable("pkldoc-macos-amd64")
val linuxAarch64: Path = executable("pkldoc-linux-aarch64")
val linuxAmd64: Path = executable("pkldoc-linux-amd64")
val alpineAmd64: Path = executable("pkldoc-alpine-linux-amd64")
val windowsAmd64: Path = executable("pkldoc-windows-amd64.exe")
val javaExecutable: Path by lazy { javaExecutable("jpkldoc") }
// order (aarch64 before amd64, linux before alpine) affects [firstExisting]
override val allNative: List<Path> =
listOf(macAarch64, macAmd64, linuxAarch64, linuxAmd64, alpineAmd64, windowsAmd64)
}
}
@@ -1,47 +0,0 @@
/*
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.commons.test
import java.nio.file.Files
import java.nio.file.Path
import org.pkl.commons.test.FileTestUtils.rootProjectDir
object PklExecutablePaths {
val macAarch64: Path = executablePath("pkl-macos-aarch64")
val macAmd64: Path = executablePath("pkl-macos-amd64")
val linuxAarch64: Path = executablePath("pkl-linux-aarch64")
val linuxAmd64: Path = executablePath("pkl-linux-amd64")
val alpineAmd64: Path = executablePath("pkl-alpine-linux-amd64")
val windowsAmd64: Path = executablePath("pkl-windows-amd64.exe")
// order (aarch64 before amd64, linux before alpine) affects [firstExisting]
val all: List<Path> =
listOf(macAarch64, macAmd64, linuxAarch64, linuxAmd64, alpineAmd64, windowsAmd64)
val existing: List<Path>
get() = all.filter(Files::exists)
val firstExisting: Path
get() =
existing.firstOrNull()
?: throw AssertionError(
"Native executable not found on system. " +
"To fix this problem, run `./gradlew assembleNative`."
)
private fun executablePath(name: String): Path =
rootProjectDir.resolve("pkl-cli/build/executable").resolve(name)
}
@@ -33,7 +33,7 @@ import org.pkl.parser.syntax.Module;
@TruffleLanguage.Registration( @TruffleLanguage.Registration(
id = "pkl", id = "pkl",
name = "Pkl", name = "Pkl",
version = "0.29.0", version = "0.29.1",
characterMimeTypes = VmLanguage.MIME_TYPE, characterMimeTypes = VmLanguage.MIME_TYPE,
contextPolicy = ContextPolicy.SHARED) contextPolicy = ContextPolicy.SHARED)
public final class VmLanguage extends TruffleLanguage<VmContext> { public final class VmLanguage extends TruffleLanguage<VmContext> {
@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.pkl.core.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.MaterializedFrame;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap;
@@ -25,10 +26,10 @@ import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.util.CollectionUtils; import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.LateInit; import org.pkl.core.util.LateInit;
import org.pkl.core.util.MutableLong;
public final class VmMapping extends VmListingOrMapping { public final class VmMapping extends VmListingOrMapping {
private long cachedLength = -1;
private int cachedEntryCount = -1;
@GuardedBy("this") @GuardedBy("this")
private @LateInit VmSet __allKeys; private @LateInit VmSet __allKeys;
@@ -124,7 +125,7 @@ public final class VmMapping extends VmListingOrMapping {
// could use shallow force, but deep force is cached // could use shallow force, but deep force is cached
force(false); force(false);
other.force(false); other.force(false);
if (getEntryCount() != other.getEntryCount()) return false; if (getLength() != other.getLength()) return false;
var cursor = cachedValues.getEntries(); var cursor = cachedValues.getEntries();
while (cursor.advance()) { while (cursor.advance()) {
@@ -162,16 +163,21 @@ public final class VmMapping extends VmListingOrMapping {
return result; return result;
} }
// assumes mapping has been forced @TruffleBoundary
public int getEntryCount() { public long getLength() {
if (cachedEntryCount != -1) return cachedEntryCount; if (cachedLength != -1) return cachedLength;
var count = new MutableLong(0);
var result = 0; var visited = new HashSet<>();
for (var key : cachedValues.getKeys()) { iterateMembers(
if (key instanceof Identifier) continue; (key, member) -> {
result += 1; var alreadyVisited = !visited.add(key);
} // important to record hidden member as visited before skipping it
cachedEntryCount = result; // because any overriding member won't carry a `hidden` identifier
return result; if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
count.getAndIncrement();
return true;
});
cachedLength = count.get();
return cachedLength;
} }
} }
@@ -18,7 +18,6 @@ package org.pkl.core.stdlib.base;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode;
import java.util.HashSet;
import org.pkl.core.ast.lambda.ApplyVmFunction1Node; import org.pkl.core.ast.lambda.ApplyVmFunction1Node;
import org.pkl.core.ast.lambda.ApplyVmFunction2Node; import org.pkl.core.ast.lambda.ApplyVmFunction2Node;
import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen; import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen;
@@ -31,7 +30,6 @@ import org.pkl.core.stdlib.ExternalMethod2Node;
import org.pkl.core.stdlib.ExternalPropertyNode; import org.pkl.core.stdlib.ExternalPropertyNode;
import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.MutableBoolean; import org.pkl.core.util.MutableBoolean;
import org.pkl.core.util.MutableLong;
import org.pkl.core.util.MutableReference; import org.pkl.core.util.MutableReference;
public final class MappingNodes { public final class MappingNodes {
@@ -53,20 +51,8 @@ public final class MappingNodes {
public abstract static class length extends ExternalPropertyNode { public abstract static class length extends ExternalPropertyNode {
@Specialization @Specialization
@TruffleBoundary
protected long eval(VmMapping self) { protected long eval(VmMapping self) {
var count = new MutableLong(0); return self.getLength();
var visited = new HashSet<>();
self.iterateMembers(
(key, member) -> {
var alreadyVisited = !visited.add(key);
// important to record hidden member as visited before skipping it
// because any overriding member won't carry a `hidden` identifier
if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
count.getAndIncrement();
return true;
});
return count.get();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,33 +19,55 @@ import org.pkl.core.util.AbstractCharEscaper;
import org.pkl.core.util.IoUtils; import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Nullable; import org.pkl.core.util.Nullable;
// https://yaml.org/spec/1.2.2/#57-escaped-characters /**
* Emits escape sequences for YAML. This is only used when emitting double-quoted strings.
*
* <p>Note: we don't need to escape space ({@code 0x20}) because we don't generate quoted multiline
* strings. We also don't need to escape forward slash ({@code 0x2f}) because a normal forward slash
* is also valid YAML.
*
* @see <a
* href="https://yaml.org/spec/1.2.2/#57-escaped-characters">https://yaml.org/spec/1.2.2/#57-escaped-characters</a>
*/
public final class YamlEscaper extends AbstractCharEscaper { public final class YamlEscaper extends AbstractCharEscaper {
private static final String[] REPLACEMENTS; private static final String[] REPLACEMENTS;
static { static {
REPLACEMENTS = new String[0x22 + 1]; REPLACEMENTS = new String[0xA0 + 1];
for (var i = 0; i < 0x20; i++) { for (var i = 0; i < 0x20; i++) {
REPLACEMENTS[i] = IoUtils.toHexEscape(i); REPLACEMENTS[i] = IoUtils.toHexEscape(i);
} }
// ns-esc-null
REPLACEMENTS[0x00] = "\\0"; REPLACEMENTS[0x00] = "\\0";
// ns-esc-bell
REPLACEMENTS[0x07] = "\\a"; REPLACEMENTS[0x07] = "\\a";
// ns-esc-backspace
REPLACEMENTS[0x08] = "\\b"; REPLACEMENTS[0x08] = "\\b";
// ns-esc-horizontal-tab
REPLACEMENTS[0x09] = "\\t"; REPLACEMENTS[0x09] = "\\t";
// ns-esc-line-feed
REPLACEMENTS[0x0A] = "\\n"; REPLACEMENTS[0x0A] = "\\n";
// ns-esc-vertical-tab
REPLACEMENTS[0x0B] = "\\v"; REPLACEMENTS[0x0B] = "\\v";
// ns-esc-form-feed
REPLACEMENTS[0x0C] = "\\f"; REPLACEMENTS[0x0C] = "\\f";
// ns-esc-carriage-return
REPLACEMENTS[0x0D] = "\\r"; REPLACEMENTS[0x0D] = "\\r";
// ns-esc-escape
REPLACEMENTS[0x1B] = "\\e"; REPLACEMENTS[0x1B] = "\\e";
// we don't ever need to escape 0x20 because we don't generate quoted multiline strings // ns-esc-double-quote
REPLACEMENTS[0x22] = "\\\""; REPLACEMENTS[0x22] = "\\\"";
// ns-esc-backslash
REPLACEMENTS[0x5c] = "\\\\";
// ns-esc-next-line
REPLACEMENTS[0x85] = "\\N";
// ns-esc-non-breaking-space
REPLACEMENTS[0xA0] = "\\_";
} }
@Override @Override
protected @Nullable String findReplacement(char ch) { protected @Nullable String findReplacement(char ch) {
//noinspection UnnecessaryUnicodeEscape //noinspection UnnecessaryUnicodeEscape
return ch <= '\u0022' return ch <= 0xA0 ? REPLACEMENTS[ch] : ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null;
? REPLACEMENTS[ch]
: ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null;
} }
} }
@@ -0,0 +1,36 @@
// test escaping in double quotes
// every string is prefixed with `\t` s.t. YamlRenderer will emit double-quoted strings.
`null` = "\t\u{00}"
bell = "\t\u{7}"
backspace = "\t\u{8}"
horizontalTab = "\t"
lineFeed = "\t\u{a}"
verticalTab = "\t\u{b}"
formFeed = "\t\u{c}"
carriageReturn = "\t\r"
escape = "\t\u{1b}"
doubleQuote = "\t\""
backslash = "\t\\"
nextLine = "\t\u{85}"
nbsp = "\t\u{a0}"
lineSep = "\t\u{2028}"
paragraphSep = "\t\u{2029}"
output {
renderer = new YamlRenderer {}
}
@@ -114,12 +114,12 @@ examples {
render(".NAN") render(".NAN")
render(".nAn") // never float render(".nAn") // never float
} }
["tag like strings"] { ["tag like strings"] {
"!!bool true" "!!bool true"
"!!str my string value" "!!str my string value"
} }
["number like string keys"] { ["number like string keys"] {
render(new Dynamic { render(new Dynamic {
`0` = "0" `0` = "0"
@@ -0,0 +1 @@
`
@@ -0,0 +1,15 @@
'null': "\t\0"
bell: "\t\a"
backspace: "\t\b"
horizontalTab: "\t"
lineFeed: "\t\n"
verticalTab: "\t\v"
formFeed: "\t\f"
carriageReturn: "\t\r"
escape: "\t\e"
doubleQuote: "\t\""
backslash: "\t\\"
nextLine: "\t\N"
nbsp: "\t\_"
lineSep: "\t\L"
paragraphSep: "\t\P"
@@ -0,0 +1,7 @@
–– Pkl Error ––
Unexpected character `
`. Did you mean `backquote`?
x | `
^
at singleBacktick (file:///$snippetsDir/input/errors/singleBacktick.pkl)
@@ -27,10 +27,10 @@ import org.junit.platform.engine.EngineDiscoveryRequest
import org.junit.platform.engine.TestDescriptor import org.junit.platform.engine.TestDescriptor
import org.junit.platform.engine.UniqueId import org.junit.platform.engine.UniqueId
import org.junit.platform.engine.support.descriptor.EngineDescriptor import org.junit.platform.engine.support.descriptor.EngineDescriptor
import org.pkl.commons.test.Executables
import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.FileTestUtils
import org.pkl.commons.test.InputOutputTestEngine import org.pkl.commons.test.InputOutputTestEngine
import org.pkl.commons.test.PackageServer import org.pkl.commons.test.PackageServer
import org.pkl.commons.test.PklExecutablePaths
import org.pkl.core.http.HttpClient import org.pkl.core.http.HttpClient
import org.pkl.core.project.Project import org.pkl.core.project.Project
import org.pkl.core.util.IoUtils import org.pkl.core.util.IoUtils
@@ -298,27 +298,27 @@ abstract class AbstractNativeLanguageSnippetTestsEngine : AbstractLanguageSnippe
} }
class MacAmd64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class MacAmd64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.macAmd64 override val pklExecutablePath: Path = Executables.pkl.macAmd64
override val testClass: KClass<*> = MacLanguageSnippetTests::class override val testClass: KClass<*> = MacLanguageSnippetTests::class
} }
class MacAarch64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class MacAarch64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.macAarch64 override val pklExecutablePath: Path = Executables.pkl.macAarch64
override val testClass: KClass<*> = MacLanguageSnippetTests::class override val testClass: KClass<*> = MacLanguageSnippetTests::class
} }
class LinuxAmd64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class LinuxAmd64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.linuxAmd64 override val pklExecutablePath: Path = Executables.pkl.linuxAmd64
override val testClass: KClass<*> = LinuxLanguageSnippetTests::class override val testClass: KClass<*> = LinuxLanguageSnippetTests::class
} }
class LinuxAarch64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class LinuxAarch64LanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.linuxAarch64 override val pklExecutablePath: Path = Executables.pkl.linuxAarch64
override val testClass: KClass<*> = LinuxLanguageSnippetTests::class override val testClass: KClass<*> = LinuxLanguageSnippetTests::class
} }
class AlpineLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class AlpineLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.alpineAmd64 override val pklExecutablePath: Path = Executables.pkl.alpineAmd64
override val testClass: KClass<*> = AlpineLanguageSnippetTests::class override val testClass: KClass<*> = AlpineLanguageSnippetTests::class
} }
@@ -340,7 +340,7 @@ private val windowsNativeExcludedTests
) )
class WindowsLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() { class WindowsLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
override val pklExecutablePath: Path = PklExecutablePaths.windowsAmd64 override val pklExecutablePath: Path = Executables.pkl.windowsAmd64
override val testClass: KClass<*> = WindowsLanguageSnippetTests::class override val testClass: KClass<*> = WindowsLanguageSnippetTests::class
override val excludedTests: List<Regex> override val excludedTests: List<Regex>
get() = super.excludedTests + windowsNativeExcludedTests + windowsExcludedTests get() = super.excludedTests + windowsNativeExcludedTests + windowsExcludedTests
+25
View File
@@ -66,6 +66,31 @@ publishing {
} }
} }
val testNativeExecutable by
tasks.registering(Test::class) {
inputs.dir("src/test/files/DocGeneratorTest/input")
outputs.dir("src/test/files/DocGeneratorTest/output")
systemProperty("org.pkl.doc.NativeExecutableTest", "true")
include(listOf("**/NativeExecutableTest.class"))
}
val testJavaExecutable by
tasks.registering(Test::class) {
dependsOn(tasks.javaExecutable)
inputs.dir("src/test/files/DocGeneratorTest/input")
outputs.dir("src/test/files/DocGeneratorTest/output")
systemProperty("org.pkl.doc.JavaExecutableTest", "true")
include(listOf("**/JavaExecutableTest.class"))
}
tasks.check { dependsOn(testJavaExecutable) }
tasks.testNative { dependsOn(testNativeExecutable) }
tasks.withType<NativeImageBuild> { extraNativeImageArgs.add("-H:IncludeResources=org/pkl/doc/.*") }
tasks.jar { manifest { attributes += mapOf("Main-Class" to "org.pkl.doc.Main") } } tasks.jar { manifest { attributes += mapOf("Main-Class" to "org.pkl.doc.Main") } }
htmlValidator { sources = files("src/test/files/DocGeneratorTest/output") } htmlValidator { sources = files("src/test/files/DocGeneratorTest/output") }
tasks.validateHtml { mustRunAfter(testJavaExecutable) }
+4 -1
View File
@@ -69,6 +69,9 @@ class DocCommand : BaseCommand(name = "pkldoc", helpLink = helpLink) {
.single() .single()
.flag(default = false) .flag(default = false)
private val isTestMode by
option(names = arrayOf("--test-mode"), help = "Internal test mode", hidden = true).flag()
private val projectOptions by ProjectOptions() private val projectOptions by ProjectOptions()
override val helpString: String = "Generate HTML documentation from Pkl modules and packages." override val helpString: String = "Generate HTML documentation from Pkl modules and packages."
@@ -78,7 +81,7 @@ class DocCommand : BaseCommand(name = "pkldoc", helpLink = helpLink) {
CliDocGeneratorOptions( CliDocGeneratorOptions(
baseOptions.baseOptions(modules, projectOptions), baseOptions.baseOptions(modules, projectOptions),
outputDir, outputDir,
true, isTestMode,
noSymlinks, noSymlinks,
) )
CliDocGenerator(options).run() CliDocGenerator(options).run()
@@ -18,11 +18,9 @@ package org.pkl.doc
import com.google.common.jimfs.Configuration import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs import com.google.common.jimfs.Jimfs
import java.net.URI import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.* import kotlin.io.path.*
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
@@ -31,83 +29,20 @@ import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.cli.CliException import org.pkl.commons.cli.CliException
import org.pkl.commons.readString
import org.pkl.commons.test.FileTestUtils
import org.pkl.commons.test.PackageServer import org.pkl.commons.test.PackageServer
import org.pkl.commons.test.listFilesRecursively
import org.pkl.commons.toPath
import org.pkl.commons.walk import org.pkl.commons.walk
import org.pkl.core.Version import org.pkl.core.Version
import org.pkl.core.util.IoUtils
import org.pkl.doc.DocGenerator.Companion.current import org.pkl.doc.DocGenerator.Companion.current
class CliDocGeneratorTest { class CliDocGeneratorTest {
companion object { companion object {
private val tempFileSystem: FileSystem by lazy { Jimfs.newFileSystem(Configuration.unix()) } private val tempFileSystem by lazy { Jimfs.newFileSystem(Configuration.unix()) }
private val tmpOutputDir by lazy { private val tmpOutputDir: Path by lazy {
tempFileSystem.getPath("/work/output").apply { createDirectories() } tempFileSystem.getPath("/work/output").apply { createDirectories() }
} }
private val projectDir = FileTestUtils.rootProjectDir.resolve("pkl-doc") private val helper = DocGeneratorTestHelper()
private val inputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/input").apply { assert(exists()) }
}
private val docsiteModule: URI by lazy {
inputDir.resolve("docsite-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1PackageModule: URI by lazy {
inputDir.resolve("com.package1/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
private val package2PackageModule: URI by lazy {
inputDir.resolve("com.package2/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1InputModules: List<URI> by lazy {
inputDir
.resolve("com.package1")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
private val package2InputModules: List<URI> by lazy {
inputDir
.resolve("com.package2")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
private val expectedOutputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/output").createDirectories()
}
private val expectedOutputFiles: List<Path> by lazy { expectedOutputDir.listFilesRecursively() }
private val actualOutputDir: Path by lazy { tempFileSystem.getPath("/work/DocGeneratorTest") }
private val actualOutputFiles: List<Path> by lazy { actualOutputDir.listFilesRecursively() }
private val expectedRelativeOutputFiles: List<String> by lazy {
expectedOutputFiles.map { path ->
IoUtils.toNormalizedPathString(expectedOutputDir.relativize(path)).let { str ->
// Git will by default clone symlinks as shortcuts on Windows, and shortcuts have a
// `.lnk` extension.
if (IoUtils.isWindows() && str.endsWith(".lnk")) str.dropLast(4) else str
}
}
}
private val actualRelativeOutputFiles: List<String> by lazy {
actualOutputFiles.map { IoUtils.toNormalizedPathString(actualOutputDir.relativize(it)) }
}
private val binaryFileExtensions = setOf("woff2", "png", "svg")
private fun runDocGenerator(outputDir: Path, cacheDir: Path?, noSymlinks: Boolean = false) { private fun runDocGenerator(outputDir: Path, cacheDir: Path?, noSymlinks: Boolean = false) {
CliDocGenerator( CliDocGenerator(
@@ -115,14 +50,14 @@ class CliDocGeneratorTest {
CliBaseOptions( CliBaseOptions(
sourceModules = sourceModules =
listOf( listOf(
docsiteModule, helper.docsiteModule,
package1PackageModule, helper.package1PackageModule,
package2PackageModule, helper.package2PackageModule,
URI("package://localhost:0/birds@0.5.0"), URI("package://localhost:0/birds@0.5.0"),
URI("package://localhost:0/fruit@1.1.0"), URI("package://localhost:0/fruit@1.1.0"),
URI("package://localhost:0/unlisted@1.0.0"), URI("package://localhost:0/unlisted@1.0.0"),
URI("package://localhost:0/deprecated@1.0.0"), URI("package://localhost:0/deprecated@1.0.0"),
) + package1InputModules + package2InputModules, ) + helper.package1InputModules + helper.package2InputModules,
moduleCacheDir = cacheDir, moduleCacheDir = cacheDir,
), ),
outputDir = outputDir, outputDir = outputDir,
@@ -135,19 +70,7 @@ class CliDocGeneratorTest {
@JvmStatic @JvmStatic
private fun generateDocs(): List<String> { private fun generateDocs(): List<String> {
val cacheDir = Files.createTempDirectory("cli-doc-generator-test-cache") return helper.generateDocs()
PackageServer.populateCacheDir(cacheDir)
runDocGenerator(actualOutputDir, cacheDir)
val missingFiles = expectedRelativeOutputFiles - actualRelativeOutputFiles.toSet()
if (missingFiles.isNotEmpty()) {
Assertions.fail<Unit>(
"The following expected files were not actually generated:\n" +
missingFiles.joinToString("\n")
)
}
return actualRelativeOutputFiles
} }
} }
@@ -158,6 +81,7 @@ class CliDocGeneratorTest {
createParentDirectories() createParentDirectories()
createFile() createFile()
} }
val descriptor2 = val descriptor2 =
tempFileSystem.getPath("/work/dir2/docsite-info.pkl").apply { tempFileSystem.getPath("/work/dir2/docsite-info.pkl").apply {
createParentDirectories() createParentDirectories()
@@ -220,49 +144,20 @@ class CliDocGeneratorTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("generateDocs") @MethodSource("generateDocs")
fun test(relativeFilePath: String) { fun test(relativeFilePath: String) {
val actualFile = actualOutputDir.resolve(relativeFilePath) DocTestUtils.testExpectedFile(
assertThat(actualFile) helper.expectedOutputDir,
.withFailMessage("Test bug: $actualFile should exist but does not.") helper.actualOutputDir,
.exists() relativeFilePath,
)
// symlinks on Git and Windows is rather finnicky; they create shortcuts by default unless
// a core Git option is set. Also, by default, symlinks require administrator privileges to run.
// We'll just test that the symlink got created but skip verifying that it points to the right
// place.
if (actualFile.isSymbolicLink() && IoUtils.isWindows()) return
val expectedFile = expectedOutputDir.resolve(relativeFilePath)
if (expectedFile.exists()) {
when {
expectedFile.isSymbolicLink() -> {
assertThat(actualFile).isSymbolicLink
assertThat(expectedFile.readSymbolicLink().toString().toPath())
.isEqualTo(actualFile.readSymbolicLink().toString().toPath())
}
expectedFile.extension in binaryFileExtensions ->
assertThat(actualFile.readBytes()).isEqualTo(expectedFile.readBytes())
else -> assertThat(actualFile.readString()).isEqualTo(expectedFile.readString())
}
} else {
expectedFile.createParentDirectories()
if (actualFile.isSymbolicLink()) {
// needs special handling because `copyTo` can't copy symlinks between file systems
val linkTarget = actualFile.readSymbolicLink()
assertThat(linkTarget).isRelative
Files.createSymbolicLink(expectedFile, linkTarget.toString().toPath())
} else {
actualFile.copyTo(expectedFile)
}
Assertions.fail("Created missing expected file `$relativeFilePath`.")
}
} }
@Test @Test
fun `creates a symlink called current by default`(@TempDir tempDir: Path) { fun `creates a symlink called current by default`(@TempDir tempDir: Path) {
PackageServer.populateCacheDir(tempDir) PackageServer.populateCacheDir(tempDir)
runDocGenerator(actualOutputDir, tempDir) runDocGenerator(helper.actualOutputDir, tempDir)
val expectedSymlink = actualOutputDir.resolve("com.package1/current") val expectedSymlink = helper.actualOutputDir.resolve("com.package1/current")
val expectedDestination = actualOutputDir.resolve("com.package1/1.2.3") val expectedDestination = helper.actualOutputDir.resolve("com.package1/1.2.3")
assertThat(expectedSymlink).isSymbolicLink().matches { assertThat(expectedSymlink).isSymbolicLink().matches {
Files.isSameFile(it, expectedDestination) Files.isSameFile(it, expectedDestination)
@@ -274,10 +169,10 @@ class CliDocGeneratorTest {
@TempDir tempDir: Path @TempDir tempDir: Path
) { ) {
PackageServer.populateCacheDir(tempDir) PackageServer.populateCacheDir(tempDir)
runDocGenerator(actualOutputDir, tempDir, noSymlinks = true) runDocGenerator(helper.actualOutputDir, tempDir, noSymlinks = true)
val currentDirectory = actualOutputDir.resolve("com.package1/current") val currentDirectory = helper.actualOutputDir.resolve("com.package1/current")
val sourceDirectory = actualOutputDir.resolve("com.package1/1.2.3") val sourceDirectory = helper.actualOutputDir.resolve("com.package1/1.2.3")
assertThat(currentDirectory).isDirectory() assertThat(currentDirectory).isDirectory()
assertThat(currentDirectory.isSymbolicLink()).isFalse() assertThat(currentDirectory.isSymbolicLink()).isFalse()
@@ -0,0 +1,181 @@
/*
* Copyright © 2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.doc
import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.exists
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.fail
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.test.FileTestUtils
import org.pkl.commons.test.PackageServer
import org.pkl.commons.test.listFilesRecursively
import org.pkl.core.util.IoUtils
class DocGeneratorTestHelper {
internal val tempDir by lazy { Files.createTempDirectory("ExecutableCliDocGeneratorTest") }
internal val projectDir = FileTestUtils.rootProjectDir.resolve("pkl-doc")
internal val inputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/input").apply { assert(exists()) }
}
internal val docsiteModule: URI by lazy {
inputDir.resolve("docsite-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1PackageModule: URI by lazy {
inputDir.resolve("com.package1/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package2PackageModule: URI by lazy {
inputDir.resolve("com.package2/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1InputModules: List<URI> by lazy {
inputDir
.resolve("com.package1")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
internal val package2InputModules: List<URI> by lazy {
inputDir
.resolve("com.package2")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
internal val expectedOutputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/output").createDirectories()
}
internal val expectedOutputFiles: List<Path> by lazy { expectedOutputDir.listFilesRecursively() }
internal val actualOutputDir: Path by lazy {
tempDir.resolve("work/DocGeneratorTest").createDirectories()
}
internal val actualOutputFiles: List<Path> by lazy { actualOutputDir.listFilesRecursively() }
internal val cacheDir: Path by lazy { tempDir.resolve("cache") }
internal val sourceModules =
listOf(
docsiteModule,
package1PackageModule,
package2PackageModule,
URI("package://localhost:0/birds@0.5.0"),
URI("package://localhost:0/fruit@1.1.0"),
URI("package://localhost:0/unlisted@1.0.0"),
URI("package://localhost:0/deprecated@1.0.0"),
) + package1InputModules + package2InputModules
internal val expectedRelativeOutputFiles: List<String> by lazy {
expectedOutputFiles.map { path ->
IoUtils.toNormalizedPathString(expectedOutputDir.relativize(path)).let { str ->
// Git will by default clone symlinks as shortcuts on Windows, and shortcuts have a
// `.lnk` extension.
if (IoUtils.isWindows() && str.endsWith(".lnk")) str.dropLast(4) else str
}
}
}
internal val actualRelativeOutputFiles: List<String> by lazy {
actualOutputFiles.map { IoUtils.toNormalizedPathString(actualOutputDir.relativize(it)) }
}
fun runPklDocCli(executable: Path, options: CliDocGeneratorOptions) {
val command = buildList {
add(executable.toString())
add("--output-dir")
add(options.normalizedOutputDir.toString())
add("--cache-dir")
add(options.base.normalizedModuleCacheDir.toString())
add("--test-mode")
addAll(sourceModules.map { it.toString() })
}
val process =
with(ProcessBuilder(command)) {
redirectErrorStream(true)
start()
}
try {
val out = process.inputStream.reader().readText()
val exitCode = process.waitFor()
if (exitCode != 0) {
fail(
"""
Process exited with $exitCode.
Output:
"""
.trimIndent() + out
)
}
} finally {
process.destroy()
}
}
private fun generateDocsWith(doGenerate: (CliDocGeneratorOptions) -> Unit): List<String> {
PackageServer.populateCacheDir(cacheDir)
val options =
CliDocGeneratorOptions(
CliBaseOptions(
sourceModules =
listOf(
docsiteModule,
package1PackageModule,
package2PackageModule,
URI("package://localhost:0/birds@0.5.0"),
URI("package://localhost:0/fruit@1.1.0"),
URI("package://localhost:0/unlisted@1.0.0"),
URI("package://localhost:0/deprecated@1.0.0"),
) + package1InputModules + package2InputModules,
moduleCacheDir = cacheDir,
),
outputDir = actualOutputDir,
isTestMode = true,
noSymlinks = false,
)
doGenerate(options)
val missingFiles = expectedRelativeOutputFiles - actualRelativeOutputFiles.toSet()
if (missingFiles.isNotEmpty()) {
Assertions.fail<Unit>(
"The following expected files were not actually generated:\n" +
missingFiles.joinToString("\n")
)
}
return actualRelativeOutputFiles
}
fun generateDocsWithCli(executable: Path): List<String> {
return generateDocsWith { runPklDocCli(executable, it) }
}
fun generateDocs(): List<String> {
return generateDocsWith { CliDocGenerator(it).run() }
}
}
@@ -0,0 +1,73 @@
/*
* Copyright © 2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.doc
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.copyTo
import kotlin.io.path.createParentDirectories
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.isSymbolicLink
import kotlin.io.path.readBytes
import kotlin.io.path.readSymbolicLink
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.pkl.commons.readString
import org.pkl.commons.toPath
import org.pkl.core.util.IoUtils
object DocTestUtils {
private val binaryFileExtensions = setOf("woff2", "png", "svg")
fun testExpectedFile(expectedOutputDir: Path, actualOutputDir: Path, relativeFilePath: String) {
val actualFile = actualOutputDir.resolve(relativeFilePath)
assertThat(actualFile)
.withFailMessage("Test bug: $actualFile should exist but does not.")
.exists()
// symlinks on Git and Windows is rather finnicky; they create shortcuts by default unless
// a core Git option is set. Also, by default, symlinks require administrator privileges to run.
// We'll just test that the symlink got created but skip verifying that it points to the right
// place.
if (actualFile.isSymbolicLink() && IoUtils.isWindows()) return
val expectedFile = expectedOutputDir.resolve(relativeFilePath)
if (expectedFile.exists()) {
when {
expectedFile.isSymbolicLink() -> {
assertThat(actualFile).isSymbolicLink
assertThat(expectedFile.readSymbolicLink().toString().toPath())
.isEqualTo(actualFile.readSymbolicLink().toString().toPath())
}
expectedFile.extension in binaryFileExtensions ->
assertThat(actualFile.readBytes()).isEqualTo(expectedFile.readBytes())
else -> assertThat(actualFile.readString()).isEqualTo(expectedFile.readString())
}
} else {
expectedFile.createParentDirectories()
if (actualFile.isSymbolicLink()) {
// needs special handling because `copyTo` can't copy symlinks between file systems
val linkTarget = actualFile.readSymbolicLink()
assertThat(linkTarget).isRelative
Files.createSymbolicLink(expectedFile, linkTarget.toString().toPath())
} else {
actualFile.copyTo(expectedFile)
}
Assertions.fail("Created missing expected file `$relativeFilePath`.")
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright © 2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.doc
import org.junit.jupiter.api.condition.DisabledIfSystemProperty
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import org.pkl.commons.test.Executables
// need both annotations for this to work (see https://stackoverflow.com/a/63252081)
@EnabledIfSystemProperty(named = "org.pkl.doc.JavaExecutableTest", matches = "true")
@DisabledIfSystemProperty(named = "org.pkl.doc.JavaExecutableTest", matches = "(?!true)")
class JavaExecutableTest {
companion object {
val helper = DocGeneratorTestHelper()
@JvmStatic
private fun generateDocs(): List<String> =
helper.generateDocsWithCli(Executables.pkldoc.javaExecutable)
}
@ParameterizedTest()
@MethodSource("generateDocs")
fun test(relativePath: String) {
DocTestUtils.testExpectedFile(helper.expectedOutputDir, helper.actualOutputDir, relativePath)
}
}
@@ -0,0 +1,42 @@
/*
* Copyright © 2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.doc
import org.junit.jupiter.api.condition.DisabledIfSystemProperty
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import org.pkl.commons.test.Executables
// need both annotations for this to work (see https://stackoverflow.com/a/63252081)
@EnabledIfSystemProperty(named = "org.pkl.doc.NativeExecutableTest", matches = "true")
@DisabledIfSystemProperty(named = "org.pkl.doc.NativeExecutableTest", matches = "(?!true)")
class NativeExecutableTest {
companion object {
val helper = DocGeneratorTestHelper()
@JvmStatic
private fun generateDocs(): List<String> {
return helper.generateDocsWithCli(Executables.pkldoc.firstExistingNative)
}
}
@ParameterizedTest()
@MethodSource("generateDocs")
fun test(relativePath: String) {
DocTestUtils.testExpectedFile(helper.expectedOutputDir, helper.actualOutputDir, relativePath)
}
}
@@ -26,17 +26,17 @@ import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.readString import org.pkl.commons.readString
import org.pkl.doc.CliDocGeneratorTest.Companion.package1InputModules
import org.pkl.doc.CliDocGeneratorTest.Companion.package1PackageModule
class SearchTest { class SearchTest {
companion object { companion object {
private val tempFileSystem = lazy { Jimfs.newFileSystem(Configuration.unix()) } private val tempFileSystem = lazy { Jimfs.newFileSystem(Configuration.unix()) }
private val helper = DocGeneratorTestHelper()
private val jsContext = lazy { private val jsContext = lazy {
// reuse CliDocGeneratorTest's input files (src/test/files/DocGeneratorTest/input) // reuse CliDocGeneratorTest's input files (src/test/files/DocGeneratorTest/input)
val packageModule: URI = package1PackageModule val packageModule: URI = helper.package1PackageModule
val inputModules: List<URI> = package1InputModules val inputModules: List<URI> = helper.package1InputModules
val pkldocDir = tempFileSystem.value.rootDirectories.first() val pkldocDir = tempFileSystem.value.rootDirectories.first()
@@ -0,0 +1,93 @@
/*
* Copyright © 2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.doc
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.exists
import org.pkl.commons.test.FileTestUtils
import org.pkl.commons.test.listFilesRecursively
import org.pkl.core.util.IoUtils
class TestUtils {
val tempFileSystem: FileSystem by lazy { Jimfs.newFileSystem(Configuration.unix()) }
val tmpOutputDir by lazy { tempFileSystem.getPath("/work/output").apply { createDirectories() } }
val projectDir = FileTestUtils.rootProjectDir.resolve("pkl-doc")
val inputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/input").apply { assert(exists()) }
}
val docsiteModule: URI by lazy {
inputDir.resolve("docsite-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1PackageModule: URI by lazy {
inputDir.resolve("com.package1/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
val package2PackageModule: URI by lazy {
inputDir.resolve("com.package2/doc-package-info.pkl").apply { assert(exists()) }.toUri()
}
internal val package1InputModules: List<URI> by lazy {
inputDir
.resolve("com.package1")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
val package2InputModules: List<URI> by lazy {
inputDir
.resolve("com.package2")
.listFilesRecursively()
.filter { it.fileName.toString() != "doc-package-info.pkl" }
.map { it.toUri() }
}
val expectedOutputDir: Path by lazy {
projectDir.resolve("src/test/files/DocGeneratorTest/output").createDirectories()
}
val expectedOutputFiles: List<Path> by lazy { expectedOutputDir.listFilesRecursively() }
val actualOutputDir: Path by lazy { tempFileSystem.getPath("/work/DocGeneratorTest") }
val actualOutputFiles: List<Path> by lazy { actualOutputDir.listFilesRecursively() }
val expectedRelativeOutputFiles: List<String> by lazy {
expectedOutputFiles.map { path ->
IoUtils.toNormalizedPathString(expectedOutputDir.relativize(path)).let { str ->
// Git will by default clone symlinks as shortcuts on Windows, and shortcuts have a
// `.lnk` extension.
if (IoUtils.isWindows() && str.endsWith(".lnk")) str.dropLast(4) else str
}
}
}
val actualRelativeOutputFiles: List<String> by lazy {
actualOutputFiles.map { IoUtils.toNormalizedPathString(actualOutputDir.relativize(it)) }
}
val binaryFileExtensions = setOf("woff2", "png", "svg")
}
@@ -489,7 +489,7 @@ public class Lexer {
} }
private void lexQuotedIdentifier() { private void lexQuotedIdentifier() {
while (lookahead != '`' && lookahead != '\n' && lookahead != '\r') { while (lookahead != '`' && lookahead != '\n' && lookahead != '\r' && lookahead != EOF) {
nextChar(); nextChar();
} }
if (lookahead == '`') { if (lookahead == '`') {
@@ -705,6 +705,13 @@ public class Lexer {
} }
private ParserError unexpectedChar(char got, String didYouMean) { private ParserError unexpectedChar(char got, String didYouMean) {
if (got == EOF) {
return unexpectedChar("EOF", didYouMean);
}
return lexError("unexpectedCharacter", got, didYouMean);
}
private ParserError unexpectedChar(String got, String didYouMean) {
return lexError("unexpectedCharacter", got, didYouMean); return lexError("unexpectedCharacter", got, didYouMean);
} }
@@ -17,6 +17,7 @@ package org.pkl.parser
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class LexerTest { class LexerTest {
@@ -46,4 +47,10 @@ class LexerTest {
assertThat(Lexer.maybeQuoteIdentifier("this")).isEqualTo("`this`") assertThat(Lexer.maybeQuoteIdentifier("this")).isEqualTo("`this`")
assertThat(Lexer.maybeQuoteIdentifier("😀")).isEqualTo("`😀`") assertThat(Lexer.maybeQuoteIdentifier("😀")).isEqualTo("`😀`")
} }
@Test
fun lexSingleBacktick() {
val thrown = assertThrows<ParserError> { Lexer("`").next() }
assertThat(thrown).hasMessageContaining("Unexpected character `EOF`")
}
} }
@@ -221,7 +221,7 @@ internal class BinaryEvaluator(
override fun visitMapping(value: VmMapping) { override fun visitMapping(value: VmMapping) {
packer.packArrayHeader(2) packer.packArrayHeader(2)
packer.packInt(CODE_MAPPING.toInt()) packer.packInt(CODE_MAPPING.toInt())
packer.packMapHeader(value.entryCount) packer.packMapHeader(value.length.toInt())
value.iterateAlreadyForcedMemberValues { key, _, memberValue -> value.iterateAlreadyForcedMemberValues { key, _, memberValue ->
visit(key) visit(key)
visit(memberValue) visit(memberValue)
+6
View File
@@ -13,3 +13,9 @@ res4: Mapping = new {
["bar"] = 2 ["bar"] = 2
} }
} }
// https://github.com/apple/pkl/issues/1151
res5: Mapping = new {
local self = this
["foo"] = new Dynamic { name = "foo" }
["bar"] = new Dynamic { name = self["foo"].name + "bar" }
}
+25 -1
View File
@@ -41,4 +41,28 @@
: :
- 3 - 3
- -
bar: 2 bar: 2
-
- 16
- res5
-
- 3
-
foo:
- 1
- Dynamic
- pkl:base
-
-
- 16
- name
- foo
bar:
- 1
- Dynamic
- pkl:base
-
-
- 16
- name
- foobar
@@ -17,7 +17,7 @@ package org.pkl.server
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.pkl.commons.test.PklExecutablePaths import org.pkl.commons.test.Executables
import org.pkl.core.messaging.MessageTransports import org.pkl.core.messaging.MessageTransports
class NativeServerTest : AbstractServerTest() { class NativeServerTest : AbstractServerTest() {
@@ -26,7 +26,7 @@ class NativeServerTest : AbstractServerTest() {
@BeforeEach @BeforeEach
fun beforeEach() { fun beforeEach() {
val executable = PklExecutablePaths.firstExisting.toString() val executable = Executables.pkl.firstExistingNative.toString()
server = ProcessBuilder(executable, "server").start() server = ProcessBuilder(executable, "server").start()
client = client =
TestTransport( TestTransport(
+1 -1
View File
@@ -36,7 +36,7 @@
/// ///
/// Warning: Although this module is ready for initial use, /// Warning: Although this module is ready for initial use,
/// benchmark results may be inaccurate or inconsistent. /// benchmark results may be inaccurate or inconsistent.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.Benchmark module pkl.Benchmark
import "pkl:platform" as _platform import "pkl:platform" as _platform
+1 -1
View File
@@ -63,7 +63,7 @@
/// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" } /// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" }
/// amends "pkl:PackageInfo" /// amends "pkl:PackageInfo"
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.DocPackageInfo module pkl.DocPackageInfo
import "pkl:reflect" import "pkl:reflect"
+1 -1
View File
@@ -31,7 +31,7 @@
/// ///
/// title = "Title displayed in the header of each page" /// title = "Title displayed in the header of each page"
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.DocsiteInfo module pkl.DocsiteInfo
import "pkl:reflect" import "pkl:reflect"
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Common settings for Pkl's own evaluator. /// Common settings for Pkl's own evaluator.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
@Since { version = "0.26.0" } @Since { version = "0.26.0" }
module pkl.EvaluatorSettings module pkl.EvaluatorSettings
+1 -1
View File
@@ -64,7 +64,7 @@
/// value = project /// value = project
/// } /// }
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.Project module pkl.Project
import "pkl:EvaluatorSettings" as EvaluatorSettingsModule import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
+1 -1
View File
@@ -19,7 +19,7 @@
/// These tools differentiate from [pkl:reflect][reflect] in that they parse Pkl modules, but do not /// These tools differentiate from [pkl:reflect][reflect] in that they parse Pkl modules, but do not
/// execute any code within these modules. /// execute any code within these modules.
@Since { version = "0.27.0" } @Since { version = "0.27.0" }
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.analyze module pkl.analyze
// used by doc comments // used by doc comments
+1 -1
View File
@@ -17,7 +17,7 @@
/// Fundamental properties, methods, and classes for writing Pkl programs. /// Fundamental properties, methods, and classes for writing Pkl programs.
/// ///
/// Members of this module are automatically available in every Pkl module. /// Members of this module are automatically available in every Pkl module.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.base module pkl.base
import "pkl:jsonnet" import "pkl:jsonnet"
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A JSON parser. /// A JSON parser.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.json module pkl.json
/// A JSON parser. /// A JSON parser.
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A [Jsonnet](https://jsonnet.org) renderer. /// A [Jsonnet](https://jsonnet.org) renderer.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.jsonnet module pkl.jsonnet
/// Constructs an [ImportStr]. /// Constructs an [ImportStr].
+1 -1
View File
@@ -18,7 +18,7 @@
/// ///
/// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`, /// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`,
/// are directly defined in classes [Number], [Int], and [Float]. /// are directly defined in classes [Number], [Int], and [Float].
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.math module pkl.math
/// The minimum [Int] value: `-9223372036854775808`. /// The minimum [Int] value: `-9223372036854775808`.
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Information about the platform that the current program runs on. /// Information about the platform that the current program runs on.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.platform module pkl.platform
/// The platform that the current program runs on. /// The platform that the current program runs on.
+1 -1
View File
@@ -16,7 +16,7 @@
/// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers). /// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers).
/// Note: This module is _experimental_ and not ready for production use. /// Note: This module is _experimental_ and not ready for production use.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.protobuf module pkl.protobuf
import "pkl:reflect" import "pkl:reflect"
+1 -1
View File
@@ -26,7 +26,7 @@
/// - Documentation generators (such as *Pkldoc*) /// - Documentation generators (such as *Pkldoc*)
/// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*) /// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*)
/// - Domain-specific schema validators /// - Domain-specific schema validators
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.reflect module pkl.reflect
import "pkl:base" import "pkl:base"
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Information about the Pkl release that the current program runs on. /// Information about the Pkl release that the current program runs on.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.release module pkl.release
import "pkl:semver" import "pkl:semver"
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers. /// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.semver module pkl.semver
/// Tells whether [version] is a valid semantic version number. /// Tells whether [version] is a valid semantic version number.
+1 -1
View File
@@ -19,7 +19,7 @@
/// Every settings file must amend this module. /// Every settings file must amend this module.
/// Unless CLI commands and build tool plugins are explicitly configured with a settings file, /// Unless CLI commands and build tool plugins are explicitly configured with a settings file,
/// they will use `~/.pkl/settings.pkl` or the defaults specified in this module. /// they will use `~/.pkl/settings.pkl` or the defaults specified in this module.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.settings module pkl.settings
import "pkl:EvaluatorSettings" import "pkl:EvaluatorSettings"
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Utilities for generating shell scripts. /// Utilities for generating shell scripts.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.shell module pkl.shell
/// Escapes [str] by enclosing it in single quotes. /// Escapes [str] by enclosing it in single quotes.
+1 -1
View File
@@ -18,7 +18,7 @@
/// ///
/// To write tests, amend this module and define [facts] or [examples] (or both). /// To write tests, amend this module and define [facts] or [examples] (or both).
/// To run tests, evaluate the amended module. /// To run tests, evaluate the amended module.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
open module pkl.test open module pkl.test
/// Named groups of boolean expressions that are expected to evaluate to [true]. /// Named groups of boolean expressions that are expected to evaluate to [true].
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// An XML renderer. /// An XML renderer.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.xml module pkl.xml
/// Renders values as XML. /// Renders values as XML.
+1 -1
View File
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A YAML 1.2 compliant YAML parser. /// A YAML 1.2 compliant YAML parser.
@ModuleInfo { minPklVersion = "0.29.0" } @ModuleInfo { minPklVersion = "0.29.1" }
module pkl.yaml module pkl.yaml
/// A YAML parser. /// A YAML parser.