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  }