Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
cue: add function for Value.Attributes()
Browse files Browse the repository at this point in the history
First attempt by @verdverm, significant edits by @mpvl.

The API has been designed so that new attribute types can
be added without breaking anything: the user will always
have to explicitly specify the types of attributes that
are needed.

This does not handle duplicate attributes. In the future,
there could be merge functionality for this purpose. This
could be done by adding option flags if we can settle on
what constitutes good options.

Original comments from Tony:

This enables a user of the Go API to get all attributes so they
can inspect them, rather than only being able to ask if a particular
attribute exists on a value. Really useful for tools leveraging CUE!

I recall @mpvl might have wanted the return to be a Value or List
rather than the slice?

Closes #529
#529

GitOrigin-RevId: 6a8951a
Change-Id: Ie3cc9983656864fa44852b924a3142c82e336f4a
  • Loading branch information
verdverm authored and mpvl committed Mar 29, 2021
1 parent 525613a commit 71383a0
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 4 deletions.
93 changes: 90 additions & 3 deletions cue/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2163,25 +2163,112 @@ func (v Value) Walk(before func(Value) bool, after func(Value)) {
func (v Value) Attribute(key string) Attribute {
// look up the attributes
if v.v == nil {
return Attribute{internal.NewNonExisting(key)}
return nonExistAttr(key)
}
// look up the attributes
for _, a := range export.ExtractFieldAttrs(v.v.Conjuncts) {
k, body := a.Split()
k, _ := a.Split()
if key != k {
continue
}
return Attribute{internal.ParseAttrBody(token.NoPos, body)}
return newAttr(internal.FieldAttr, a)
}

return Attribute{internal.NewNonExisting(key)}
}

func newAttr(k internal.AttrKind, a *ast.Attribute) Attribute {
key, body := a.Split()
x := internal.ParseAttrBody(token.NoPos, body)
x.Name = key
x.Kind = k
return Attribute{x}
}

func nonExistAttr(key string) Attribute {
a := internal.NewNonExisting(key)
a.Name = key
a.Kind = internal.FieldAttr
return Attribute{a}
}

// Attributes reports all field attributes for the Value.
func (v Value) Attributes(flags AttrFlag) []Attribute {
if v.v == nil {
return nil
}

attrs := []Attribute{}

if flags&FieldAttr != 0 {
for _, a := range export.ExtractFieldAttrs(v.v.Conjuncts) {
attrs = append(attrs, newAttr(internal.FieldAttr, a))
}
}

if flags&DeclAttr != 0 {
for _, a := range export.ExtractDeclAttrs(v.v.Conjuncts) {
attrs = append(attrs, newAttr(internal.DeclAttr, a))
}
}

return attrs
}

// AttrFlag indicates the location of an attribute within CUE source.
type AttrFlag int

const (
// FieldAttr indicates a field attribute.
// foo: bar @attr()
FieldAttr AttrFlag = AttrFlag(internal.FieldAttr)

// DeclAttr indicates a declaration position.
// foo: {
// @attr()
// }
DeclAttr AttrFlag = AttrFlag(internal.DeclAttr)

// A ValueAttr is a bit mask to request any attribute that is locally
// associated with a field, instead of, for instance, an entire file.
ValueAttr AttrFlag = FieldAttr | DeclAttr

// TODO: Possible future attr kinds
// ElemAttr (is a ValueAttr)
// FileAttr (not a ValueAttr)

// TODO: Merge: merge namesake attributes.
)

// An Attribute contains meta data about a field.
type Attribute struct {
attr internal.Attr
}

// Format implements fmt.Formatter.
func (a Attribute) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "@%s(%s)", a.attr.Name, a.attr.Body)
}

var _ fmt.Formatter = &Attribute{}

// Name returns the name of the attribute, for instance, "json" for @json(...).
func (a *Attribute) Name() string {
return a.attr.Name
}

// Contents reports the full contents of an attribute within parentheses, so
// contents in @attr(contents).
func (a *Attribute) Contents() string {
return a.attr.Body
}

// Kind reports the type of location within CUE source where the attribute
// was specified.
func (a *Attribute) Kind() AttrFlag {
return AttrFlag(a.attr.Kind)
}

// Err returns the error associated with this Attribute or nil if this
// attribute is valid.
func (a *Attribute) Err() error {
Expand Down
51 changes: 51 additions & 0 deletions cue/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,57 @@ func cmpError(a, b error) bool {
return a.Error() == b.Error()
}

func TestAttributes(t *testing.T) {
const config = `
a: {
a: 0 @foo(a,b,c=1)
b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
}
b: {
@embed(foo)
3
} @field(foo)
`

testCases := []struct {
flags AttrFlag
path string
out string
}{{
flags: FieldAttr,
path: "a.a",
out: "[@foo(a,b,c=1)]",
}, {
flags: FieldAttr,
path: "a.b",
out: "[@bar(a,b,c,d=1) @foo(a,,d=1)]",
}, {
flags: DeclAttr,
path: "b",
out: "[@embed(foo)]",
}, {
flags: FieldAttr,
path: "b",
out: "[@field(foo)]",
}, {
flags: ValueAttr,
path: "b",
out: "[@field(foo) @embed(foo)]",
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
v := getInstance(t, config).Value().LookupPath(ParsePath(tc.path))
a := v.Attributes(tc.flags)
got := fmt.Sprint(a)
if got != tc.out {
t.Errorf("got %v; want %v", got, tc.out)
}

})
}
}

func TestAttributeErr(t *testing.T) {
const config = `
a: {
Expand Down
32 changes: 31 additions & 1 deletion internal/attrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,31 @@ import (
"cuelang.org/go/cue/token"
)

// AttrKind indicates the location of an attribute within CUE source.
type AttrKind uint8

const (
// FieldAttr indicates an attribute is a field attribute.
// foo: bar @attr()
FieldAttr AttrKind = 1 << iota

// DeclAttr indicates an attribute was specified at a declaration position.
// foo: {
// @attr()
// }
DeclAttr

// TODO: Possible future attr kinds
// ElemAttr
// FileAttr
// ValueAttr = FieldAttr|DeclAttr|ElemAttr
)

// Attr holds positional information for a single Attr.
type Attr struct {
Name string // e.g. "json" or "protobuf"
Body string
Kind AttrKind
Fields []keyValue
Err error
}
Expand All @@ -44,8 +66,16 @@ type keyValue struct {
}

func (kv *keyValue) Text() string { return kv.data }
func (kv *keyValue) Key() string { return kv.data[:kv.equal] }
func (kv *keyValue) Key() string {
if kv.equal == 0 {
return kv.data
}
return kv.data[:kv.equal]
}
func (kv *keyValue) Value() string {
if kv.equal == 0 {
return ""
}
return strings.TrimSpace(kv.data[kv.equal+1:])
}

Expand Down

0 comments on commit 71383a0

Please sign in to comment.