Beego/vendor/github.com/beego/goyaml2/reader.go

434 lines
8.9 KiB
Go

package goyaml2
import (
"bufio"
"fmt"
"github.com/wendal/errors"
"io"
"log"
"strings"
)
const (
DEBUG = true
MAP_KEY_ONLY = iota
)
func Read(r io.Reader) (interface{}, error) {
yr := &yamlReader{}
yr.br = bufio.NewReader(r)
obj, err := yr.ReadObject(0)
if err == io.EOF {
err = nil
}
if obj == nil {
log.Println("Obj == nil")
}
return obj, err
}
type yamlReader struct {
br *bufio.Reader
nodes []interface{}
lineNum int
lastLine string
}
func (y *yamlReader) ReadObject(minIndent int) (interface{}, error) {
line, err := y.NextLine()
if err != nil {
if err == io.EOF && line != "" {
//log.Println("Read EOF , but still some data here")
} else {
//log.Println("ReadERR", err)
return nil, err
}
}
y.lastLine = line
indent, str := getIndent(line)
if indent < minIndent {
//log.Println("Current Indent Unexpect : ", str, indent, minIndent)
return nil, y.Error("Unexpect Indent", nil)
}
if indent > minIndent {
//log.Println("Change minIndent from %d to %d", minIndent, indent)
minIndent = indent
}
switch str[0] {
case '-':
return y.ReadList(minIndent)
case '[':
fallthrough
case '{':
y.lastLine = ""
_, value, err := y.asMapKeyValue("tmp:" + str)
if err != nil {
return nil, y.Error("Err inline map/list", nil)
}
return value, nil
}
//log.Println("Read Objcet as Map", indent, str)
return y.ReadMap(minIndent)
}
func (y *yamlReader) ReadList(minIndent int) ([]interface{}, error) {
list := []interface{}{}
for {
line, err := y.NextLine()
if err != nil {
return list, err
}
indent, str := getIndent(line)
switch {
case indent < minIndent:
y.lastLine = line
if len(list) == 0 {
return nil, nil
}
return list, nil
case indent == minIndent:
if str[0] != '-' {
y.lastLine = line
return list, nil
}
if len(str) < 2 {
return nil, y.Error("ListItem is Emtry", nil)
}
key, value, err := y.asMapKeyValue(str[1:])
if err != nil {
return nil, err
}
switch value {
case nil:
list = append(list, key)
case MAP_KEY_ONLY:
return nil, y.Error("Not support List-Map yet", nil)
default:
_map := map[string]interface{}{key.(string): value}
list = append(list, _map)
_line, _err := y.NextLine()
if _err != nil && _err != io.EOF {
return nil, err
}
if _line == "" {
return list, nil
}
y.lastLine = _line
_indent, _str := getIndent(line)
if _indent >= minIndent+2 {
switch _str[0] {
case '-':
return nil, y.Error("Unexpect", nil)
case '[':
return nil, y.Error("Unexpect", nil)
case '{':
return nil, y.Error("Unexpect", nil)
}
// look like a map
_map2, _err := y.ReadMap(_indent)
if _map2 != nil {
_map2[key.(string)] = value
}
if err != nil {
return list, _err
}
}
}
continue
default:
return nil, y.Error("Bad Indent\n"+line, nil)
}
}
panic("ERROR")
return nil, errors.New("Impossible")
}
func (y *yamlReader) ReadMap(minIndent int) (map[string]interface{}, error) {
_map := map[string]interface{}{}
//log.Println("ReadMap", minIndent)
OUT:
for {
line, err := y.NextLine()
if err != nil {
return _map, err
}
indent, str := getIndent(line)
//log.Printf("Indent : %d, str = %s", indent, str)
switch {
case indent < minIndent:
y.lastLine = line
if len(_map) == 0 {
return nil, nil
}
return _map, nil
case indent == minIndent:
key, value, err := y.asMapKeyValue(str)
if err != nil {
return nil, err
}
//log.Println("Key=", key, "value=", value)
switch value {
case nil:
return nil, y.Error("Unexpect", nil)
case MAP_KEY_ONLY:
//log.Println("KeyOnly, read inner Map", key)
//--------------------------------------
_line, err := y.NextLine()
if err != nil {
if err == io.EOF {
if _line == "" {
// Emtry map item?
_map[key.(string)] = nil
return _map, err
}
} else {
return nil, y.Error("ERR?", err)
}
}
y.lastLine = _line
_indent, _str := getIndent(_line)
if _indent < minIndent {
return _map, nil
}
////log.Println("##>>", _indent, _str)
if _indent == minIndent {
if _str[0] == '-' {
//log.Println("Read Same-Indent ListItem for Map")
_list, err := y.ReadList(minIndent)
if _list != nil {
_map[key.(string)] = _list
}
if err != nil {
return _map, nil
}
continue OUT
} else {
// Emtry map item?
_map[key.(string)] = nil
continue OUT
}
}
//--------------------------------------
//log.Println("Read Map Item", _indent, _str)
obj, err := y.ReadObject(_indent)
if obj != nil {
_map[key.(string)] = obj
}
if err != nil {
return _map, err
}
default:
_map[key.(string)] = value
}
default:
//log.Println("Bad", indent, str)
return nil, y.Error("Bad Indent\n"+line, nil)
}
}
panic("ERROR")
return nil, errors.New("Impossible")
}
func (y *yamlReader) NextLine() (line string, err error) {
if y.lastLine != "" {
line = y.lastLine
y.lastLine = ""
//log.Println("Return lastLine", line)
return
}
for {
line, err = y.br.ReadString('\n')
y.lineNum++
if err != nil {
return
}
if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "#") {
continue
}
line = strings.TrimRight(line, "\n\t\r ")
if line == "" {
continue
}
//log.Println("Return Line", line)
return
}
//log.Println("Impossbible : " + line)
return // impossbile!
}
func getIndent(str string) (int, string) {
indent := 0
for i, s := range str {
switch s {
case ' ':
indent++
case '\t':
indent += 4
default:
return indent, str[i:]
}
}
panic("Invalid indent : " + str)
return -1, ""
}
func (y *yamlReader) asMapKeyValue(str string) (key interface{}, val interface{}, err error) {
tokens := splitToken(str)
key = tokens[0]
if len(tokens) == 1 {
return key, nil, nil
}
if tokens[1] != ":" {
return "", nil, y.Error("Unexpect "+str, nil)
}
if len(tokens) == 2 {
return key, MAP_KEY_ONLY, nil
}
if len(tokens) == 3 {
return key, tokens[2], nil
}
switch tokens[2] {
case "[":
list := []interface{}{}
for i := 3; i < len(tokens)-1; i++ {
list = append(list, tokens[i])
}
return key, list, nil
case "{":
_map := map[string]interface{}{}
for i := 3; i < len(tokens)-1; i += 4 {
//log.Println(">>>", i, tokens[i])
if i > len(tokens)-2 {
return "", nil, y.Error("Unexpect "+str, nil)
}
if tokens[i+1] != ":" {
return "", nil, y.Error("Unexpect "+str, nil)
}
_map[tokens[i].(string)] = tokens[i+2]
if (i + 3) < (len(tokens) - 1) {
if tokens[i+3] != "," {
return "", "", y.Error("Unexpect "+str, nil)
}
} else {
break
}
}
return key, _map, nil
}
//log.Println(str, tokens)
return "", nil, y.Error("Unexpect "+str, nil)
}
func splitToken(str string) (tokens []interface{}) {
str = strings.Trim(str, "\r\t\n ")
if str == "" {
panic("Impossbile")
return
}
tokens = []interface{}{}
lastPos := 0
for i := 0; i < len(str); i++ {
switch str[i] {
case ':':
fallthrough
case '{':
fallthrough
case '[':
fallthrough
case '}':
fallthrough
case ']':
fallthrough
case ',':
if i > lastPos {
tokens = append(tokens, str[lastPos:i])
}
tokens = append(tokens, str[i:i+1])
lastPos = i + 1
case ' ':
if i > lastPos {
tokens = append(tokens, str[lastPos:i])
}
lastPos = i + 1
case '\'':
//log.Println("Scan End of String")
i++
start := i
for ; i < len(str); i++ {
if str[i] == '\'' {
//log.Println("Found End of String", start, i)
break
}
}
tokens = append(tokens, str[start:i])
lastPos = i + 1
case '"':
i++
start := i
for ; i < len(str); i++ {
if str[i] == '"' {
break
}
}
tokens = append(tokens, str[start:i])
lastPos = i + 1
}
}
////log.Println("last", lastPos)
if lastPos < len(str) {
tokens = append(tokens, str[lastPos:])
}
if len(tokens) == 1 {
tokens[0] = string2Val(tokens[0].(string))
return
}
if tokens[1] == ":" {
if len(tokens) == 2 {
return
}
if tokens[2] == "{" || tokens[2] == "[" {
return
}
str = strings.Trim(strings.SplitN(str, ":", 2)[1], "\t ")
if len(str) > 2 {
if str[0] == '\'' && str[len(str)-1] == '\'' {
str = str[1 : len(str)-1]
} else if str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
}
}
val := string2Val(str)
tokens = []interface{}{tokens[0], tokens[1], val}
return
}
if len(str) > 2 {
if str[0] == '\'' && str[len(str)-1] == '\'' {
str = str[1 : len(str)-1]
} else if str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
}
}
val := string2Val(str)
tokens = []interface{}{val}
return
}
func (y *yamlReader) Error(msg string, err error) error {
if err != nil {
return errors.New(fmt.Sprintf("line %d : %s : %v", y.lineNum, msg, err.Error()))
}
return errors.New(fmt.Sprintf("line %d >> %s", y.lineNum, msg))
}