clair/vendor/github.com/kylelemons/go-gypsy/yaml/parser.go
2016-02-24 16:34:54 -05:00

375 lines
6.8 KiB
Go

// Copyright 2013 Google, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
)
// Parse returns a root-level Node parsed from the lines read from r. In
// general, this will be done for you by one of the File constructors.
func Parse(r io.Reader) (node Node, err error) {
lb := &lineBuffer{
Reader: bufio.NewReader(r),
}
defer func() {
if r := recover(); r != nil {
switch r := r.(type) {
case error:
err = r
case string:
err = errors.New(r)
default:
err = fmt.Errorf("%v", r)
}
}
}()
node = parseNode(lb, 0, nil)
return
}
// Supporting types and constants
const (
typUnknown = iota
typSequence
typMapping
typScalar
)
var typNames = []string{
"Unknown", "Sequence", "Mapping", "Scalar",
}
type lineReader interface {
Next(minIndent int) *indentedLine
}
type indentedLine struct {
lineno int
indent int
line []byte
}
func (line *indentedLine) String() string {
return fmt.Sprintf("%2d: %s%s", line.indent,
strings.Repeat(" ", 0*line.indent), string(line.line))
}
func parseNode(r lineReader, ind int, initial Node) (node Node) {
first := true
node = initial
// read lines
for {
line := r.Next(ind)
if line == nil {
break
}
if len(line.line) == 0 {
continue
}
if first {
ind = line.indent
first = false
}
types := []int{}
pieces := []string{}
var inlineValue func([]byte)
inlineValue = func(partial []byte) {
// TODO(kevlar): This can be a for loop now
vtyp, brk := getType(partial)
begin, end := partial[:brk], partial[brk:]
if vtyp == typMapping {
end = end[1:]
}
end = bytes.TrimLeft(end, " ")
switch vtyp {
case typScalar:
types = append(types, typScalar)
pieces = append(pieces, string(end))
return
case typMapping:
types = append(types, typMapping)
pieces = append(pieces, strings.TrimSpace(string(begin)))
trimmed := bytes.TrimSpace(end)
if len(trimmed) == 1 && trimmed[0] == '|' {
text := ""
for {
l := r.Next(1)
if l == nil {
break
}
s := string(l.line)
s = strings.TrimSpace(s)
if len(s) == 0 {
break
}
text = text + "\n" + s
}
types = append(types, typScalar)
pieces = append(pieces, string(text))
return
}
inlineValue(end)
case typSequence:
types = append(types, typSequence)
pieces = append(pieces, "-")
inlineValue(end)
}
}
inlineValue(line.line)
var prev Node
// Nest inlines
for len(types) > 0 {
last := len(types) - 1
typ, piece := types[last], pieces[last]
var current Node
if last == 0 {
current = node
}
//child := parseNode(r, line.indent+1, typUnknown) // TODO allow scalar only
// Add to current node
switch typ {
case typScalar: // last will be == nil
if _, ok := current.(Scalar); current != nil && !ok {
panic("cannot append scalar to non-scalar node")
}
if current != nil {
current = Scalar(piece) + " " + current.(Scalar)
break
}
current = Scalar(piece)
case typMapping:
var mapNode Map
var ok bool
var child Node
// Get the current map, if there is one
if mapNode, ok = current.(Map); current != nil && !ok {
_ = current.(Map) // panic
} else if current == nil {
mapNode = make(Map)
}
if _, inlineMap := prev.(Scalar); inlineMap && last > 0 {
current = Map{
piece: prev,
}
break
}
child = parseNode(r, line.indent+1, prev)
mapNode[piece] = child
current = mapNode
case typSequence:
var listNode List
var ok bool
var child Node
// Get the current list, if there is one
if listNode, ok = current.(List); current != nil && !ok {
_ = current.(List) // panic
} else if current == nil {
listNode = make(List, 0)
}
if _, inlineList := prev.(Scalar); inlineList && last > 0 {
current = List{
prev,
}
break
}
child = parseNode(r, line.indent+1, prev)
listNode = append(listNode, child)
current = listNode
}
if last < 0 {
last = 0
}
types = types[:last]
pieces = pieces[:last]
prev = current
}
node = prev
}
return
}
func getType(line []byte) (typ, split int) {
if len(line) == 0 {
return
}
if line[0] == '-' {
typ = typSequence
split = 1
return
}
typ = typScalar
if line[0] == ' ' || line[0] == '"' {
return
}
// the first character is real
// need to iterate past the first word
// things like "foo:" and "foo :" are mappings
// everything else is a scalar
idx := bytes.IndexAny(line, " \":")
if idx < 0 {
return
}
if line[idx] == '"' {
return
}
if line[idx] == ':' {
typ = typMapping
split = idx
} else if line[idx] == ' ' {
// we have a space
// need to see if its all spaces until a :
for i := idx; i < len(line); i++ {
switch ch := line[i]; ch {
case ' ':
continue
case ':':
// only split on colons followed by a space
if i+1 < len(line) && line[i+1] != ' ' {
continue
}
typ = typMapping
split = i
break
default:
break
}
}
}
if typ == typMapping && split+1 < len(line) && line[split+1] != ' ' {
typ = typScalar
split = 0
}
return
}
// lineReader implementations
type lineBuffer struct {
*bufio.Reader
readLines int
pending *indentedLine
}
func (lb *lineBuffer) Next(min int) (next *indentedLine) {
if lb.pending == nil {
var (
read []byte
more bool
err error
)
l := new(indentedLine)
l.lineno = lb.readLines
more = true
for more {
read, more, err = lb.ReadLine()
if err != nil {
if err == io.EOF {
return nil
}
panic(err)
}
l.line = append(l.line, read...)
}
lb.readLines++
for _, ch := range l.line {
switch ch {
case ' ':
l.indent += 1
continue
default:
}
break
}
l.line = l.line[l.indent:]
// Ignore blank lines and comments.
if len(l.line) == 0 || l.line[0] == '#' {
return lb.Next(min)
}
lb.pending = l
}
next = lb.pending
if next.indent < min {
return nil
}
lb.pending = nil
return
}
type lineSlice []*indentedLine
func (ls *lineSlice) Next(min int) (next *indentedLine) {
if len(*ls) == 0 {
return nil
}
next = (*ls)[0]
if next.indent < min {
return nil
}
*ls = (*ls)[1:]
return
}
func (ls *lineSlice) Push(line *indentedLine) {
*ls = append(*ls, line)
}