1package 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}
363