1
2
3
4
5
6
7
8
9
10
11
12
13
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 pbast "github.com/protocolbuffers/txtpbfmt/ast"
26 "github.com/protocolbuffers/txtpbfmt/parser"
27 )
28
29
30 type Encoder struct {
31
32 }
33
34
35
36 func NewEncoder(options ...Option) *Encoder {
37 return &Encoder{}
38 }
39
40
41
42
43
44
45
46
47
48 func (e *Encoder) Encode(v cue.Value, options ...Option) ([]byte, error) {
49 n := &pbast.Node{}
50 enc := &encoder{}
51
52 enc.encodeMsg(n, v)
53
54 if enc.errs != nil {
55 return nil, enc.errs
56 }
57
58
59 s := parser.Pretty(n.Children, 0)
60 return []byte(s), nil
61 }
62
63 type encoder struct {
64 errs errors.Error
65 }
66
67 func (e *encoder) addErr(err error) {
68 e.errs = errors.Append(e.errs, errors.Promote(err, "textproto"))
69 }
70
71 func (e *encoder) encodeMsg(parent *pbast.Node, v cue.Value) {
72 i, err := v.Fields()
73 if err != nil {
74 e.addErr(err)
75 return
76 }
77 for i.Next() {
78 v := i.Value()
79 if !v.IsConcrete() {
80 continue
81 }
82
83 info, err := pbinternal.FromIter(i)
84 if err != nil {
85 e.addErr(err)
86 }
87
88 switch info.CompositeType {
89 case pbinternal.List:
90 elems, err := v.List()
91 if err != nil {
92 e.addErr(err)
93 return
94 }
95 for first := true; elems.Next(); first = false {
96 n := &pbast.Node{Name: info.Name}
97 if first {
98 copyMeta(n, v)
99 }
100 elem := elems.Value()
101 copyMeta(n, elem)
102 parent.Children = append(parent.Children, n)
103 e.encodeValue(n, elem)
104 }
105
106 case pbinternal.Map:
107 i, err := v.Fields()
108 if err != nil {
109 e.addErr(err)
110 return
111 }
112 for first := true; i.Next(); first = false {
113 n := &pbast.Node{Name: info.Name}
114 if first {
115 copyMeta(n, v)
116 }
117 parent.Children = append(parent.Children, n)
118 var key *pbast.Node
119 switch info.KeyType {
120 case pbinternal.String, pbinternal.Bytes:
121 key = pbast.StringNode("key", i.Label())
122 default:
123 key = &pbast.Node{
124 Name: "key",
125 Values: []*pbast.Value{{Value: i.Label()}},
126 }
127 }
128 n.Children = append(n.Children, key)
129
130 value := &pbast.Node{Name: "value"}
131 e.encodeValue(value, i.Value())
132 n.Children = append(n.Children, value)
133 }
134
135 default:
136 n := &pbast.Node{Name: info.Name}
137 copyMeta(n, v)
138 e.encodeValue(n, v)
139
140 parent.Children = append(parent.Children, n)
141 }
142 }
143 }
144
145
146
147
148
149
150 func copyMeta(x *pbast.Node, v cue.Value) {
151 for _, doc := range v.Doc() {
152 s := strings.TrimRight(doc.Text(), "\n")
153 for _, c := range strings.Split(s, "\n") {
154 x.PreComments = append(x.PreComments, "# "+c)
155 }
156 }
157 }
158
159 func (e *encoder) encodeValue(n *pbast.Node, v cue.Value) {
160 var value string
161 switch v.Kind() {
162 case cue.StructKind:
163 e.encodeMsg(n, v)
164
165 case cue.StringKind:
166 s, err := v.String()
167 if err != nil {
168 e.addErr(err)
169 }
170 sn := pbast.StringNode("foo", s)
171 n.Values = append(n.Values, sn.Values...)
172
173 case cue.BytesKind:
174 b, err := v.Bytes()
175 if err != nil {
176 e.addErr(err)
177 }
178 sn := pbast.StringNode("foo", string(b))
179 n.Values = append(n.Values, sn.Values...)
180
181 case cue.BoolKind:
182 value = fmt.Sprint(v)
183 n.Values = append(n.Values, &pbast.Value{Value: value})
184
185 case cue.IntKind, cue.FloatKind, cue.NumberKind:
186 d, _ := v.Decimal()
187 value := d.String()
188
189 if info, _ := pbinternal.FromValue("", v); !info.IsEnum {
190 } else if i, err := v.Int64(); err != nil {
191 } else if s := pbinternal.MatchByInt(v, i); s != "" {
192 value = s
193 }
194
195 n.Values = append(n.Values, &pbast.Value{Value: value})
196
197 default:
198 e.addErr(errors.Newf(v.Pos(), "textproto: unknown type %v", v.Kind()))
199 }
200 }
201
View as plain text