github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/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  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"strings"
    27  	"unicode"
    28  
    29  	"github.com/ghodss/yaml"
    30  	"github.com/golang/glog"
    31  )
    32  
    33  // ToJSON converts a single YAML document into a JSON document
    34  // or returns an error. If the document appears to be JSON the
    35  // YAML decoding path is not used (so that error messages are
    36  // JSON specific).
    37  func ToJSON(data []byte) ([]byte, error) {
    38  	if hasJSONPrefix(data) {
    39  		return data, nil
    40  	}
    41  	return yaml.YAMLToJSON(data)
    42  }
    43  
    44  // YAMLToJSONDecoder decodes YAML documents from an io.Reader by
    45  // separating individual documents. It first converts the YAML
    46  // body to JSON, then unmarshals the JSON.
    47  type YAMLToJSONDecoder struct {
    48  	scanner *bufio.Scanner
    49  }
    50  
    51  // NewYAMLToJSONDecoder decodes YAML documents from the provided
    52  // stream in chunks by converting each document (as defined by
    53  // the YAML spec) into its own chunk, converting it to JSON via
    54  // yaml.YAMLToJSON, and then passing it to json.Decoder.
    55  func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder {
    56  	scanner := bufio.NewScanner(r)
    57  	scanner.Split(splitYAMLDocument)
    58  	return &YAMLToJSONDecoder{
    59  		scanner: scanner,
    60  	}
    61  }
    62  
    63  // Decode reads a YAML document as JSON from the stream or returns
    64  // an error. The decoding rules match json.Unmarshal, not
    65  // yaml.Unmarshal.
    66  func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
    67  	if d.scanner.Scan() {
    68  		data, err := yaml.YAMLToJSON(d.scanner.Bytes())
    69  		if err != nil {
    70  			return err
    71  		}
    72  		return json.Unmarshal(data, into)
    73  	}
    74  	err := d.scanner.Err()
    75  	if err == nil {
    76  		err = io.EOF
    77  	}
    78  	return err
    79  }
    80  
    81  // YAMLDecoder reads chunks of objects and returns ErrShortBuffer if
    82  // the data is not sufficient.
    83  type YAMLDecoder struct {
    84  	r         io.ReadCloser
    85  	scanner   *bufio.Scanner
    86  	remaining []byte
    87  }
    88  
    89  // NewDocumentDecoder decodes YAML documents from the provided
    90  // stream in chunks by converting each document (as defined by
    91  // the YAML spec) into its own chunk. io.ErrShortBuffer will be
    92  // returned if the entire buffer could not be read to assist
    93  // the caller in framing the chunk.
    94  func NewDocumentDecoder(r io.ReadCloser) io.ReadCloser {
    95  	scanner := bufio.NewScanner(r)
    96  	scanner.Split(splitYAMLDocument)
    97  	return &YAMLDecoder{
    98  		r:       r,
    99  		scanner: scanner,
   100  	}
   101  }
   102  
   103  // Read reads the previous slice into the buffer, or attempts to read
   104  // the next chunk.
   105  // TODO: switch to readline approach.
   106  func (d *YAMLDecoder) Read(data []byte) (n int, err error) {
   107  	left := len(d.remaining)
   108  	if left == 0 {
   109  		// return the next chunk from the stream
   110  		if !d.scanner.Scan() {
   111  			err := d.scanner.Err()
   112  			if err == nil {
   113  				err = io.EOF
   114  			}
   115  			return 0, err
   116  		}
   117  		out := d.scanner.Bytes()
   118  		d.remaining = out
   119  		left = len(out)
   120  	}
   121  
   122  	// fits within data
   123  	if left <= len(data) {
   124  		copy(data, d.remaining)
   125  		d.remaining = nil
   126  		return len(d.remaining), nil
   127  	}
   128  
   129  	// caller will need to reread
   130  	copy(data, d.remaining[:left])
   131  	d.remaining = d.remaining[left:]
   132  	return len(data), io.ErrShortBuffer
   133  }
   134  
   135  func (d *YAMLDecoder) Close() error {
   136  	return d.r.Close()
   137  }
   138  
   139  const yamlSeparator = "\n---"
   140  
   141  // splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents.
   142  func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {
   143  	if atEOF && len(data) == 0 {
   144  		return 0, nil, nil
   145  	}
   146  	sep := len([]byte(yamlSeparator))
   147  	if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {
   148  		// We have a potential document terminator
   149  		i += sep
   150  		after := data[i:]
   151  		if len(after) == 0 {
   152  			// we can't read any more characters
   153  			if atEOF {
   154  				return len(data), data[:len(data)-sep], nil
   155  			}
   156  			return 0, nil, nil
   157  		}
   158  		if j := bytes.IndexByte(after, '\n'); j >= 0 {
   159  			return i + j + 1, data[0 : i-sep], nil
   160  		}
   161  		return 0, nil, nil
   162  	}
   163  	// If we're at EOF, we have a final, non-terminated line. Return it.
   164  	if atEOF {
   165  		return len(data), data, nil
   166  	}
   167  	// Request more data.
   168  	return 0, nil, nil
   169  }
   170  
   171  // decoder is a convenience interface for Decode.
   172  type decoder interface {
   173  	Decode(into interface{}) error
   174  }
   175  
   176  // YAMLOrJSONDecoder attempts to decode a stream of JSON documents or
   177  // YAML documents by sniffing for a leading { character.
   178  type YAMLOrJSONDecoder struct {
   179  	r          io.Reader
   180  	bufferSize int
   181  
   182  	decoder decoder
   183  }
   184  
   185  // NewYAMLOrJSONDecoder returns a decoder that will process YAML documents
   186  // or JSON documents from the given reader as a stream. bufferSize determines
   187  // how far into the stream the decoder will look to figure out whether this
   188  // is a JSON stream (has whitespace followed by an open brace).
   189  func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder {
   190  	return &YAMLOrJSONDecoder{
   191  		r:          r,
   192  		bufferSize: bufferSize,
   193  	}
   194  }
   195  
   196  // Decode unmarshals the next object from the underlying stream into the
   197  // provide object, or returns an error.
   198  func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
   199  	if d.decoder == nil {
   200  		buffer, isJSON := GuessJSONStream(d.r, d.bufferSize)
   201  		if isJSON {
   202  			glog.V(4).Infof("decoding stream as JSON")
   203  			d.decoder = json.NewDecoder(buffer)
   204  		} else {
   205  			glog.V(4).Infof("decoding stream as YAML")
   206  			d.decoder = NewYAMLToJSONDecoder(buffer)
   207  		}
   208  	}
   209  	err := d.decoder.Decode(into)
   210  	if jsonDecoder, ok := d.decoder.(*json.Decoder); ok {
   211  		if syntax, ok := err.(*json.SyntaxError); ok {
   212  			data, readErr := ioutil.ReadAll(jsonDecoder.Buffered())
   213  			if readErr != nil {
   214  				glog.V(4).Infof("reading stream failed: %v", readErr)
   215  			}
   216  			js := string(data)
   217  			start := strings.LastIndex(js[:syntax.Offset], "\n") + 1
   218  			line := strings.Count(js[:start], "\n")
   219  			return fmt.Errorf("json: line %d: %s", line, syntax.Error())
   220  		}
   221  	}
   222  	return err
   223  }
   224  
   225  // GuessJSONStream scans the provided reader up to size, looking
   226  // for an open brace indicating this is JSON. It will return the
   227  // bufio.Reader it creates for the consumer.
   228  func GuessJSONStream(r io.Reader, size int) (io.Reader, bool) {
   229  	buffer := bufio.NewReaderSize(r, size)
   230  	b, _ := buffer.Peek(size)
   231  	return buffer, hasJSONPrefix(b)
   232  }
   233  
   234  var jsonPrefix = []byte("{")
   235  
   236  // hasJSONPrefix returns true if the provided buffer appears to start with
   237  // a JSON open brace.
   238  func hasJSONPrefix(buf []byte) bool {
   239  	return hasPrefix(buf, jsonPrefix)
   240  }
   241  
   242  // Return true if the first non-whitespace bytes in buf is
   243  // prefix.
   244  func hasPrefix(buf []byte, prefix []byte) bool {
   245  	trim := bytes.TrimLeftFunc(buf, unicode.IsSpace)
   246  	return bytes.HasPrefix(trim, prefix)
   247  }