遍历
有几种方式遍历 Value。例如基于路径的查找,迭代、以及遍历语法树。 其中一些时可配置的,与不同的字段标签有关,例如常规的、隐藏的、可选的,以及定义。
这一节我们会使用下面这个 Value 作为例子
value.cue
obj: {
sub: {
foo: "bar"
n: int
}
list: [1, 2, {a: 3}]
any: _
#def: val: string
_hid: 42
opt?: bool
labels: [string]: string
}
Selectors 和 MakePath
我们上一节解释了如何使用 LookupPath
with ParsePath
迭代。
我们可以用 Selectors
和 MakePath
编程构建路径。
也可以用这些来反向找出Value的标签(也就是最后一级字段名)
selectors.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
// printing the label vs path
sub := val.LookupPath(cue.ParsePath("obj.sub"))
fmt.Println(sub.Path(), getLabel(sub))
}
// helper function for getting the label for a value
func getLabel(val cue.Value) cue.Selector {
ss := val.Path().Selectors()
s := ss[len(ss)-1]
return s
}
go run selectors.go
遍历列表
list.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
// lookup a list value
val = val.LookupPath(cue.ParsePath("obj.list"))
// we use iterators to traverse a list
// List() returns an iterator
iter, _ := val.List()
// This pattern is standard iteration
// We get the current element and nil at the end
for iter.Next() {
fmt.Println(iter.Value())
}
}
go run list.go
1
2
{
a: 3
}
文档: List
遍历字段
fields.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
val = val.LookupPath(cue.ParsePath("obj"))
// without options
fmt.Println("Without\n---------")
printFields(val.Fields())
// default options
fmt.Println("Default\n---------")
printFields(val.Fields(defaultOptions...))
// custom options
fmt.Println("Custom\n---------")
printFields(val.Fields(customOptions...))
}
func printFields(iter *cue.Iterator, err error) {
for iter.Next() {
fmt.Printf("%v: %v\n", iter.Selector(), iter.Value())
}
fmt.Println()
}
// Cue's default
var defaultOptions = []cue.Option{
cue.Attributes(true),
cue.Concrete(false),
cue.Definitions(false),
cue.DisallowCycles(false),
cue.Docs(false),
cue.Hidden(false),
cue.Optional(false),
cue.ResolveReferences(false),
// The following are not set
// nor do they have a bool arg
// cue.Final(),
// cue.Raw(),
// cue.Schema(),
}
// Our custom options
var customOptions = []cue.Option{
cue.Definitions(true),
cue.Hidden(true),
cue.Optional(true),
}
go run fields.go
Without
---------
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
Default
---------
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
Custom
---------
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
#def: {
val: string
}
_hid: 42
opt: bool
labels: {}
在类型上使用 Switch 语句
你也许想根据值的类型进行一些选择性操作。
可以在 val.IncompleteKind()
上使用 switch 语句。
switch.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
val = val.LookupPath(cue.ParsePath("obj"))
fmt.Println("obj:")
iter, _ := val.Fields()
for iter.Next() {
printNodeType(iter.Value())
}
fmt.Println("\nsub:")
val = val.LookupPath(cue.ParsePath("sub"))
iter, _ = val.Fields()
for iter.Next() {
printNodeType(iter.Value())
}
}
func printNodeType(val cue.Value) {
switch val.IncompleteKind() {
case cue.StructKind:
fmt.Println("struct")
case cue.ListKind:
fmt.Println("list")
default:
printLeafType(val)
}
}
func printLeafType(val cue.Value) {
fmt.Println(val.IncompleteKind())
}
go run switch.go
obj:
struct
list
_
struct
sub:
string
int
在一个 Value 上 Walk
walk.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
// before (pre-order) traversal
preprinter := func(v cue.Value) bool {
fmt.Printf("%v\n", v)
return true
}
// after (post-order) traversal
cnt := 0
postcounter := func(v cue.Value) {
cnt++
}
// walk the value
val.Walk(preprinter, postcounter)
// print count
fmt.Println("\n\nCount:", cnt)
}
go run walk.go
{
obj: {
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
}
}
{
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
}
{
foo: "bar"
n: int
}
"bar"
int
[1, 2, {
a: 3
}]
1
2
{
a: 3
}
3
_
{}
Count: 12
文档: Walk
自定义遍历
之前的例子演示了默认的遍历,其中一些字段没有被遍历到。
这是因为 CUE 的默认 Walk()
方法使用了默认的 Field()
。
为了遍历所有字段,我们需要自己定义 walk 函数,从而我们可以给 Field()
传入自定义的选项。
custom.go
package main
import (
"fmt"
"os"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
const input = `
a: {
i: int
j: int | *i
}
`
func main() {
c := cuecontext.New()
d, _ := os.ReadFile("value.cue")
val := c.CompileBytes(d)
// before (pre-order) traversal
preprinter := func(v cue.Value) bool {
fmt.Printf("%v\n", v)
return true
}
// after (post-order) traversal
cnt := 0
postcounter := func(v cue.Value) {
cnt++
}
Walk(val, preprinter, postcounter, customOptions...)
}
// Walk is an alternative to cue.Value.Walk which handles more field types
// You can customize this with your own options
func Walk(v cue.Value, before func(cue.Value) bool, after func(cue.Value), options ...cue.Option) {
// call before and possibly stop recursion
if before != nil && !before(v) {
return
}
// possibly recurse
switch v.IncompleteKind() {
case cue.StructKind:
if options == nil {
options = defaultOptions
}
s, _ := v.Fields(options...)
for s.Next() {
Walk(s.Value(), before, after, options...)
}
case cue.ListKind:
l, _ := v.List()
for l.Next() {
Walk(l.Value(), before, after, options...)
}
// no default (basic lit types)
}
if after != nil {
after(v)
}
}
// Cue's default
var defaultOptions = []cue.Option{
cue.Attributes(true),
cue.Concrete(false),
cue.Definitions(false),
cue.DisallowCycles(false),
cue.Docs(false),
cue.Hidden(false),
cue.Optional(false),
cue.ResolveReferences(false),
// The following are not set
// nor do they have a bool arg
// cue.Final(),
// cue.Raw(),
// cue.Schema(),
}
// Our custom options
var customOptions = []cue.Option{
cue.Definitions(true),
cue.Hidden(true),
cue.Optional(true),
}
go run custom.go
{
obj: {
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
}
}
{
sub: {
foo: "bar"
n: int
}
list: [1, 2, {
a: 3
}]
any: _
labels: {}
}
{
foo: "bar"
n: int
}
"bar"
int
[1, 2, {
a: 3
}]
1
2
{
a: 3
}
3
_
{
val: string
}
string
42
bool
{}