github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/util/yaml/decoder.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors All rights reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package yaml 18 19 import ( 20 "bufio" 21 "bytes" 22 "encoding/json" 23 "io" 24 "unicode" 25 26 "github.com/ghodss/yaml" 27 "github.com/golang/glog" 28 ) 29 30 // ToJSON converts a single YAML document into a JSON document 31 // or returns an error. If the document appears to be JSON the 32 // YAML decoding path is not used (so that error messages are) 33 // JSON specific. 34 func ToJSON(data []byte) ([]byte, error) { 35 if hasJSONPrefix(data) { 36 return data, nil 37 } 38 return yaml.YAMLToJSON(data) 39 } 40 41 // YAMLToJSONDecoder decodes YAML documents from an io.Reader by 42 // separating individual documents. It first converts the YAML 43 // body to JSON, then unmarshals the JSON. 44 type YAMLToJSONDecoder struct { 45 scanner *bufio.Scanner 46 } 47 48 // NewYAMLToJSONDecoder decodes YAML documents from the provided 49 // stream in chunks by converting each document (as defined by 50 // the YAML spec) into its own chunk, converting it to JSON via 51 // yaml.YAMLToJSON, and then passing it to json.Decoder. 52 func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder { 53 scanner := bufio.NewScanner(r) 54 scanner.Split(splitYAMLDocument) 55 return &YAMLToJSONDecoder{ 56 scanner: scanner, 57 } 58 } 59 60 // Decode reads a YAML document as JSON from the stream or returns 61 // an error. The decoding rules match json.Unmarshal, not 62 // yaml.Unmarshal. 63 func (d *YAMLToJSONDecoder) Decode(into interface{}) error { 64 if d.scanner.Scan() { 65 data, err := yaml.YAMLToJSON(d.scanner.Bytes()) 66 if err != nil { 67 return err 68 } 69 return json.Unmarshal(data, into) 70 } 71 err := d.scanner.Err() 72 if err == nil { 73 err = io.EOF 74 } 75 return err 76 } 77 78 const yamlSeparator = "\n---" 79 80 // splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents. 81 func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) { 82 if atEOF && len(data) == 0 { 83 return 0, nil, nil 84 } 85 sep := len([]byte(yamlSeparator)) 86 if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 { 87 // We have a potential document terminator 88 i += sep 89 after := data[i:] 90 if len(after) == 0 { 91 // we can't read any more characters 92 if atEOF { 93 return len(data), data[:len(data)-sep], nil 94 } 95 return 0, nil, nil 96 } 97 if j := bytes.IndexByte(after, '\n'); j >= 0 { 98 return i + j + 1, data[0 : i-sep], nil 99 } 100 return 0, nil, nil 101 } 102 // If we're at EOF, we have a final, non-terminated line. Return it. 103 if atEOF { 104 return len(data), data, nil 105 } 106 // Request more data. 107 return 0, nil, nil 108 } 109 110 // decoder is a convenience interface for Decode. 111 type decoder interface { 112 Decode(into interface{}) error 113 } 114 115 // YAMLOrJSONDecoder attempts to decode a stream of JSON documents or 116 // YAML documents by sniffing for a leading { character. 117 type YAMLOrJSONDecoder struct { 118 r io.Reader 119 bufferSize int 120 121 decoder decoder 122 } 123 124 // NewYAMLOrJSONDecoder returns a decoder that will process YAML documents 125 // or JSON documents from the given reader as a stream. bufferSize determines 126 // how far into the stream the decoder will look to figure out whether this 127 // is a JSON stream (has whitespace followed by an open brace). 128 func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder { 129 return &YAMLOrJSONDecoder{ 130 r: r, 131 bufferSize: bufferSize, 132 } 133 } 134 135 // Decode unmarshals the next object from the underlying stream into the 136 // provide object, or returns an error. 137 func (d *YAMLOrJSONDecoder) Decode(into interface{}) error { 138 if d.decoder == nil { 139 buffer, isJSON := guessJSONStream(d.r, d.bufferSize) 140 if isJSON { 141 glog.V(4).Infof("decoding stream as JSON") 142 d.decoder = json.NewDecoder(buffer) 143 } else { 144 glog.V(4).Infof("decoding stream as YAML") 145 d.decoder = NewYAMLToJSONDecoder(buffer) 146 } 147 } 148 return d.decoder.Decode(into) 149 } 150 151 // guessJSONStream scans the provided reader up to size, looking 152 // for an open brace indicating this is JSON. It will return the 153 // bufio.Reader it creates for the consumer. 154 func guessJSONStream(r io.Reader, size int) (io.Reader, bool) { 155 buffer := bufio.NewReaderSize(r, size) 156 b, _ := buffer.Peek(size) 157 return buffer, hasJSONPrefix(b) 158 } 159 160 var jsonPrefix = []byte("{") 161 162 // hasJSONPrefix returns true if the provided buffer appears to start with 163 // a JSON open brace. 164 func hasJSONPrefix(buf []byte) bool { 165 return hasPrefix(buf, jsonPrefix) 166 } 167 168 // Return true if the first non-whitespace bytes in buf is 169 // prefix. 170 func hasPrefix(buf []byte, prefix []byte) bool { 171 trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) 172 return bytes.HasPrefix(trim, prefix) 173 }