this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

encoding/protobuf/textproto: add encoder

Doesn't really copy positioning and all comments, but
the textproto API is sort of hit or miss anyway whether
this information is supported at all.
So good enough for now.

Issue #5

Change-Id: Ia0d09a0c4b92756f68c2a09a114311b363fef33a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9370
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>

+496 -1
+7 -1
encoding/protobuf/pbinternal/attribute.go
··· 16 16 17 17 import ( 18 18 "strings" 19 + "unicode" 20 + "unicode/utf8" 19 21 20 22 "cuelang.org/go/cue" 21 23 ) ··· 49 51 CompositeType CompositeType 50 52 ValueType ValueType 51 53 Type string 54 + 55 + IsEnum bool 52 56 53 57 // For maps only 54 58 KeyType ValueType // only for maps ··· 97 101 98 102 case cue.StructKind: 99 103 if strings.HasPrefix(info.Type, "map[") { 100 - a := strings.SplitN(info.Type[len("map["):], ",", 2) 104 + a := strings.SplitN(info.Type[len("map["):], "]", 2) 101 105 info.KeyTypeString = strings.TrimSpace(a[0]) 102 106 switch info.KeyTypeString { 103 107 case "string": ··· 133 137 134 138 case cue.IntKind: 135 139 info.ValueType = Int 140 + r, _ := utf8.DecodeRuneInString(info.Type) 141 + info.IsEnum = unicode.In(r, unicode.Upper) 136 142 137 143 case cue.FloatKind, cue.NumberKind: 138 144 info.ValueType = Float
+44
encoding/protobuf/pbinternal/symbol.go
··· 64 64 65 65 return false 66 66 } 67 + 68 + // MatchByInt finds a symbol for a given enum value and sets it in x. 69 + func MatchByInt(v cue.Value, val int64) string { 70 + if op, a := v.Expr(); op == cue.AndOp { 71 + for _, v := range a { 72 + if s := MatchByInt(v, val); s != "" { 73 + return s 74 + } 75 + } 76 + } 77 + v = cue.Dereference(v) 78 + return matchByInt(v, val) 79 + } 80 + 81 + func matchByInt(v cue.Value, val int64) string { 82 + switch op, a := v.Expr(); op { 83 + case cue.OrOp, cue.AndOp: 84 + for _, v := range a { 85 + if s := matchByInt(v, val); s != "" { 86 + return s 87 + } 88 + } 89 + 90 + default: 91 + if i, err := v.Int64(); err != nil || i != val { 92 + break 93 + } 94 + 95 + _, path := v.ReferencePath() 96 + a := path.Selectors() 97 + if len(a) == 0 { 98 + break 99 + } 100 + 101 + sel := a[len(a)-1] 102 + if !sel.IsDefinition() { 103 + break 104 + } 105 + 106 + return sel.String()[1:] 107 + } 108 + 109 + return "" 110 + }
+203
encoding/protobuf/textproto/encoder.go
··· 1 + // Copyright 2021 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package textproto 16 + 17 + import ( 18 + "fmt" 19 + "strings" 20 + 21 + "cuelang.org/go/cue" 22 + "cuelang.org/go/cue/errors" 23 + "cuelang.org/go/encoding/protobuf/pbinternal" 24 + 25 + "github.com/protocolbuffers/txtpbfmt/ast" 26 + pbast "github.com/protocolbuffers/txtpbfmt/ast" 27 + "github.com/protocolbuffers/txtpbfmt/parser" 28 + ) 29 + 30 + // Encoder marshals CUE into text proto. 31 + // 32 + type Encoder struct { 33 + // Schema 34 + } 35 + 36 + // NewEncoder returns a new encoder, where the given options are default 37 + // options. 38 + func NewEncoder(options ...Option) *Encoder { 39 + return &Encoder{} 40 + } 41 + 42 + // Encode converts a CUE value to a text proto file. 43 + // 44 + // Fields do not need to have a @protobuf attribute except for in the following 45 + // cases: 46 + // 47 + // - it is explicitly required that only fields with an attribute are exported 48 + // - a struct represents a Protobuf map 49 + // - custom naming 50 + // 51 + func (e *Encoder) Encode(v cue.Value, options ...Option) ([]byte, error) { 52 + n := &pbast.Node{} 53 + enc := &encoder{} 54 + 55 + enc.encodeMsg(n, v) 56 + 57 + if enc.errs != nil { 58 + return nil, enc.errs 59 + } 60 + 61 + // Pretty printing does not do errors, and returns a string (why o why?). 62 + s := parser.Pretty(n.Children, 0) 63 + return []byte(s), nil 64 + } 65 + 66 + type encoder struct { 67 + errs errors.Error 68 + } 69 + 70 + func (e *encoder) addErr(err error) { 71 + e.errs = errors.Append(e.errs, errors.Promote(err, "textproto")) 72 + } 73 + 74 + func (e *encoder) encodeMsg(parent *pbast.Node, v cue.Value) { 75 + i, err := v.Fields() 76 + if err != nil { 77 + e.addErr(err) 78 + return 79 + } 80 + for i.Next() { 81 + v := i.Value() 82 + if !v.IsConcrete() { 83 + continue 84 + } 85 + 86 + info, err := pbinternal.FromIter(i) 87 + if err != nil { 88 + e.addErr(err) 89 + } 90 + 91 + switch info.CompositeType { 92 + case pbinternal.List: 93 + elems, err := v.List() 94 + if err != nil { 95 + e.addErr(err) 96 + return 97 + } 98 + for first := true; elems.Next(); first = false { 99 + n := &pbast.Node{Name: info.Name} 100 + if first { 101 + copyMeta(n, v) 102 + } 103 + elem := elems.Value() 104 + copyMeta(n, elem) 105 + parent.Children = append(parent.Children, n) 106 + e.encodeValue(n, elem) 107 + } 108 + 109 + case pbinternal.Map: 110 + i, err := v.Fields() 111 + if err != nil { 112 + e.addErr(err) 113 + return 114 + } 115 + for first := true; i.Next(); first = false { 116 + n := &pbast.Node{Name: info.Name} 117 + if first { 118 + copyMeta(n, v) 119 + } 120 + parent.Children = append(parent.Children, n) 121 + var key *pbast.Node 122 + switch info.KeyType { 123 + case pbinternal.String, pbinternal.Bytes: 124 + key = pbast.StringNode("key", i.Label()) 125 + default: 126 + key = &pbast.Node{ 127 + Name: "key", 128 + Values: []*ast.Value{{Value: i.Label()}}, 129 + } 130 + } 131 + n.Children = append(n.Children, key) 132 + 133 + value := &pbast.Node{Name: "value"} 134 + e.encodeValue(value, i.Value()) 135 + n.Children = append(n.Children, value) 136 + } 137 + 138 + default: 139 + n := &pbast.Node{Name: info.Name} 140 + copyMeta(n, v) 141 + e.encodeValue(n, v) 142 + // Don't add if there are no values or children. 143 + parent.Children = append(parent.Children, n) 144 + } 145 + } 146 + } 147 + 148 + // copyMeta copies metadata from nodes to values. 149 + // 150 + // TODO: also copy positions. The textproto API is rather messy and complex, 151 + // though, and so far it seems to be quite buggy too. Not sure if it is worth 152 + // the effort. 153 + func copyMeta(x *pbast.Node, v cue.Value) { 154 + for _, doc := range v.Doc() { 155 + s := strings.TrimRight(doc.Text(), "\n") 156 + for _, c := range strings.Split(s, "\n") { 157 + x.PreComments = append(x.PreComments, "# "+c) 158 + } 159 + } 160 + } 161 + 162 + func (e *encoder) encodeValue(n *pbast.Node, v cue.Value) { 163 + var value string 164 + switch v.Kind() { 165 + case cue.StructKind: 166 + e.encodeMsg(n, v) 167 + 168 + case cue.StringKind: 169 + s, err := v.String() 170 + if err != nil { 171 + e.addErr(err) 172 + } 173 + sn := pbast.StringNode("foo", s) 174 + n.Values = append(n.Values, sn.Values...) 175 + 176 + case cue.BytesKind: 177 + b, err := v.Bytes() 178 + if err != nil { 179 + e.addErr(err) 180 + } 181 + sn := pbast.StringNode("foo", string(b)) 182 + n.Values = append(n.Values, sn.Values...) 183 + 184 + case cue.BoolKind: 185 + value = fmt.Sprint(v) 186 + n.Values = append(n.Values, &pbast.Value{Value: value}) 187 + 188 + case cue.IntKind, cue.FloatKind, cue.NumberKind: 189 + d, _ := v.Decimal() 190 + value := d.String() 191 + 192 + if info, _ := pbinternal.FromValue("", v); !info.IsEnum { 193 + } else if i, err := v.Int64(); err != nil { 194 + } else if s := pbinternal.MatchByInt(v, i); s != "" { 195 + value = s 196 + } 197 + 198 + n.Values = append(n.Values, &pbast.Value{Value: value}) 199 + 200 + default: 201 + e.addErr(errors.Newf(v.Pos(), "textproto: unknown type %v", v.Kind())) 202 + } 203 + }
+73
encoding/protobuf/textproto/encoder_test.go
··· 1 + // Copyright 2021 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package textproto_test 16 + 17 + import ( 18 + "strings" 19 + "testing" 20 + 21 + "cuelang.org/go/cue" 22 + "cuelang.org/go/cue/errors" 23 + "cuelang.org/go/encoding/protobuf/textproto" 24 + "cuelang.org/go/internal/cuetest" 25 + "cuelang.org/go/internal/cuetxtar" 26 + ) 27 + 28 + func TestEncode(t *testing.T) { 29 + test := cuetxtar.TxTarTest{ 30 + Root: "./testdata/encoder", 31 + Name: "encode", 32 + Update: cuetest.UpdateGoldenFiles, 33 + } 34 + 35 + r := cue.Runtime{} 36 + 37 + test.Run(t, func(t *cuetxtar.Test) { 38 + // TODO: use high-level API. 39 + 40 + var schema, value cue.Value 41 + 42 + for _, f := range t.Archive.Files { 43 + switch { 44 + case strings.HasSuffix(f.Name, ".cue"): 45 + inst, err := r.Compile(f.Name, f.Data) 46 + if err != nil { 47 + t.WriteErrors(errors.Promote(err, "test")) 48 + return 49 + } 50 + switch f.Name { 51 + case "schema.cue": 52 + schema = inst.Value() 53 + case "value.cue": 54 + value = inst.Value() 55 + } 56 + } 57 + } 58 + 59 + v := schema.Unify(value) 60 + if err := v.Err(); err != nil { 61 + t.WriteErrors(errors.Promote(err, "test")) 62 + return 63 + } 64 + 65 + b, err := textproto.NewEncoder().Encode(v) 66 + if err != nil { 67 + t.WriteErrors(errors.Promote(err, "test")) 68 + return 69 + } 70 + _, _ = t.Write(b) 71 + 72 + }) 73 + }
+76
encoding/protobuf/textproto/testdata/encoder/enum.txtar
··· 1 + -- schema.cue -- 2 + enum: [string]: 3 + { "foo", #enumValue: 1 } | 4 + { "bar", #enumValue: 2 } @protobuf(1,Enum) 5 + 6 + defEnum: [string]: #FOO | #BAR @protobuf(2,Enum) 7 + 8 + #FOO: 1 9 + #BAR: 2 10 + 11 + 12 + typeEnum: [string]: #Enum @protobuf(3,Enum) 13 + 14 + #Enum: #FOO | #BAR | 3 15 + 16 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 17 + // symbolEnum: [string]: 18 + // { 1, @symbol(foo) } | 19 + // { 2, @symbol(bar) } 20 + 21 + 22 + singleEnum: #single @protobuf(3,Enum) 23 + 24 + #single: 1 25 + 26 + badEnum: { string, #enumValue: 1 } | { "two", #enumValue: 2 } 27 + 28 + 29 + -- value.cue -- 30 + enum: asIs: "foo" 31 + 32 + // Convert integers to strings 33 + defEnum: foo: 1 34 + defEnum: bar: 2 35 + 36 + typeEnum: foo: 1 37 + typeEnum: bar: 2 38 + typeEnum: baz: 3 39 + 40 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 41 + // symbolEnum: foo: "foo" 42 + // symbolEnum: bar: "bar" 43 + // symbolEnum: baz: "baz" 44 + 45 + singleEnum: 1 46 + 47 + -- out/jsonpb -- 48 + enum: asIs: "foo" 49 + enum: asIsUnknown: "foobar" 50 + 51 + // Convert integers to strings 52 + defEnum: foo: "foo" 53 + defEnum: bar: "bar" 54 + defEnum: baz: 3 55 + 56 + // TODO: consider supporting @symbol(foo) or @json(,symbol=foo) 57 + // symbolEnum: foo: "foo" 58 + // symbolEnum: bar: "bar" 59 + // symbolEnum: baz: "baz" 60 + 61 + singleEnum: "single" 62 + -- out/encode -- 63 + enum: { 64 + asIs: "foo" 65 + } 66 + defEnum: { 67 + # Convert integers to strings 68 + foo: FOO 69 + bar: BAR 70 + } 71 + typeEnum: { 72 + foo: FOO 73 + bar: BAR 74 + baz: 3 75 + } 76 + singleEnum: single
+24
encoding/protobuf/textproto/testdata/encoder/list.txtar
··· 1 + -- value.cue -- 2 + // List comment 3 + intList: [ 1, 2, 3 ] 4 + 5 + structList: [{ 6 + foo: 1 7 + bar: 2 8 + }, { 9 + foo: 3 10 + bar: 4 11 + }] 12 + -- out/encode -- 13 + # List comment 14 + intList: 1 15 + intList: 2 16 + intList: 3 17 + structList: { 18 + foo: 1 19 + bar: 2 20 + } 21 + structList: { 22 + foo: 3 23 + bar: 4 24 + }
+44
encoding/protobuf/textproto/testdata/encoder/map.txtar
··· 1 + -- value.cue -- 2 + m: _ @protobuf(1,map[string]string) 3 + m: foo: "bar" 4 + m: qux: "quux" 5 + m: "1": "one" 6 + 7 + // Doc 1 8 + intMap: _ @protobuf(1,map[int]string) 9 + // Doc 2 10 + intMap: { "1": "one" } 11 + // Doc 3 (seems not to be supported by proto) 12 + intMap: "2": "two" 13 + intMap: { 14 + // Doc inner (seems not to be supported by proto) 15 + "3": "three" 16 + } 17 + 18 + -- out/encode -- 19 + m: { 20 + key: "foo" 21 + value: "bar" 22 + } 23 + m: { 24 + key: "qux" 25 + value: "quux" 26 + } 27 + m: { 28 + key: "1" 29 + value: "one" 30 + } 31 + # Doc 1 32 + # Doc 2 33 + intMap: { 34 + key: 1 35 + value: "one" 36 + } 37 + intMap: { 38 + key: 2 39 + value: "two" 40 + } 41 + intMap: { 42 + key: 3 43 + value: "three" 44 + }
+23
encoding/protobuf/textproto/testdata/encoder/simple.txtar
··· 1 + -- value.cue -- 2 + a: 1 3 + b: 2 4 + c: 3.4 5 + 6 + d: "foo\u1234" 7 + e: '\000' 8 + 9 + f: false 10 + // Doc comment 11 + t: true 12 + 13 + notConcrete: string 14 + 15 + -- out/encode -- 16 + a: 1 17 + b: 2 18 + c: 3.4 19 + d: "fooሴ" 20 + e: "\x00" 21 + f: false 22 + # Doc comment 23 + t: true
+2
go.sum
··· 45 45 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 46 46 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 47 47 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 48 + github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 49 + github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 48 50 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 49 51 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 50 52 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=