SPICE-0024: Annotation converters (#1333)

This enables defining declarative key and/or value transformations in
cases where neither `Class`- nor path-based converters can be applied
gracefully. It is also the only way to express transforming the
resulting property names in `Typed` objects without applying a converter
to the entire containing type, which is cumbersome at best.

SPICE: https://github.com/apple/pkl-evolution/pull/26
This commit is contained in:
Jen Basch
2026-01-23 12:44:41 -08:00
committed by GitHub
parent ed0cad668f
commit 73264e8fd1
51 changed files with 773 additions and 141 deletions
@@ -0,0 +1,19 @@
class LineComment extends ConvertProperty {
text: String
valuePrefix: String = ""
prefix: String
suffix: String = ""
render = (prop, renderer) ->
Pair(prop.key, new RenderDirective {
text =
List(
valuePrefix,
(renderer as ValueRenderer).renderValue(prop.value),
prefix,
outer.text,
suffix,
).join("")
})
}
@@ -0,0 +1,63 @@
open class Prefix extends ConvertProperty {
prefix: String
render = (prop, _) -> Pair(prefix + prop.key, prop.value)
}
class CamelCase extends ConvertProperty {
render = (prop, _) ->
Pair(
prop.key.replaceAllMapped(Regex("[^A-Za-z0-9]+([A-Za-z0-9])"), (match) ->
match.groups[1].value.toUpperCase()
),
prop.value,
)
}
class MultiplyValue extends ConvertProperty {
factor: Number
render = (prop, _) ->
Pair(prop.key, prop.value as Number * factor)
}
class SubtractValue extends ConvertProperty {
difference: Number
render = (prop, _) ->
Pair(prop.key, prop.value as Number - difference)
}
open class Foo {
no_converter: Int = 1
@Prefix { prefix = "foo_" }
prefixed_with_foo: Int = 2
@Prefix { prefix = "foo_" }
base_class_first: Int = 3
@MultiplyValue { factor = 2 }
transform_value: Int = 4
@SubtractValue { difference = 2 }
@MultiplyValue { factor = 3 }
@ConvertProperty {
render = (prop, _) -> Pair(prop.key, prop.value as Number + 4)
}
@MultiplyValue { factor = 3 }
@SubtractValue { difference = 11 }
in_order: Int = 5
}
class Bar extends Foo {
@CamelCase
base_class_first: Int = 3
}
output {
value = new Bar {}
renderer = new PcfRenderer {
convertPropertyTransformers {
[Prefix] { prefix = "foo_" }
}
}
}
@@ -0,0 +1,27 @@
import "pkl:json"
import ".../input-helper/api/annotationConverter.pkl"
@json.Property { name = "FOO" }
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new JsonRenderer {
convertPropertyTransformers {
// NB: this renders https://json5.org format which is a superset of JSON that supports commas.
[annotationConverter.LineComment] { prefix = ", // " }
}
}
}
@@ -0,0 +1,26 @@
import "pkl:jsonnet"
import ".../input-helper/api/annotationConverter.pkl"
@jsonnet.Property { name = "FOO" }
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new jsonnet.Renderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = ", // " }
}
}
}
@@ -0,0 +1,23 @@
import ".../input-helper/api/annotationConverter.pkl"
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new PListRenderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = "<!-- "; suffix = " -->" }
}
}
}
@@ -0,0 +1,23 @@
import ".../input-helper/api/annotationConverter.pkl"
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new PcfRenderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = " // "; valuePrefix = "= " }
}
}
}
@@ -0,0 +1,23 @@
import ".../input-helper/api/annotationConverter.pkl"
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new PropertiesRenderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = "\n# " }
}
}
}
@@ -0,0 +1,28 @@
import "pkl:protobuf"
import ".../input-helper/api/annotationConverter.pkl"
class Comment extends Annotation
@protobuf.Property { name = "FOO" }
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new protobuf.Renderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = " # " }
}
}
}
@@ -0,0 +1,26 @@
import "pkl:xml"
import ".../input-helper/api/annotationConverter.pkl"
@xml.Property { name = "FOO" }
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
}
output {
renderer = new xml.Renderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = "<!-- "; suffix = " -->" }
}
}
}
@@ -0,0 +1,41 @@
import "pkl:yaml"
import ".../input-helper/api/annotationConverter.pkl"
class Tag extends ConvertProperty {
tag: String(startsWith("!"))
render = (prop, renderer) ->
if (renderer is YamlRenderer)
Pair(prop.key, new RenderDirective {
text = " \(tag) \(renderer.renderValue(prop.value))"
})
else
prop
}
@yaml.Property { name = "FOO" }
foo: String = "a"
@annotationConverter.LineComment { text = "bar" }
bar: String = "b"
baz: Nested
class Nested {
@annotationConverter.LineComment { text = "qux" }
qux: String = "c"
quux: String = "c"
@Tag { tag = "!!foo" }
quuux: String = "d"
}
output {
renderer = new YamlRenderer {
convertPropertyTransformers {
[annotationConverter.LineComment] { prefix = " # "; valuePrefix = " " }
}
}
}
@@ -0,0 +1,5 @@
no_converter = 1
foo_prefixed_with_foo = 2
fooBaseClassFirst = 3
transform_value = 8
in_order = 28
@@ -0,0 +1,8 @@
{
"FOO": "a",
"bar": "b", // bar,
"baz": {
"qux": "c", // qux,
"quux": "c"
}
}
@@ -0,0 +1,8 @@
{
FOO: 'a',
bar: 'b', // bar,
baz: {
qux: 'c', // qux,
quux: 'c',
},
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>foo</key>
<string>a</string>
<key>bar</key>
<string>b</string><!-- bar -->
<key>baz</key>
<dict>
<key>qux</key>
<string>c</string><!-- qux -->
<key>quux</key>
<string>c</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,6 @@
foo = "a"
bar = "b" // bar
baz {
qux = "c" // qux
quux = "c"
}
@@ -172,6 +172,12 @@
-
- 3
- {}
-
- 16
- 'convertPropertyTransformers'
-
- 3
- {}
-
- 16
- 'extension'
@@ -0,0 +1,6 @@
foo = a
bar = b
# bar
baz.qux = c
# qux
baz.quux = c
@@ -0,0 +1,6 @@
FOO: "a"
bar: "b" # bar
baz: {
qux: "c" # qux
quux: "c"
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<FOO>a</FOO>
<bar>b<!-- bar --></bar>
<baz>
<qux>c<!-- qux --></qux>
<quux>c</quux>
</baz>
</root>
@@ -0,0 +1,6 @@
FOO: a
bar: b # bar
baz:
qux: c # qux
quux: c
quuux: !!foo d