Skip to content

Instantly share code, notes, and snippets.

@jmgilman
Created August 10, 2024 01:30
Show Gist options
  • Save jmgilman/330ae229d08d857a4ffed5f365740812 to your computer and use it in GitHub Desktop.
Save jmgilman/330ae229d08d857a4ffed5f365740812 to your computer and use it in GitHub Desktop.
Updating and deleting values in CUE
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
ctx := cuecontext.New()
contents, err := os.ReadFile("test.cue")
if err != nil {
fmt.Printf("failed to read file: %v\n", err)
os.Exit(1)
}
ov, err := compileCUE(ctx, contents)
if err != nil {
fmt.Printf("failed to compile CUE: %v\n", err)
os.Exit(1)
}
fmt.Println("Delete root.foo.a")
v, err := delete(ctx, ov, "root.foo.a")
if err != nil {
fmt.Printf("failed to delete path: %v\n", err)
os.Exit(1)
}
fmt.Printf("%+v\n\n", v)
fmt.Println("Delete root.bar[0].a[0]")
v, err = delete(ctx, ov, "root.bar[0].a[0]")
if err != nil {
fmt.Printf("failed to delete path: %v\n", err)
os.Exit(1)
}
fmt.Printf("%+v\n\n", v)
fmt.Println("Replace root.foo.b with 30")
v, err = replace(ctx, ov, "root.foo.b", ctx.CompileString("30"))
if err != nil {
fmt.Printf("failed to replace field: %v\n", err)
os.Exit(1)
}
fmt.Printf("%+v\n\n", v)
fmt.Println("Replace root.bar[1].a with [0, 1, 2]")
v, err = replace(ctx, ov, "root.bar[1].a", ctx.CompileString("[0, 1, 2]"))
if err != nil {
fmt.Printf("failed to replace field: %v\n", err)
os.Exit(1)
}
fmt.Printf("%+v\n\n", v)
}
// compileCUE compiles the given CUE contents and returns the resulting value.
// If the contents are invalid, an error is returned.
func compileCUE(ctx *cue.Context, contents []byte) (cue.Value, error) {
v := ctx.CompileBytes(contents)
if v.Err() != nil {
return cue.Value{}, v.Err()
}
return v, nil
}
// delete deletes the field at the given path from the given value.
// The path must point to either a struct field or a list index.
func delete(ctx *cue.Context, v cue.Value, path string) (cue.Value, error) {
// final holds the final value after the delete operation
var final cue.Value
refPath := cue.ParsePath(path)
refSels := refPath.Selectors()
// Make sure the target path exists
if !v.LookupPath(refPath).Exists() {
return v, fmt.Errorf("path %q does not exist", path)
}
// Isolate the last selector in the target path, which is the field to delete
deletedSel, parentSels := refSels[len(refSels)-1], refSels[:len(refSels)-1]
parentPath := cue.MakePath(parentSels...) // Path to the parent of the field to delete
var err error
final, err = deleteFrom(ctx, v.LookupPath(parentPath), deletedSel)
if err != nil {
return v, fmt.Errorf("failed to delete field: %v", err)
}
// Replace the parent struct in the given value with the new struct that has the target field removed
final, err = replace(ctx, v, parentPath.String(), final)
if err != nil {
return v, fmt.Errorf("failed to rebuild struct: %v", err)
}
return final, nil
}
// deleteFrom deletes the field at the given selector from the given value.
// The value must be a struct or a list.
func deleteFrom(ctx *cue.Context, v cue.Value, targetSel cue.Selector) (cue.Value, error) {
switch targetSel.Type() {
case cue.IndexLabel:
new := ctx.CompileString("[...]")
list, err := v.List()
if err != nil {
return v, fmt.Errorf("expected list: %v", err)
}
var i int
for list.Next() {
if list.Selector() == targetSel {
continue
}
new = new.FillPath(cue.MakePath(cue.Index(i)), list.Value())
i++
}
return new, nil
case cue.StringLabel:
new := ctx.CompileString("{}")
fields, err := v.Fields()
if err != nil {
return v, fmt.Errorf("expected struct: %v", err)
}
for fields.Next() {
if fields.Selector() == targetSel {
continue
}
new = new.FillPath(cue.MakePath(fields.Selector()), fields.Value())
}
return new, nil
default:
return v, fmt.Errorf("unsupported selector type %s", targetSel.Type().String())
}
}
// replace replaces the value at the given path with the given value.
// The path must point to either a struct field or a list index.
func replace(ctx *cue.Context, v cue.Value, path string, replace cue.Value) (cue.Value, error) {
cpath := cue.ParsePath(path)
if !v.LookupPath(cpath).Exists() {
return v, fmt.Errorf("path %q does not exist", path)
}
final := replace
sels := cpath.Selectors()
for len(sels) > 0 {
var lastSel cue.Selector
curIndex := len(sels) - 1
lastSel, sels = sels[curIndex], sels[:curIndex]
switch lastSel.Type() {
case cue.IndexLabel:
new := ctx.CompileString("[...]")
curList, err := v.LookupPath(cue.MakePath(sels...)).List()
if err != nil {
return cue.Value{}, fmt.Errorf("expected list at path %s: %v", path, err)
}
for i := 0; curList.Next(); i++ {
var val cue.Value
if curList.Selector() == lastSel {
val = final
} else {
val = curList.Value()
}
new = new.FillPath(cue.MakePath(cue.Index(i)), val)
}
final = new
case cue.StringLabel:
new := ctx.CompileString("{}")
curFields, err := v.LookupPath(cue.MakePath(sels...)).Fields()
if err != nil {
return cue.Value{}, fmt.Errorf("expected struct at path %s: %v", path, err)
}
for curFields.Next() {
fieldPath := cue.MakePath(curFields.Selector())
if curFields.Selector() == lastSel {
new = new.FillPath(fieldPath, final)
} else {
new = new.FillPath(fieldPath, curFields.Value())
}
}
final = new
default:
return cue.Value{}, fmt.Errorf("unknown selector type %s", lastSel.Type())
}
}
return final, nil
}
Delete root.foo.a
{
version: "1.0"
root: {
foo: {
b: 3
}
bar: [{
a: [0, 1, 2]
b: 2
}, {
a: 3
b: 4
}]
}
}
Delete root.bar[0].a[0]
{
version: "1.0"
root: {
foo: {
a: true
b: 3
}
bar: [{
a: [1, 2]
b: 2
}, {
a: 3
b: 4
}]
}
}
Replace root.foo.b with 30
{
version: "1.0"
root: {
foo: {
a: true
b: 30
}
bar: [{
a: [0, 1, 2]
b: 2
}, {
a: 3
b: 4
}]
}
}
Replace root.bar[1].a with [0, 1, 2]
{
version: "1.0"
root: {
foo: {
a: true
b: 3
}
bar: [{
a: [0, 1, 2]
b: 2
}, {
a: [0, 1, 2]
b: 4
}]
}
}
version: "1.0"
root: {
foo: {
a: true
b: 3
}
bar: [
{
a: [0, 1, 2]
b: 2
},
{
a: 3
b: 4
},
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment