遍历



有几种方式遍历 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 迭代。 我们可以用 SelectorsMakePath 编程构建路径。 也可以用这些来反向找出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

文档: MakePath, Selectors

遍历列表

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: {}

文档: Fields, Options

在类型上使用 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
{}
我们绝不会将你的邮箱分享给任何人。
2022 Hofstadter, Inc