123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // 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)
- }
|