Created
August 10, 2024 01:30
-
-
Save jmgilman/330ae229d08d857a4ffed5f365740812 to your computer and use it in GitHub Desktop.
Updating and deleting values in CUE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}] | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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