blob: a14435e82f84fb34158b2687793d4974aaaf1080 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package yaml
2
3import (
4 "encoding"
5 "fmt"
6 "io"
7 "reflect"
8 "regexp"
9 "sort"
10 "strconv"
11 "strings"
12 "time"
13 "unicode/utf8"
14)
15
16type encoder struct {
17 emitter yaml_emitter_t
18 event yaml_event_t
19 out []byte
20 flow bool
21 // doneInit holds whether the initial stream_start_event has been
22 // emitted.
23 doneInit bool
24}
25
26func newEncoder() *encoder {
27 e := &encoder{}
28 yaml_emitter_initialize(&e.emitter)
29 yaml_emitter_set_output_string(&e.emitter, &e.out)
30 yaml_emitter_set_unicode(&e.emitter, true)
31 return e
32}
33
34func newEncoderWithWriter(w io.Writer) *encoder {
35 e := &encoder{}
36 yaml_emitter_initialize(&e.emitter)
37 yaml_emitter_set_output_writer(&e.emitter, w)
38 yaml_emitter_set_unicode(&e.emitter, true)
39 return e
40}
41
42func (e *encoder) init() {
43 if e.doneInit {
44 return
45 }
46 yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
47 e.emit()
48 e.doneInit = true
49}
50
51func (e *encoder) finish() {
52 e.emitter.open_ended = false
53 yaml_stream_end_event_initialize(&e.event)
54 e.emit()
55}
56
57func (e *encoder) destroy() {
58 yaml_emitter_delete(&e.emitter)
59}
60
61func (e *encoder) emit() {
62 // This will internally delete the e.event value.
63 e.must(yaml_emitter_emit(&e.emitter, &e.event))
64}
65
66func (e *encoder) must(ok bool) {
67 if !ok {
68 msg := e.emitter.problem
69 if msg == "" {
70 msg = "unknown problem generating YAML content"
71 }
72 failf("%s", msg)
73 }
74}
75
76func (e *encoder) marshalDoc(tag string, in reflect.Value) {
77 e.init()
78 yaml_document_start_event_initialize(&e.event, nil, nil, true)
79 e.emit()
80 e.marshal(tag, in)
81 yaml_document_end_event_initialize(&e.event, true)
82 e.emit()
83}
84
85func (e *encoder) marshal(tag string, in reflect.Value) {
86 if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
87 e.nilv()
88 return
89 }
90 iface := in.Interface()
91 switch m := iface.(type) {
92 case time.Time, *time.Time:
93 // Although time.Time implements TextMarshaler,
94 // we don't want to treat it as a string for YAML
95 // purposes because YAML has special support for
96 // timestamps.
97 case Marshaler:
98 v, err := m.MarshalYAML()
99 if err != nil {
100 fail(err)
101 }
102 if v == nil {
103 e.nilv()
104 return
105 }
106 in = reflect.ValueOf(v)
107 case encoding.TextMarshaler:
108 text, err := m.MarshalText()
109 if err != nil {
110 fail(err)
111 }
112 in = reflect.ValueOf(string(text))
113 case nil:
114 e.nilv()
115 return
116 }
117 switch in.Kind() {
118 case reflect.Interface:
119 e.marshal(tag, in.Elem())
120 case reflect.Map:
121 e.mapv(tag, in)
122 case reflect.Ptr:
123 if in.Type() == ptrTimeType {
124 e.timev(tag, in.Elem())
125 } else {
126 e.marshal(tag, in.Elem())
127 }
128 case reflect.Struct:
129 if in.Type() == timeType {
130 e.timev(tag, in)
131 } else {
132 e.structv(tag, in)
133 }
134 case reflect.Slice, reflect.Array:
135 if in.Type().Elem() == mapItemType {
136 e.itemsv(tag, in)
137 } else {
138 e.slicev(tag, in)
139 }
140 case reflect.String:
141 e.stringv(tag, in)
142 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
143 if in.Type() == durationType {
144 e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
145 } else {
146 e.intv(tag, in)
147 }
148 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
149 e.uintv(tag, in)
150 case reflect.Float32, reflect.Float64:
151 e.floatv(tag, in)
152 case reflect.Bool:
153 e.boolv(tag, in)
154 default:
155 panic("cannot marshal type: " + in.Type().String())
156 }
157}
158
159func (e *encoder) mapv(tag string, in reflect.Value) {
160 e.mappingv(tag, func() {
161 keys := keyList(in.MapKeys())
162 sort.Sort(keys)
163 for _, k := range keys {
164 e.marshal("", k)
165 e.marshal("", in.MapIndex(k))
166 }
167 })
168}
169
170func (e *encoder) itemsv(tag string, in reflect.Value) {
171 e.mappingv(tag, func() {
172 slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
173 for _, item := range slice {
174 e.marshal("", reflect.ValueOf(item.Key))
175 e.marshal("", reflect.ValueOf(item.Value))
176 }
177 })
178}
179
180func (e *encoder) structv(tag string, in reflect.Value) {
181 sinfo, err := getStructInfo(in.Type())
182 if err != nil {
183 panic(err)
184 }
185 e.mappingv(tag, func() {
186 for _, info := range sinfo.FieldsList {
187 var value reflect.Value
188 if info.Inline == nil {
189 value = in.Field(info.Num)
190 } else {
191 value = in.FieldByIndex(info.Inline)
192 }
193 if info.OmitEmpty && isZero(value) {
194 continue
195 }
196 e.marshal("", reflect.ValueOf(info.Key))
197 e.flow = info.Flow
198 e.marshal("", value)
199 }
200 if sinfo.InlineMap >= 0 {
201 m := in.Field(sinfo.InlineMap)
202 if m.Len() > 0 {
203 e.flow = false
204 keys := keyList(m.MapKeys())
205 sort.Sort(keys)
206 for _, k := range keys {
207 if _, found := sinfo.FieldsMap[k.String()]; found {
208 panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
209 }
210 e.marshal("", k)
211 e.flow = false
212 e.marshal("", m.MapIndex(k))
213 }
214 }
215 }
216 })
217}
218
219func (e *encoder) mappingv(tag string, f func()) {
220 implicit := tag == ""
221 style := yaml_BLOCK_MAPPING_STYLE
222 if e.flow {
223 e.flow = false
224 style = yaml_FLOW_MAPPING_STYLE
225 }
226 yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
227 e.emit()
228 f()
229 yaml_mapping_end_event_initialize(&e.event)
230 e.emit()
231}
232
233func (e *encoder) slicev(tag string, in reflect.Value) {
234 implicit := tag == ""
235 style := yaml_BLOCK_SEQUENCE_STYLE
236 if e.flow {
237 e.flow = false
238 style = yaml_FLOW_SEQUENCE_STYLE
239 }
240 e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
241 e.emit()
242 n := in.Len()
243 for i := 0; i < n; i++ {
244 e.marshal("", in.Index(i))
245 }
246 e.must(yaml_sequence_end_event_initialize(&e.event))
247 e.emit()
248}
249
250// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
251//
252// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
253// in YAML 1.2 and by this package, but these should be marshalled quoted for
254// the time being for compatibility with other parsers.
255func isBase60Float(s string) (result bool) {
256 // Fast path.
257 if s == "" {
258 return false
259 }
260 c := s[0]
261 if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
262 return false
263 }
264 // Do the full match.
265 return base60float.MatchString(s)
266}
267
268// From http://yaml.org/type/float.html, except the regular expression there
269// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
270var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
271
272func (e *encoder) stringv(tag string, in reflect.Value) {
273 var style yaml_scalar_style_t
274 s := in.String()
275 canUsePlain := true
276 switch {
277 case !utf8.ValidString(s):
278 if tag == yaml_BINARY_TAG {
279 failf("explicitly tagged !!binary data must be base64-encoded")
280 }
281 if tag != "" {
282 failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
283 }
284 // It can't be encoded directly as YAML so use a binary tag
285 // and encode it as base64.
286 tag = yaml_BINARY_TAG
287 s = encodeBase64(s)
288 case tag == "":
289 // Check to see if it would resolve to a specific
290 // tag when encoded unquoted. If it doesn't,
291 // there's no need to quote it.
292 rtag, _ := resolve("", s)
293 canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
294 }
295 // Note: it's possible for user code to emit invalid YAML
296 // if they explicitly specify a tag and a string containing
297 // text that's incompatible with that tag.
298 switch {
299 case strings.Contains(s, "\n"):
300 style = yaml_LITERAL_SCALAR_STYLE
301 case canUsePlain:
302 style = yaml_PLAIN_SCALAR_STYLE
303 default:
304 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
305 }
306 e.emitScalar(s, "", tag, style)
307}
308
309func (e *encoder) boolv(tag string, in reflect.Value) {
310 var s string
311 if in.Bool() {
312 s = "true"
313 } else {
314 s = "false"
315 }
316 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
317}
318
319func (e *encoder) intv(tag string, in reflect.Value) {
320 s := strconv.FormatInt(in.Int(), 10)
321 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
322}
323
324func (e *encoder) uintv(tag string, in reflect.Value) {
325 s := strconv.FormatUint(in.Uint(), 10)
326 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
327}
328
329func (e *encoder) timev(tag string, in reflect.Value) {
330 t := in.Interface().(time.Time)
331 s := t.Format(time.RFC3339Nano)
332 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
333}
334
335func (e *encoder) floatv(tag string, in reflect.Value) {
336 // Issue #352: When formatting, use the precision of the underlying value
337 precision := 64
338 if in.Kind() == reflect.Float32 {
339 precision = 32
340 }
341
342 s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
343 switch s {
344 case "+Inf":
345 s = ".inf"
346 case "-Inf":
347 s = "-.inf"
348 case "NaN":
349 s = ".nan"
350 }
351 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
352}
353
354func (e *encoder) nilv() {
355 e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
356}
357
358func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
359 implicit := tag == ""
360 e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
361 e.emit()
362}