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