mirror of https://github.com/subgraph/fw-daemon
parent
b8f3bb54f9
commit
3798b30a2c
@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get -v github.com/naoina/go-stringutil
|
||||||
|
script:
|
||||||
|
- go test -v -bench . -benchmem ./...
|
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2015 Naoya Inada <naoina@kuune.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -0,0 +1,13 @@
|
|||||||
|
# stringutil [![Build Status](https://travis-ci.org/naoina/go-stringutil.svg?branch=master)](https://travis-ci.org/naoina/go-stringutil)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/naoina/go-stringutil
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See https://godoc.org/github.com/naoina/go-stringutil
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
@ -0,0 +1,253 @@
|
|||||||
|
package stringutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
terminationCharacter = '#'
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustDoubleArray(da *doubleArray, err error) *doubleArray {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return da
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) Build(keys []string) error {
|
||||||
|
records := makeRecords(keys)
|
||||||
|
if err := da.build(records, 1, 0, make(map[int]struct{})); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type doubleArray struct {
|
||||||
|
bc []baseCheck
|
||||||
|
node []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDoubleArray(keys []string) (*doubleArray, error) {
|
||||||
|
da := &doubleArray{
|
||||||
|
bc: []baseCheck{0},
|
||||||
|
node: []int{-1}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
|
||||||
|
}
|
||||||
|
if err := da.Build(keys); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return da, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseCheck contains BASE, CHECK and Extra flags.
|
||||||
|
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
|
||||||
|
//
|
||||||
|
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
|
||||||
|
// |----------------------|--|--------|
|
||||||
|
// 32 10 8 0
|
||||||
|
type baseCheck uint32
|
||||||
|
|
||||||
|
func (bc baseCheck) Base() int {
|
||||||
|
return int(bc >> 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *baseCheck) SetBase(base int) {
|
||||||
|
*bc |= baseCheck(base) << 10
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc baseCheck) Check() byte {
|
||||||
|
return byte(bc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *baseCheck) SetCheck(check byte) {
|
||||||
|
*bc |= baseCheck(check)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc baseCheck) IsEmpty() bool {
|
||||||
|
return bc&0xfffffcff == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) Lookup(path string) (length int) {
|
||||||
|
idx := 1
|
||||||
|
tmpIdx := idx
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
c := path[i]
|
||||||
|
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c)
|
||||||
|
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx = tmpIdx
|
||||||
|
}
|
||||||
|
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter {
|
||||||
|
return da.node[da.bc[next].Base()]
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) LookupByBytes(path []byte) (length int) {
|
||||||
|
idx := 1
|
||||||
|
tmpIdx := idx
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
c := path[i]
|
||||||
|
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c)
|
||||||
|
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx = tmpIdx
|
||||||
|
}
|
||||||
|
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter {
|
||||||
|
return da.node[da.bc[next].Base()]
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) build(srcs []record, idx, depth int, usedBase map[int]struct{}) error {
|
||||||
|
sort.Stable(recordSlice(srcs))
|
||||||
|
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if leaf != nil {
|
||||||
|
da.bc[idx].SetBase(len(da.node))
|
||||||
|
da.node = append(da.node, leaf.value)
|
||||||
|
}
|
||||||
|
for _, sib := range siblings {
|
||||||
|
da.setCheck(da.nextIndex(base, sib.c), sib.c)
|
||||||
|
}
|
||||||
|
for _, sib := range siblings {
|
||||||
|
if err := da.build(srcs[sib.start:sib.end], da.nextIndex(base, sib.c), depth+1, usedBase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) setBase(i, base int) {
|
||||||
|
da.bc[i].SetBase(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) setCheck(i int, check byte) {
|
||||||
|
da.bc[i].SetCheck(check)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) findEmptyIndex(start int) int {
|
||||||
|
i := start
|
||||||
|
for ; i < len(da.bc); i++ {
|
||||||
|
if da.bc[i].IsEmpty() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBase returns good BASE.
|
||||||
|
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
|
||||||
|
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
|
||||||
|
base = da.nextIndex(idx, firstChar)
|
||||||
|
if _, used := usedBase[base]; used {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for ; i < len(siblings); i++ {
|
||||||
|
next := da.nextIndex(base, siblings[i].c)
|
||||||
|
if len(da.bc) <= next {
|
||||||
|
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
|
||||||
|
}
|
||||||
|
if !da.bc[next].IsEmpty() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == len(siblings) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usedBase[base] = struct{}{}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) arrange(records []record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
|
||||||
|
siblings, leaf, err = makeSiblings(records, depth)
|
||||||
|
if err != nil {
|
||||||
|
return -1, nil, nil, err
|
||||||
|
}
|
||||||
|
if len(siblings) < 1 {
|
||||||
|
return -1, nil, leaf, nil
|
||||||
|
}
|
||||||
|
base = da.findBase(siblings, idx, usedBase)
|
||||||
|
da.setBase(idx, base)
|
||||||
|
return base, siblings, leaf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type sibling struct {
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
c byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da *doubleArray) nextIndex(base int, c byte) int {
|
||||||
|
return base ^ int(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSiblings(records []record, depth int) (sib []sibling, leaf *record, err error) {
|
||||||
|
var (
|
||||||
|
pc byte
|
||||||
|
n int
|
||||||
|
)
|
||||||
|
for i, r := range records {
|
||||||
|
if len(r.key) <= depth {
|
||||||
|
leaf = &r
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := r.key[depth]
|
||||||
|
switch {
|
||||||
|
case pc < c:
|
||||||
|
sib = append(sib, sibling{start: i, c: c})
|
||||||
|
case pc == c:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("stringutil: BUG: records hasn't been sorted")
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
sib[n-1].end = i
|
||||||
|
}
|
||||||
|
pc = c
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return nil, leaf, nil
|
||||||
|
}
|
||||||
|
sib[n-1].end = len(records)
|
||||||
|
return sib, leaf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type record struct {
|
||||||
|
key string
|
||||||
|
value int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRecords(srcs []string) (records []record) {
|
||||||
|
termChar := string(terminationCharacter)
|
||||||
|
for _, s := range srcs {
|
||||||
|
records = append(records, record{
|
||||||
|
key: string(s + termChar),
|
||||||
|
value: utf8.RuneCountInString(s),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordSlice []record
|
||||||
|
|
||||||
|
func (rs recordSlice) Len() int {
|
||||||
|
return len(rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs recordSlice) Less(i, j int) bool {
|
||||||
|
return rs[i].key < rs[j].key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs recordSlice) Swap(i, j int) {
|
||||||
|
rs[i], rs[j] = rs[j], rs[i]
|
||||||
|
}
|
@ -0,0 +1,320 @@
|
|||||||
|
package stringutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// Based on https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
|
||||||
|
commonInitialismMap = map[string]struct{}{
|
||||||
|
"API": struct{}{},
|
||||||
|
"ASCII": struct{}{},
|
||||||
|
"CPU": struct{}{},
|
||||||
|
"CSRF": struct{}{},
|
||||||
|
"CSS": struct{}{},
|
||||||
|
"DNS": struct{}{},
|
||||||
|
"EOF": struct{}{},
|
||||||
|
"GUID": struct{}{},
|
||||||
|
"HTML": struct{}{},
|
||||||
|
"HTTP": struct{}{},
|
||||||
|
"HTTPS": struct{}{},
|
||||||
|
"ID": struct{}{},
|
||||||
|
"IP": struct{}{},
|
||||||
|
"JSON": struct{}{},
|
||||||
|
"LHS": struct{}{},
|
||||||
|
"QPS": struct{}{},
|
||||||
|
"RAM": struct{}{},
|
||||||
|
"RHS": struct{}{},
|
||||||
|
"RPC": struct{}{},
|
||||||
|
"SLA": struct{}{},
|
||||||
|
"SMTP": struct{}{},
|
||||||
|
"SQL": struct{}{},
|
||||||
|
"SSH": struct{}{},
|
||||||
|
"TCP": struct{}{},
|
||||||
|
"TLS": struct{}{},
|
||||||
|
"TTL": struct{}{},
|
||||||
|
"UDP": struct{}{},
|
||||||
|
"UI": struct{}{},
|
||||||
|
"UID": struct{}{},
|
||||||
|
"UUID": struct{}{},
|
||||||
|
"URI": struct{}{},
|
||||||
|
"URL": struct{}{},
|
||||||
|
"UTF8": struct{}{},
|
||||||
|
"VM": struct{}{},
|
||||||
|
"XML": struct{}{},
|
||||||
|
"XSRF": struct{}{},
|
||||||
|
"XSS": struct{}{},
|
||||||
|
}
|
||||||
|
commonInitialisms = keys(commonInitialismMap)
|
||||||
|
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
|
||||||
|
longestLen = longestLength(commonInitialisms)
|
||||||
|
shortestLen = shortestLength(commonInitialisms, longestLen)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToUpperCamelCase returns a copy of the string s with all Unicode letters mapped to their camel case.
|
||||||
|
// It will convert to upper case previous letter of '_' and first letter, and remove letter of '_'.
|
||||||
|
func ToUpperCamelCase(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
upper := true
|
||||||
|
start := 0
|
||||||
|
result := make([]byte, 0, len(s))
|
||||||
|
var runeBuf [utf8.UTFMax]byte
|
||||||
|
var initialism []byte
|
||||||
|
for _, c := range s {
|
||||||
|
if c == '_' {
|
||||||
|
upper = true
|
||||||
|
candidate := string(result[start:])
|
||||||
|
initialism = initialism[:0]
|
||||||
|
for _, r := range candidate {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
initialism = append(initialism, toUpperASCII(byte(r)))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r))
|
||||||
|
initialism = append(initialism, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
|
||||||
|
result = append(result[:start], initialism...)
|
||||||
|
}
|
||||||
|
start = len(result)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if upper {
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
result = append(result, toUpperASCII(byte(c)))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(c))
|
||||||
|
result = append(result, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
upper = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
result = append(result, byte(c))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], c)
|
||||||
|
result = append(result, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidate := string(result[start:])
|
||||||
|
initialism = initialism[:0]
|
||||||
|
for _, r := range candidate {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
initialism = append(initialism, toUpperASCII(byte(r)))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r))
|
||||||
|
initialism = append(initialism, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
|
||||||
|
result = append(result[:start], initialism...)
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUpperCamelCaseASCII is similar to ToUpperCamelCase, but optimized for
|
||||||
|
// only the ASCII characters.
|
||||||
|
// ToUpperCamelCaseASCII is faster than ToUpperCamelCase, but doesn't work if
|
||||||
|
// contains non-ASCII characters.
|
||||||
|
func ToUpperCamelCaseASCII(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
upper := true
|
||||||
|
start := 0
|
||||||
|
result := make([]byte, 0, len(s))
|
||||||
|
var initialism []byte
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c == '_' {
|
||||||
|
upper = true
|
||||||
|
candidate := result[start:]
|
||||||
|
initialism = initialism[:0]
|
||||||
|
for _, b := range candidate {
|
||||||
|
initialism = append(initialism, toUpperASCII(b))
|
||||||
|
}
|
||||||
|
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
|
||||||
|
result = append(result[:start], initialism...)
|
||||||
|
}
|
||||||
|
start = len(result)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if upper {
|
||||||
|
result = append(result, toUpperASCII(c))
|
||||||
|
upper = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, c)
|
||||||
|
}
|
||||||
|
candidate := result[start:]
|
||||||
|
initialism = initialism[:0]
|
||||||
|
for _, b := range candidate {
|
||||||
|
initialism = append(initialism, toUpperASCII(b))
|
||||||
|
}
|
||||||
|
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
|
||||||
|
result = append(result[:start], initialism...)
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSnakeCase returns a copy of the string s with all Unicode letters mapped to their snake case.
|
||||||
|
// It will insert letter of '_' at position of previous letter of uppercase and all
|
||||||
|
// letters convert to lower case.
|
||||||
|
// ToSnakeCase does not insert '_' letter into a common initialism word like ID, URL and so on.
|
||||||
|
func ToSnakeCase(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := make([]byte, 0, len(s))
|
||||||
|
var runeBuf [utf8.UTFMax]byte
|
||||||
|
var j, skipCount int
|
||||||
|
for i, c := range s {
|
||||||
|
if i < skipCount {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if unicode.IsUpper(c) {
|
||||||
|
if i != 0 {
|
||||||
|
result = append(result, '_')
|
||||||
|
}
|
||||||
|
next := nextIndex(j, len(s))
|
||||||
|
if length := commonInitialism.Lookup(s[j:next]); length > 0 {
|
||||||
|
for _, r := range s[j : j+length] {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
result = append(result, toLowerASCII(byte(r)))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(r))
|
||||||
|
result = append(result, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j += length - 1
|
||||||
|
skipCount = i + length
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
result = append(result, toLowerASCII(byte(c)))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(c))
|
||||||
|
result = append(result, runeBuf[:n]...)
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSnakeCaseASCII is similar to ToSnakeCase, but optimized for only the ASCII
|
||||||
|
// characters.
|
||||||
|
// ToSnakeCaseASCII is faster than ToSnakeCase, but doesn't work correctly if
|
||||||
|
// contains non-ASCII characters.
|
||||||
|
func ToSnakeCaseASCII(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := make([]byte, 0, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if isUpperASCII(c) {
|
||||||
|
if i != 0 {
|
||||||
|
result = append(result, '_')
|
||||||
|
}
|
||||||
|
if k := i + shortestLen - 1; k < len(s) && isUpperASCII(s[k]) {
|
||||||
|
if length := commonInitialism.Lookup(s[i:nextIndex(i, len(s))]); length > 0 {
|
||||||
|
for j, buf := 0, s[i:i+length]; j < len(buf); j++ {
|
||||||
|
result = append(result, toLowerASCII(buf[j]))
|
||||||
|
}
|
||||||
|
i += length - 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, toLowerASCII(c))
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCommonInitialism adds ss to list of common initialisms.
|
||||||
|
func AddCommonInitialism(ss ...string) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
for _, s := range ss {
|
||||||
|
commonInitialismMap[s] = struct{}{}
|
||||||
|
}
|
||||||
|
commonInitialisms = keys(commonInitialismMap)
|
||||||
|
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
|
||||||
|
longestLen = longestLength(commonInitialisms)
|
||||||
|
shortestLen = shortestLength(commonInitialisms, longestLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelCommonInitialism deletes ss from list of common initialisms.
|
||||||
|
func DelCommonInitialism(ss ...string) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
for _, s := range ss {
|
||||||
|
delete(commonInitialismMap, s)
|
||||||
|
}
|
||||||
|
commonInitialisms = keys(commonInitialismMap)
|
||||||
|
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
|
||||||
|
longestLen = longestLength(commonInitialisms)
|
||||||
|
shortestLen = shortestLength(commonInitialisms, longestLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUpperASCII(c byte) bool {
|
||||||
|
return 'A' <= c && c <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLowerASCII(c byte) bool {
|
||||||
|
return 'a' <= c && c <= 'z'
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUpperASCII(c byte) byte {
|
||||||
|
if isLowerASCII(c) {
|
||||||
|
return c - ('a' - 'A')
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLowerASCII(c byte) byte {
|
||||||
|
if isUpperASCII(c) {
|
||||||
|
return c + 'a' - 'A'
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextIndex(i, maxlen int) int {
|
||||||
|
if n := i + longestLen; n < maxlen {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return maxlen
|
||||||
|
}
|
||||||
|
|
||||||
|
func keys(m map[string]struct{}) []string {
|
||||||
|
result := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
result = append(result, k)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortestLength(strs []string, shortest int) int {
|
||||||
|
for _, s := range strs {
|
||||||
|
if candidate := utf8.RuneCountInString(s); candidate < shortest {
|
||||||
|
shortest = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shortest
|
||||||
|
}
|
||||||
|
|
||||||
|
func longestLength(strs []string) (longest int) {
|
||||||
|
for _, s := range strs {
|
||||||
|
if candidate := utf8.RuneCountInString(s); candidate > longest {
|
||||||
|
longest = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return longest
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test ./...
|
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2014 Naoya Inada <naoina@kuune.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -0,0 +1,16 @@
|
|||||||
|
GO = go
|
||||||
|
PEG = peg
|
||||||
|
|
||||||
|
.SUFFIXES: .peg .peg.go
|
||||||
|
|
||||||
|
.PHONY: all test clean
|
||||||
|
all: parse.peg.go
|
||||||
|
|
||||||
|
.peg.peg.go:
|
||||||
|
$(PEG) -switch -inline $<
|
||||||
|
|
||||||
|
test: all
|
||||||
|
$(GO) test ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.peg.go
|
@ -0,0 +1,364 @@
|
|||||||
|
# TOML parser and encoder library for Golang [![Build Status](https://travis-ci.org/naoina/toml.png?branch=master)](https://travis-ci.org/naoina/toml)
|
||||||
|
|
||||||
|
[TOML](https://github.com/toml-lang/toml) parser and encoder library for [Golang](http://golang.org/).
|
||||||
|
|
||||||
|
This library is compatible with TOML version [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/naoina/toml
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The following TOML save as `example.toml`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# This is a TOML document. Boom.
|
||||||
|
|
||||||
|
title = "TOML Example"
|
||||||
|
|
||||||
|
[owner]
|
||||||
|
name = "Lance Uppercut"
|
||||||
|
dob = 1979-05-27T07:32:00-08:00 # First class dates? Why not?
|
||||||
|
|
||||||
|
[database]
|
||||||
|
server = "192.168.1.1"
|
||||||
|
ports = [ 8001, 8001, 8002 ]
|
||||||
|
connection_max = 5000
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[servers]
|
||||||
|
|
||||||
|
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||||
|
[servers.alpha]
|
||||||
|
ip = "10.0.0.1"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[servers.beta]
|
||||||
|
ip = "10.0.0.2"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[clients]
|
||||||
|
data = [ ["gamma", "delta"], [1, 2] ]
|
||||||
|
|
||||||
|
# Line breaks are OK when inside arrays
|
||||||
|
hosts = [
|
||||||
|
"alpha",
|
||||||
|
"omega"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then above TOML will mapping to `tomlConfig` struct using `toml.Unmarshal`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/naoina/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlConfig struct {
|
||||||
|
Title string
|
||||||
|
Owner struct {
|
||||||
|
Name string
|
||||||
|
Dob time.Time
|
||||||
|
}
|
||||||
|
Database struct {
|
||||||
|
Server string
|
||||||
|
Ports []int
|
||||||
|
ConnectionMax uint
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
Servers map[string]Server
|
||||||
|
Clients struct {
|
||||||
|
Data [][]interface{}
|
||||||
|
Hosts []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
IP string
|
||||||
|
DC string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open("example.toml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
buf, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var config tomlConfig
|
||||||
|
if err := toml.Unmarshal(buf, &config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// then to use the unmarshaled config...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mappings
|
||||||
|
|
||||||
|
A key and value of TOML will map to the corresponding field.
|
||||||
|
The fields of struct for mapping must be exported.
|
||||||
|
|
||||||
|
The rules of the mapping of key are following:
|
||||||
|
|
||||||
|
#### Exact matching
|
||||||
|
|
||||||
|
```toml
|
||||||
|
timeout_seconds = 256
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Timeout_seconds int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Camelcase matching
|
||||||
|
|
||||||
|
```toml
|
||||||
|
server_name = "srv1"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
ServerName string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Uppercase matching
|
||||||
|
|
||||||
|
```toml
|
||||||
|
ip = "10.0.0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
IP string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the following examples for the value mappings.
|
||||||
|
|
||||||
|
### String
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = "string"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integer
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = 100
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All types that can be used are following:
|
||||||
|
|
||||||
|
* int8 (from `-128` to `127`)
|
||||||
|
* int16 (from `-32768` to `32767`)
|
||||||
|
* int32 (from `-2147483648` to `2147483647`)
|
||||||
|
* int64 (from `-9223372036854775808` to `9223372036854775807`)
|
||||||
|
* int (same as `int32` on 32bit environment, or `int64` on 64bit environment)
|
||||||
|
* uint8 (from `0` to `255`)
|
||||||
|
* uint16 (from `0` to `65535`)
|
||||||
|
* uint32 (from `0` to `4294967295`)
|
||||||
|
* uint64 (from `0` to `18446744073709551615`)
|
||||||
|
* uint (same as `uint` on 32bit environment, or `uint64` on 64bit environment)
|
||||||
|
|
||||||
|
### Float
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = 3.1415
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val float32
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All types that can be used are following:
|
||||||
|
|
||||||
|
* float32
|
||||||
|
* float64
|
||||||
|
|
||||||
|
### Boolean
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = true
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Datetime
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = 2014-09-28T21:27:39Z
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Array
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val = ["a", "b", "c"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val []string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also following examples all can be mapped:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
val1 = [1, 2, 3]
|
||||||
|
val2 = [["a", "b"], ["c", "d"]]
|
||||||
|
val3 = [[1, 2, 3], ["a", "b", "c"]]
|
||||||
|
val4 = [[1, 2, 3], [["a", "b"], [true, false]]]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Val1 []int
|
||||||
|
Val2 [][]string
|
||||||
|
Val3 [][]interface{}
|
||||||
|
Val4 [][]interface{}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Table
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[server]
|
||||||
|
type = "app"
|
||||||
|
|
||||||
|
[server.development]
|
||||||
|
ip = "10.0.0.1"
|
||||||
|
|
||||||
|
[server.production]
|
||||||
|
ip = "10.0.0.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Server map[string]Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
IP string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the following struct instead of map of struct.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Server struct {
|
||||||
|
Development Server
|
||||||
|
Production Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
IP string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Array of Tables
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[fruit]]
|
||||||
|
name = "apple"
|
||||||
|
|
||||||
|
[fruit.physical]
|
||||||
|
color = "red"
|
||||||
|
shape = "round"
|
||||||
|
|
||||||
|
[[fruit.variety]]
|
||||||
|
name = "red delicious"
|
||||||
|
|
||||||
|
[[fruit.variety]]
|
||||||
|
name = "granny smith"
|
||||||
|
|
||||||
|
[[fruit]]
|
||||||
|
name = "banana"
|
||||||
|
|
||||||
|
[[fruit.variety]]
|
||||||
|
name = "plantain"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Fruit []struct {
|
||||||
|
Name string
|
||||||
|
Physical struct {
|
||||||
|
Color string
|
||||||
|
Shape string
|
||||||
|
}
|
||||||
|
Variety []struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `toml.UnmarshalTOML` interface
|
||||||
|
|
||||||
|
```toml
|
||||||
|
duration = "10s"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
import time
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Duration Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Duration) UnmarshalTOML(data []byte) error {
|
||||||
|
d.Duration, err := time.ParseDuration(string(data))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API documentation
|
||||||
|
|
||||||
|
See [Godoc](http://godoc.org/github.com/naoina/toml).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
@ -0,0 +1,184 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Position struct {
|
||||||
|
Begin int
|
||||||
|
End int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Value interface {
|
||||||
|
Pos() int
|
||||||
|
End() int
|
||||||
|
Source() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type String struct {
|
||||||
|
Position Position
|
||||||
|
Value string
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) Pos() int {
|
||||||
|
return s.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) End() int {
|
||||||
|
return s.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) Source() string {
|
||||||
|
return string(s.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Integer struct {
|
||||||
|
Position Position
|
||||||
|
Value string
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Integer) Pos() int {
|
||||||
|
return i.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Integer) End() int {
|
||||||
|
return i.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Integer) Source() string {
|
||||||
|
return string(i.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Integer) Int() (int64, error) {
|
||||||
|
return strconv.ParseInt(i.Value, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Float struct {
|
||||||
|
Position Position
|
||||||
|
Value string
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float) Pos() int {
|
||||||
|
return f.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float) End() int {
|
||||||
|
return f.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float) Source() string {
|
||||||
|
return string(f.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float) Float() (float64, error) {
|
||||||
|
return strconv.ParseFloat(f.Value, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Boolean struct {
|
||||||
|
Position Position
|
||||||
|
Value string
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Boolean) Pos() int {
|
||||||
|
return b.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Boolean) End() int {
|
||||||
|
return b.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Boolean) Source() string {
|
||||||
|
return string(b.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Boolean) Boolean() (bool, error) {
|
||||||
|
return strconv.ParseBool(b.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Datetime struct {
|
||||||
|
Position Position
|
||||||
|
Value string
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datetime) Pos() int {
|
||||||
|
return d.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datetime) End() int {
|
||||||
|
return d.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datetime) Source() string {
|
||||||
|
return string(d.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datetime) Time() (time.Time, error) {
|
||||||
|
return time.Parse(time.RFC3339Nano, d.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Array struct {
|
||||||
|
Position Position
|
||||||
|
Value []Value
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Array) Pos() int {
|
||||||
|
return a.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Array) End() int {
|
||||||
|
return a.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Array) Source() string {
|
||||||
|
return string(a.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TableTypeNormal TableType = iota
|
||||||
|
TableTypeArray
|
||||||
|
)
|
||||||
|
|
||||||
|
var tableTypes = [...]string{
|
||||||
|
"normal",
|
||||||
|
"array",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TableType) String() string {
|
||||||
|
return tableTypes[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Table struct {
|
||||||
|
Position Position
|
||||||
|
Line int
|
||||||
|
Name string
|
||||||
|
Fields map[string]interface{}
|
||||||
|
Type TableType
|
||||||
|
Data []rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Pos() int {
|
||||||
|
return t.Position.Begin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) End() int {
|
||||||
|
return t.Position.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Source() string {
|
||||||
|
return string(t.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyValue struct {
|
||||||
|
Key string
|
||||||
|
Value Value
|
||||||
|
Line int
|
||||||
|
}
|
@ -0,0 +1,678 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/naoina/toml/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableSeparator = '.'
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
escapeReplacer = strings.NewReplacer(
|
||||||
|
"\b", "\\n",
|
||||||
|
"\f", "\\f",
|
||||||
|
"\n", "\\n",
|
||||||
|
"\r", "\\r",
|
||||||
|
"\t", "\\t",
|
||||||
|
)
|
||||||
|
underscoreReplacer = strings.NewReplacer(
|
||||||
|
"_", "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshal parses the TOML data and stores the result in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Unmarshal will mapped to v that according to following rules:
|
||||||
|
//
|
||||||
|
// TOML strings to string
|
||||||
|
// TOML integers to any int type
|
||||||
|
// TOML floats to float32 or float64
|
||||||
|
// TOML booleans to bool
|
||||||
|
// TOML datetimes to time.Time
|
||||||
|
// TOML arrays to any type of slice or []interface{}
|
||||||
|
// TOML tables to struct
|
||||||
|
// TOML array of tables to slice of struct
|
||||||
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
|
table, err := Parse(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := UnmarshalTable(table, v); err != nil {
|
||||||
|
return fmt.Errorf("toml: unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Decoder reads and decodes TOML from an input stream.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new Decoder that reads from r.
|
||||||
|
// Note that it reads all from r before parsing it.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode parses the TOML data from its input and stores it in the value pointed to by v.
|
||||||
|
// See the documentation for Unmarshal for details about the conversion of TOML into a Go value.
|
||||||
|
func (d *Decoder) Decode(v interface{}) error {
|
||||||
|
b, err := ioutil.ReadAll(d.r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Unmarshal(b, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||||
|
// TOML description of themselves.
|
||||||
|
// The input can be assumed to be a valid encoding of a TOML value.
|
||||||
|
// UnmarshalJSON must copy the TOML data if it wishes to retain the data after
|
||||||
|
// returning.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
UnmarshalTOML([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v.
|
||||||
|
//
|
||||||
|
// UnmarshalTable will mapped to v that according to following rules:
|
||||||
|
//
|
||||||
|
// TOML strings to string
|
||||||
|
// TOML integers to any int type
|
||||||
|
// TOML floats to float32 or float64
|
||||||
|
// TOML booleans to bool
|
||||||
|
// TOML datetimes to time.Time
|
||||||
|
// TOML arrays to any type of slice or []interface{}
|
||||||
|
// TOML tables to struct
|
||||||
|
// TOML array of tables to slice of struct
|
||||||
|
func UnmarshalTable(t *ast.Table, v interface{}) (err error) {
|
||||||
|
if v == nil {
|
||||||
|
return fmt.Errorf("v must not be nil")
|
||||||
|
}
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if kind := rv.Kind(); kind != reflect.Ptr && kind != reflect.Map {
|
||||||
|
return fmt.Errorf("v must be a pointer or map")
|
||||||
|
}
|
||||||
|
for rv.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
if err, ok := setUnmarshaler(rv, string(t.Data)); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for key, val := range t.Fields {
|
||||||
|
switch av := val.(type) {
|
||||||
|
case *ast.KeyValue:
|
||||||
|
fv, fieldName, found := findField(rv, key)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av.Line, key, v)
|
||||||
|
}
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
mv := reflect.New(fv.Type().Elem()).Elem()
|
||||||
|
if err := UnmarshalTable(t, mv.Addr().Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fv.SetMapIndex(reflect.ValueOf(fieldName), mv)
|
||||||
|
default:
|
||||||
|
if err := setValue(fv, av.Value); err != nil {
|
||||||
|
return fmt.Errorf("line %d: %v.%s: %v", av.Line, rv.Type(), fieldName, err)
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Map {
|
||||||
|
rv.SetMapIndex(reflect.ValueOf(fieldName), fv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.Table:
|
||||||
|
fv, fieldName, found := findField(rv, key)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av.Line, key, v)
|
||||||
|
}
|
||||||
|
if err, ok := setUnmarshaler(fv, string(av.Data)); ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for fv.Kind() == reflect.Ptr {
|
||||||
|
fv.Set(reflect.New(fv.Type().Elem()))
|
||||||
|
fv = fv.Elem()
|
||||||
|
}
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
vv := reflect.New(fv.Type()).Elem()
|
||||||
|
if err := UnmarshalTable(av, vv.Addr().Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fv.Set(vv)
|
||||||
|
if rv.Kind() == reflect.Map {
|
||||||
|
rv.SetMapIndex(reflect.ValueOf(fieldName), fv)
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
mv := reflect.MakeMap(fv.Type())
|
||||||
|
if err := UnmarshalTable(av, mv.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fv.Set(mv)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("line %d: `%v.%s' must be struct or map, but %v given", av.Line, rv.Type(), fieldName, fv.Kind())
|
||||||
|
}
|
||||||
|
case []*ast.Table:
|
||||||
|
fv, fieldName, found := findField(rv, key)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av[0].Line, key, v)
|
||||||
|
}
|
||||||
|
data := make([]string, 0, len(av))
|
||||||
|
for _, tbl := range av {
|
||||||
|
data = append(data, string(tbl.Data))
|
||||||
|
}
|
||||||
|
if err, ok := setUnmarshaler(fv, strings.Join(data, "\n")); ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t := fv.Type().Elem()
|
||||||
|
pc := 0
|
||||||
|
for ; t.Kind() == reflect.Ptr; pc++ {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if fv.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("line %d: `%v.%s' must be slice type, but %v given", av[0].Line, rv.Type(), fieldName, fv.Kind())
|
||||||
|
}
|
||||||
|
for _, tbl := range av {
|
||||||
|
var vv reflect.Value
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
vv = reflect.MakeMap(t)
|
||||||
|
if err := UnmarshalTable(tbl, vv.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
vv = reflect.New(t).Elem()
|
||||||
|
if err := UnmarshalTable(tbl, vv.Addr().Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < pc; i++ {
|
||||||
|
vv = vv.Addr()
|
||||||
|
pv := reflect.New(vv.Type()).Elem()
|
||||||
|
pv.Set(vv)
|
||||||
|
vv = pv
|
||||||
|
}
|
||||||
|
fv.Set(reflect.Append(fv, vv))
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Map {
|
||||||
|
rv.SetMapIndex(reflect.ValueOf(fieldName), fv)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("BUG: unknown type `%T'", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUnmarshaler(lhs reflect.Value, data string) (error, bool) {
|
||||||
|
for lhs.Kind() == reflect.Ptr {
|
||||||
|
lhs.Set(reflect.New(lhs.Type().Elem()))
|
||||||
|
lhs = lhs.Elem()
|
||||||
|
}
|
||||||
|
if lhs.CanAddr() {
|
||||||
|
if u, ok := lhs.Addr().Interface().(Unmarshaler); ok {
|
||||||
|
return u.UnmarshalTOML([]byte(data)), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setValue(lhs reflect.Value, val ast.Value) error {
|
||||||
|
for lhs.Kind() == reflect.Ptr {
|
||||||
|
lhs.Set(reflect.New(lhs.Type().Elem()))
|
||||||
|
lhs = lhs.Elem()
|
||||||
|
}
|
||||||
|
if err, ok := setUnmarshaler(lhs, val.Source()); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch v := val.(type) {
|
||||||
|
case *ast.Integer:
|
||||||
|
if err := setInt(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.Float:
|
||||||
|
if err := setFloat(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.String:
|
||||||
|
if err := setString(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.Boolean:
|
||||||
|
if err := setBoolean(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.Datetime:
|
||||||
|
if err := setDatetime(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.Array:
|
||||||
|
if err := setArray(lhs, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInt(fv reflect.Value, v *ast.Integer) error {
|
||||||
|
i, err := v.Int()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if fv.OverflowInt(i) {
|
||||||
|
return &errorOutOfRange{fv.Kind(), i}
|
||||||
|
}
|
||||||
|
fv.SetInt(i)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
fv.SetUint(uint64(i))
|
||||||
|
case reflect.Interface:
|
||||||
|
fv.Set(reflect.ValueOf(i))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("`%v' is not any types of int", fv.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFloat(fv reflect.Value, v *ast.Float) error {
|
||||||
|
f, err := v.Float()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if fv.OverflowFloat(f) {
|
||||||
|
return &errorOutOfRange{fv.Kind(), f}
|
||||||
|
}
|
||||||
|
fv.SetFloat(f)
|
||||||
|
case reflect.Interface:
|
||||||
|
fv.Set(reflect.ValueOf(f))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("`%v' is not float32 or float64", fv.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setString(fv reflect.Value, v *ast.String) error {
|
||||||
|
return set(fv, v.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoolean(fv reflect.Value, v *ast.Boolean) error {
|
||||||
|
b, err := v.Boolean()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return set(fv, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDatetime(fv reflect.Value, v *ast.Datetime) error {
|
||||||
|
tm, err := v.Time()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return set(fv, tm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArray(fv reflect.Value, v *ast.Array) error {
|
||||||
|
if len(v.Value) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
typ := reflect.TypeOf(v.Value[0])
|
||||||
|
for _, vv := range v.Value[1:] {
|
||||||
|
if typ != reflect.TypeOf(vv) {
|
||||||
|
return fmt.Errorf("array cannot contain multiple types")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sliceType := fv.Type()
|
||||||
|
if fv.Kind() == reflect.Interface {
|
||||||
|
sliceType = reflect.SliceOf(sliceType)
|
||||||
|
}
|
||||||
|
slice := reflect.MakeSlice(sliceType, 0, len(v.Value))
|
||||||
|
t := sliceType.Elem()
|
||||||
|
for _, vv := range v.Value {
|
||||||
|
tmp := reflect.New(t).Elem()
|
||||||
|
if err := setValue(tmp, vv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice = reflect.Append(slice, tmp)
|
||||||
|
}
|
||||||
|
fv.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(fv reflect.Value, v interface{}) error {
|
||||||
|
rhs := reflect.ValueOf(v)
|
||||||
|
if !rhs.Type().AssignableTo(fv.Type()) {
|
||||||
|
return fmt.Errorf("`%v' type is not assignable to `%v' type", rhs.Type(), fv.Type())
|
||||||
|
}
|
||||||
|
fv.Set(rhs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stack struct {
|
||||||
|
key string
|
||||||
|
table *ast.Table
|
||||||
|
}
|
||||||
|
|
||||||
|
type toml struct {
|
||||||
|
table *ast.Table
|
||||||
|
line int
|
||||||
|
currentTable *ast.Table
|
||||||
|
s string
|
||||||
|
key string
|
||||||
|
val ast.Value
|
||||||
|
arr *array
|
||||||
|
tableMap map[string]*ast.Table
|
||||||
|
stack []*stack
|
||||||
|
skip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) init(data []rune) {
|
||||||
|
p.line = 1
|
||||||
|
p.table = &ast.Table{
|
||||||
|
Line: p.line,
|
||||||
|
Type: ast.TableTypeNormal,
|
||||||
|
Data: data[:len(data)-1], // truncate the end_symbol added by PEG parse generator.
|
||||||
|
}
|
||||||
|
p.tableMap = map[string]*ast.Table{
|
||||||
|
"": p.table,
|
||||||
|
}
|
||||||
|
p.currentTable = p.table
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) Error(err error) {
|
||||||
|
panic(convertError{fmt.Errorf("toml: line %d: %v", p.line, err)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetTime(begin, end int) {
|
||||||
|
p.val = &ast.Datetime{
|
||||||
|
Position: ast.Position{Begin: begin, End: end},
|
||||||
|
Data: p.buffer[begin:end],
|
||||||
|
Value: string(p.buffer[begin:end]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetFloat64(begin, end int) {
|
||||||
|
p.val = &ast.Float{
|
||||||
|
Position: ast.Position{Begin: begin, End: end},
|
||||||
|
Data: p.buffer[begin:end],
|
||||||
|
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetInt64(begin, end int) {
|
||||||
|
p.val = &ast.Integer{
|
||||||
|
Position: ast.Position{Begin: begin, End: end},
|
||||||
|
Data: p.buffer[begin:end],
|
||||||
|
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetString(begin, end int) {
|
||||||
|
p.val = &ast.String{
|
||||||
|
Position: ast.Position{Begin: begin, End: end},
|
||||||
|
Data: p.buffer[begin:end],
|
||||||
|
Value: p.s,
|
||||||
|
}
|
||||||
|
p.s = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetBool(begin, end int) {
|
||||||
|
p.val = &ast.Boolean{
|
||||||
|
Position: ast.Position{Begin: begin, End: end},
|
||||||
|
Data: p.buffer[begin:end],
|
||||||
|
Value: string(p.buffer[begin:end]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) StartArray() {
|
||||||
|
if p.arr == nil {
|
||||||
|
p.arr = &array{line: p.line, current: &ast.Array{}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.arr.child = &array{parent: p.arr, line: p.line, current: &ast.Array{}}
|
||||||
|
p.arr = p.arr.child
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) AddArrayVal() {
|
||||||
|
if p.arr.current == nil {
|
||||||
|
p.arr.current = &ast.Array{}
|
||||||
|
}
|
||||||
|
p.arr.current.Value = append(p.arr.current.Value, p.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetArray(begin, end int) {
|
||||||
|
p.arr.current.Position = ast.Position{Begin: begin, End: end}
|
||||||
|
p.arr.current.Data = p.buffer[begin:end]
|
||||||
|
p.val = p.arr.current
|
||||||
|
p.arr = p.arr.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetTable(buf []rune, begin, end int) {
|
||||||
|
p.setTable(p.table, buf, begin, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) setTable(t *ast.Table, buf []rune, begin, end int) {
|
||||||
|
name := string(buf[begin:end])
|
||||||
|
names := splitTableKey(name)
|
||||||
|
if t, exists := p.tableMap[name]; exists {
|
||||||
|
if lt := p.tableMap[names[len(names)-1]]; t.Type == ast.TableTypeArray || lt != nil && lt.Type == ast.TableTypeNormal {
|
||||||
|
p.Error(fmt.Errorf("table `%s' is in conflict with %v table in line %d", name, t.Type, t.Line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, err := p.lookupTable(t, names)
|
||||||
|
if err != nil {
|
||||||
|
p.Error(err)
|
||||||
|
}
|
||||||
|
p.currentTable = t
|
||||||
|
p.tableMap[name] = p.currentTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tomlParser) SetTableString(begin, end int) {
|
||||||
|
p.currentTable.Data = p.buffer[begin:end]
|
||||||
|
|
||||||
|
p.currentTable.Position.Begin = begin
|
||||||
|
p.currentTable.Position.End = end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetArrayTable(buf []rune, begin, end int) {
|
||||||
|
p.setArrayTable(p.table, buf, begin, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) setArrayTable(t *ast.Table, buf []rune, begin, end int) {
|
||||||
|
name := string(buf[begin:end])
|
||||||
|
if t, exists := p.tableMap[name]; exists && t.Type == ast.TableTypeNormal {
|
||||||
|
p.Error(fmt.Errorf("table `%s' is in conflict with %v table in line %d", name, t.Type, t.Line))
|
||||||
|
}
|
||||||
|
names := splitTableKey(name)
|
||||||
|
t, err := p.lookupTable(t, names[:len(names)-1])
|
||||||
|
if err != nil {
|
||||||
|
p.Error(err)
|
||||||
|
}
|
||||||
|
last := names[len(names)-1]
|
||||||
|
tbl := &ast.Table{
|
||||||
|
Position: ast.Position{begin, end},
|
||||||
|
Line: p.line,
|
||||||
|
Name: last,
|
||||||
|
Type: ast.TableTypeArray,
|
||||||
|
}
|
||||||
|
switch v := t.Fields[last].(type) {
|
||||||
|
case nil:
|
||||||
|
if t.Fields == nil {
|
||||||
|
t.Fields = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
t.Fields[last] = []*ast.Table{tbl}
|
||||||
|
case []*ast.Table:
|
||||||
|
t.Fields[last] = append(v, tbl)
|
||||||
|
case *ast.KeyValue:
|
||||||
|
p.Error(fmt.Errorf("key `%s' is in conflict with line %d", last, v.Line))
|
||||||
|
default:
|
||||||
|
p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", last, v))
|
||||||
|
}
|
||||||
|
p.currentTable = tbl
|
||||||
|
p.tableMap[name] = p.currentTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) StartInlineTable() {
|
||||||
|
p.skip = false
|
||||||
|
p.stack = append(p.stack, &stack{p.key, p.currentTable})
|
||||||
|
buf := []rune(p.key)
|
||||||
|
if p.arr == nil {
|
||||||
|
p.setTable(p.currentTable, buf, 0, len(buf))
|
||||||
|
} else {
|
||||||
|
p.setArrayTable(p.currentTable, buf, 0, len(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) EndInlineTable() {
|
||||||
|
st := p.stack[len(p.stack)-1]
|
||||||
|
p.key, p.currentTable = st.key, st.table
|
||||||
|
p.stack[len(p.stack)-1] = nil
|
||||||
|
p.stack = p.stack[:len(p.stack)-1]
|
||||||
|
p.skip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) AddLineCount(i int) {
|
||||||
|
p.line += i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetKey(buf []rune, begin, end int) {
|
||||||
|
p.key = string(buf[begin:end])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) AddKeyValue() {
|
||||||
|
if p.skip {
|
||||||
|
p.skip = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if val, exists := p.currentTable.Fields[p.key]; exists {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case *ast.Table:
|
||||||
|
p.Error(fmt.Errorf("key `%s' is in conflict with %v table in line %d", p.key, v.Type, v.Line))
|
||||||
|
case *ast.KeyValue:
|
||||||
|
p.Error(fmt.Errorf("key `%s' is in conflict with line %d", p.key, v.Line))
|
||||||
|
default:
|
||||||
|
p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", p.key, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.currentTable.Fields == nil {
|
||||||
|
p.currentTable.Fields = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
p.currentTable.Fields[p.key] = &ast.KeyValue{
|
||||||
|
Key: p.key,
|
||||||
|
Value: p.val,
|
||||||
|
Line: p.line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetBasicString(buf []rune, begin, end int) {
|
||||||
|
p.s = p.unquote(string(buf[begin:end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetMultilineString() {
|
||||||
|
p.s = p.unquote(`"` + escapeReplacer.Replace(strings.TrimLeft(p.s, "\r\n")) + `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) AddMultilineBasicBody(buf []rune, begin, end int) {
|
||||||
|
p.s += string(buf[begin:end])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetLiteralString(buf []rune, begin, end int) {
|
||||||
|
p.s = string(buf[begin:end])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) SetMultilineLiteralString(buf []rune, begin, end int) {
|
||||||
|
p.s = strings.TrimLeft(string(buf[begin:end]), "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) unquote(s string) string {
|
||||||
|
s, err := strconv.Unquote(s)
|
||||||
|
if err != nil {
|
||||||
|
p.Error(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) {
|
||||||
|
for _, s := range keys {
|
||||||
|
val, exists := t.Fields[s]
|
||||||
|
if !exists {
|
||||||
|
tbl := &ast.Table{
|
||||||
|
Line: p.line,
|
||||||
|
Name: s,
|
||||||
|
Type: ast.TableTypeNormal,
|
||||||
|
}
|
||||||
|
if t.Fields == nil {
|
||||||
|
t.Fields = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
t.Fields[s] = tbl
|
||||||
|
t = tbl
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch v := val.(type) {
|
||||||
|
case *ast.Table:
|
||||||
|
t = v
|
||||||
|
case []*ast.Table:
|
||||||
|
t = v[len(v)-1]
|
||||||
|
case *ast.KeyValue:
|
||||||
|
return nil, fmt.Errorf("key `%s' is in conflict with line %d", s, v.Line)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", s, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitTableKey(tk string) []string {
|
||||||
|
key := make([]byte, 0, 1)
|
||||||
|
keys := make([]string, 0, 1)
|
||||||
|
inQuote := false
|
||||||
|
for i := 0; i < len(tk); i++ {
|
||||||
|
k := tk[i]
|
||||||
|
switch {
|
||||||
|
case k == tableSeparator && !inQuote:
|
||||||
|
keys = append(keys, string(key))
|
||||||
|
key = key[:0] // reuse buffer.
|
||||||
|
case k == '"':
|
||||||
|
inQuote = !inQuote
|
||||||
|
case (k == ' ' || k == '\t') && !inQuote:
|
||||||
|
// skip.
|
||||||
|
default:
|
||||||
|
key = append(key, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys = append(keys, string(key))
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertError struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e convertError) Error() string {
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
type array struct {
|
||||||
|
parent *array
|
||||||
|
child *array
|
||||||
|
current *ast.Array
|
||||||
|
line int
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"github.com/naoina/go-stringutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tagOmitempty = "omitempty"
|
||||||
|
tagSkip = "-"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Marshal returns the TOML encoding of v.
|
||||||
|
//
|
||||||
|
// Struct values encode as TOML. Each exported struct field becomes a field of
|
||||||
|
// the TOML structure unless
|
||||||
|
// - the field's tag is "-", or
|
||||||
|
// - the field is empty and its tag specifies the "omitempty" option.
|
||||||
|
// The "toml" key in the struct field's tag value is the key name, followed by
|
||||||
|
// an optional comma and options. Examples:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field int `toml:"-"`
|
||||||
|
//
|
||||||
|
// // Field appears in TOML as key "myName".
|
||||||
|
// Field int `toml:"myName"`
|
||||||
|
//
|
||||||
|
// // Field appears in TOML as key "myName" and the field is omitted from the
|
||||||
|
// // result of encoding if its value is empty.
|
||||||
|
// Field int `toml:"myName,omitempty"`
|
||||||
|
//
|
||||||
|
// // Field appears in TOML as key "field", but the field is skipped if
|
||||||
|
// // empty.
|
||||||
|
// // Note the leading comma.
|
||||||
|
// Field int `toml:",omitempty"`
|
||||||
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return marshal(nil, "", reflect.ValueOf(v), false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Encoder writes TOML to an output stream.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the TOML of v to the stream.
|
||||||
|
// See the documentation for Marshal for details about the conversion of Go values to TOML.
|
||||||
|
func (e *Encoder) Encode(v interface{}) error {
|
||||||
|
b, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = e.w.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshaler is the interface implemented by objects that can marshal themselves into valid TOML.
|
||||||
|
type Marshaler interface {
|
||||||
|
MarshalTOML() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshal(buf []byte, prefix string, rv reflect.Value, inArray, arrayTable bool) ([]byte, error) {
|
||||||
|
rt := rv.Type()
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
ft := rt.Field(i)
|
||||||
|
if !ast.IsExported(ft.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
colName, rest := extractTag(rt.Field(i).Tag.Get(fieldTagName))
|
||||||
|
if colName == tagSkip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if colName == "" {
|
||||||
|
colName = stringutil.ToSnakeCase(ft.Name)
|
||||||
|
}
|
||||||
|
fv := rv.Field(i)
|
||||||
|
switch rest {
|
||||||
|
case tagOmitempty:
|
||||||
|
if fv.Interface() == reflect.Zero(ft.Type).Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if buf, err = encodeValue(buf, prefix, colName, fv, inArray, arrayTable); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeValue(buf []byte, prefix, name string, fv reflect.Value, inArray, arrayTable bool) ([]byte, error) {
|
||||||
|
switch t := fv.Interface().(type) {
|
||||||
|
case Marshaler:
|
||||||
|
b, err := t.MarshalTOML()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return appendNewline(append(appendKey(buf, name, inArray, arrayTable), b...), inArray, arrayTable), nil
|
||||||
|
case time.Time:
|
||||||
|
return appendNewline(encodeTime(appendKey(buf, name, inArray, arrayTable), t), inArray, arrayTable), nil
|
||||||
|
}
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return appendNewline(encodeInt(appendKey(buf, name, inArray, arrayTable), fv.Int()), inArray, arrayTable), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return appendNewline(encodeUint(appendKey(buf, name, inArray, arrayTable), fv.Uint()), inArray, arrayTable), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return appendNewline(encodeFloat(appendKey(buf, name, inArray, arrayTable), fv.Float()), inArray, arrayTable), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
return appendNewline(encodeBool(appendKey(buf, name, inArray, arrayTable), fv.Bool()), inArray, arrayTable), nil
|
||||||
|
case reflect.String:
|
||||||
|
return appendNewline(encodeString(appendKey(buf, name, inArray, arrayTable), fv.String()), inArray, arrayTable), nil
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
ft := fv.Type().Elem()
|
||||||
|
for ft.Kind() == reflect.Ptr {
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
if ft.Kind() == reflect.Struct {
|
||||||
|
name := tableName(prefix, name)
|
||||||
|
var err error
|
||||||
|
for i := 0; i < fv.Len(); i++ {
|
||||||
|
if buf, err = marshal(append(append(append(buf, '[', '['), name...), ']', ']', '\n'), name, fv.Index(i), false, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
buf = append(appendKey(buf, name, inArray, arrayTable), '[')
|
||||||
|
var err error
|
||||||
|
for i := 0; i < fv.Len(); i++ {
|
||||||
|
if i != 0 {
|
||||||
|
buf = append(buf, ',')
|
||||||
|
}
|
||||||
|
if buf, err = encodeValue(buf, prefix, name, fv.Index(i), true, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return appendNewline(append(buf, ']'), inArray, arrayTable), nil
|
||||||
|
case reflect.Struct:
|
||||||
|
name := tableName(prefix, name)
|
||||||
|
return marshal(append(append(append(buf, '['), name...), ']', '\n'), name, fv, inArray, arrayTable)
|
||||||
|
case reflect.Interface:
|
||||||
|
var err error
|
||||||
|
if buf, err = encodeInterface(appendKey(buf, name, inArray, arrayTable), fv.Interface()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return appendNewline(buf, inArray, arrayTable), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("toml: marshal: unsupported type %v", fv.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendKey(buf []byte, key string, inArray, arrayTable bool) []byte {
|
||||||
|
if !inArray {
|
||||||
|
return append(append(buf, key...), '=')
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendNewline(buf []byte, inArray, arrayTable bool) []byte {
|
||||||
|
if !inArray {
|
||||||
|
return append(buf, '\n')
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeInterface(buf []byte, v interface{}) ([]byte, error) {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case int:
|
||||||
|
return encodeInt(buf, int64(v)), nil
|
||||||
|
case int8:
|
||||||
|
return encodeInt(buf, int64(v)), nil
|
||||||
|
case int16:
|
||||||
|
return encodeInt(buf, int64(v)), nil
|
||||||
|
case int32:
|
||||||
|
return encodeInt(buf, int64(v)), nil
|
||||||
|
case int64:
|
||||||
|
return encodeInt(buf, v), nil
|
||||||
|
case uint:
|
||||||
|
return encodeUint(buf, uint64(v)), nil
|
||||||
|
case uint8:
|
||||||
|
return encodeUint(buf, uint64(v)), nil
|
||||||
|
case uint16:
|
||||||
|
return encodeUint(buf, uint64(v)), nil
|
||||||
|
case uint32:
|
||||||
|
return encodeUint(buf, uint64(v)), nil
|
||||||
|
case uint64:
|
||||||
|
return encodeUint(buf, v), nil
|
||||||
|
case float32:
|
||||||
|
return encodeFloat(buf, float64(v)), nil
|
||||||
|
case float64:
|
||||||
|
return encodeFloat(buf, v), nil
|
||||||
|
case bool:
|
||||||
|
return encodeBool(buf, v), nil
|
||||||
|
case string:
|
||||||
|
return encodeString(buf, v), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("toml: marshal: unable to detect a type of value `%v'", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeInt(buf []byte, i int64) []byte {
|
||||||
|
return strconv.AppendInt(buf, i, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeUint(buf []byte, u uint64) []byte {
|
||||||
|
return strconv.AppendUint(buf, u, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeFloat(buf []byte, f float64) []byte {
|
||||||
|
return strconv.AppendFloat(buf, f, 'e', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBool(buf []byte, b bool) []byte {
|
||||||
|
return strconv.AppendBool(buf, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeString(buf []byte, s string) []byte {
|
||||||
|
return strconv.AppendQuote(buf, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeTime(buf []byte, t time.Time) []byte {
|
||||||
|
return append(buf, t.Format(time.RFC3339Nano)...)
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *parseError) Line() int {
|
||||||
|
tokens := e.p.tokenTree.Error()
|
||||||
|
positions := make([]int, len(tokens)*2)
|
||||||
|
p := 0
|
||||||
|
for _, token := range tokens {
|
||||||
|
positions[p], p = int(token.begin), p+1
|
||||||
|
positions[p], p = int(token.end), p+1
|
||||||
|
}
|
||||||
|
for _, t := range translatePositions(e.p.Buffer, positions) {
|
||||||
|
if e.p.line < t.line {
|
||||||
|
e.p.line = t.line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.p.line
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorOutOfRange struct {
|
||||||
|
kind reflect.Kind
|
||||||
|
v interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *errorOutOfRange) Error() string {
|
||||||
|
return fmt.Sprintf("value %d is out of range for `%v` type", err.v, err.kind)
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/naoina/toml/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse returns an AST representation of TOML.
|
||||||
|
// The toplevel is represented by a table.
|
||||||
|
func Parse(data []byte) (*ast.Table, error) {
|
||||||
|
d := &parseState{p: &tomlParser{Buffer: string(data)}}
|
||||||
|
d.init()
|
||||||
|
|
||||||
|
if err := d.parse(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.p.toml.table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type parseState struct {
|
||||||
|
p *tomlParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *parseState) init() {
|
||||||
|
d.p.Init()
|
||||||
|
d.p.toml.init(d.p.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *parseState) parse() error {
|
||||||
|
if err := d.p.Parse(); err != nil {
|
||||||
|
if err, ok := err.(*parseError); ok {
|
||||||
|
return fmt.Errorf("toml: line %d: parse error", err.Line())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return d.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *parseState) execute() (err error) {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
cerr, ok := e.(convertError)
|
||||||
|
if !ok {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
err = cerr.err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
d.p.Execute()
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
type tomlParser Peg {
|
||||||
|
toml
|
||||||
|
}
|
||||||
|
|
||||||
|
TOML <- Expression (newline Expression)* newline? !. { _ = buffer }
|
||||||
|
|
||||||
|
Expression <- (
|
||||||
|
<ws table ws comment? (wsnl keyval ws comment?)*> { p.SetTableString(begin, end) }
|
||||||
|
/ ws keyval ws comment?
|
||||||
|
/ ws comment?
|
||||||
|
/ ws
|
||||||
|
)
|
||||||
|
|
||||||
|
newline <- <[\r\n]+> { p.AddLineCount(end - begin) }
|
||||||
|
|
||||||
|
ws <- [ \t]*
|
||||||
|
wsnl <- (
|
||||||
|
[ \t]
|
||||||
|
/ <[\r\n]> { p.AddLineCount(end - begin) }
|
||||||
|
)*
|
||||||
|
|
||||||
|
comment <- '#' <[\t -\0x10FFFF]*>
|
||||||
|
|
||||||
|
keyval <- key ws '=' ws val { p.AddKeyValue() }
|
||||||
|
|
||||||
|
key <- bareKey / quotedKey
|
||||||
|
|
||||||
|
bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
quotedKey <- '"' <basicChar+> '"' { p.SetKey(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
val <- (
|
||||||
|
<datetime> { p.SetTime(begin, end) }
|
||||||
|
/ <float> { p.SetFloat64(begin, end) }
|
||||||
|
/ <integer> { p.SetInt64(begin, end) }
|
||||||
|
/ <string> { p.SetString(begin, end) }
|
||||||
|
/ <boolean> { p.SetBool(begin, end) }
|
||||||
|
/ <array> { p.SetArray(begin, end) }
|
||||||
|
/ inlineTable
|
||||||
|
)
|
||||||
|
|
||||||
|
table <- stdTable / arrayTable
|
||||||
|
|
||||||
|
stdTable <- '[' ws <tableKey> ws ']' { p.SetTable(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
arrayTable <- '[[' ws <tableKey> ws ']]' { p.SetArrayTable(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
inlineTable <- (
|
||||||
|
'{' { p.StartInlineTable() }
|
||||||
|
ws inlineTableKeyValues ws
|
||||||
|
'}' { p.EndInlineTable() }
|
||||||
|
)
|
||||||
|
|
||||||
|
inlineTableKeyValues <- (keyval inlineTableValSep?)*
|
||||||
|
|
||||||
|
tableKey <- key (tableKeySep key)*
|
||||||
|
|
||||||
|
tableKeySep <- ws '.' ws
|
||||||
|
|
||||||
|
inlineTableValSep <- ws ',' ws
|
||||||
|
|
||||||
|
integer <- [\-+]? int
|
||||||
|
int <- [1-9] (digit / '_' digit)+ / digit
|
||||||
|
|
||||||
|
float <- integer (frac exp? / frac? exp)
|
||||||
|
frac <- '.' digit (digit / '_' digit)*
|
||||||
|
exp <- [eE] [\-+]? digit (digit / '_' digit)*
|
||||||
|
|
||||||
|
string <- (
|
||||||
|
mlLiteralString
|
||||||
|
/ literalString
|
||||||
|
/ mlBasicString
|
||||||
|
/ basicString
|
||||||
|
)
|
||||||
|
|
||||||
|
basicString <- <'"' basicChar* '"'> { p.SetBasicString(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
basicChar <- basicUnescaped / escaped
|
||||||
|
escaped <- escape ([btnfr"/\\] / 'u' hexQuad / 'U' hexQuad hexQuad)
|
||||||
|
|
||||||
|
basicUnescaped <- [ -!#-\[\]-\0x10FFFF]
|
||||||
|
|
||||||
|
escape <- '\\'
|
||||||
|
|
||||||
|
mlBasicString <- '"""' mlBasicBody '"""' { p.SetMultilineString() }
|
||||||
|
|
||||||
|
mlBasicBody <- (
|
||||||
|
<basicChar / newline> { p.AddMultilineBasicBody(p.buffer, begin, end) }
|
||||||
|
/ escape newline wsnl
|
||||||
|
)*
|
||||||
|
|
||||||
|
literalString <- "'" <literalChar*> "'" { p.SetLiteralString(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
literalChar <- [\t -&(-\0x10FFFF]
|
||||||
|
|
||||||
|
mlLiteralString <- "'''" <mlLiteralBody> "'''" { p.SetMultilineLiteralString(p.buffer, begin, end) }
|
||||||
|
|
||||||
|
mlLiteralBody <- (!"'''" (mlLiteralChar / newline))*
|
||||||
|
|
||||||
|
mlLiteralChar <- [\t -\0x10FFFF]
|
||||||
|
|
||||||
|
hexdigit <- [0-9A-Fa-f]
|
||||||
|
hexQuad <- hexdigit hexdigit hexdigit hexdigit
|
||||||
|
|
||||||
|
boolean <- 'true' / 'false'
|
||||||
|
|
||||||
|
dateFullYear <- digitQuad
|
||||||
|
dateMonth <- digitDual
|
||||||
|
dateMDay <- digitDual
|
||||||
|
timeHour <- digitDual
|
||||||
|
timeMinute <- digitDual
|
||||||
|
timeSecond <- digitDual
|
||||||
|
timeSecfrac <- '.' digit+
|
||||||
|
timeNumoffset <- [\-+] timeHour ':' timeMinute
|
||||||
|
timeOffset <- 'Z' / timeNumoffset
|
||||||
|
partialTime <- timeHour ':' timeMinute ':' timeSecond timeSecfrac?
|
||||||
|
fullDate <- dateFullYear '-' dateMonth '-' dateMDay
|
||||||
|
fullTime <- partialTime timeOffset
|
||||||
|
datetime <- fullDate 'T' fullTime
|
||||||
|
|
||||||
|
digit <- [0-9]
|
||||||
|
digitDual <- digit digit
|
||||||
|
digitQuad <- digitDual digitDual
|
||||||
|
|
||||||
|
array <- (
|
||||||
|
'[' { p.StartArray() }
|
||||||
|
wsnl arrayValues wsnl
|
||||||
|
']'
|
||||||
|
)
|
||||||
|
|
||||||
|
arrayValues <- (
|
||||||
|
val { p.AddArrayVal() }
|
||||||
|
arraySep? (comment? newline)?
|
||||||
|
)*
|
||||||
|
|
||||||
|
arraySep <- ws ',' wsnl
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,79 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// toCamelCase returns a copy of the string s with all Unicode letters mapped to their camel case.
|
||||||
|
// It will convert to upper case previous letter of '_' and first letter, and remove letter of '_'.
|
||||||
|
func toCamelCase(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := make([]rune, 0, len(s))
|
||||||
|
upper := false
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '_' {
|
||||||
|
upper = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if upper {
|
||||||
|
result = append(result, unicode.ToUpper(r))
|
||||||
|
upper = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
|
result[0] = unicode.ToUpper(result[0])
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
fieldTagName = "toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findField(rv reflect.Value, name string) (field reflect.Value, fieldName string, found bool) {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
rt := rv.Type()
|
||||||
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
|
ft := rt.Field(i)
|
||||||
|
if !ast.IsExported(ft.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if col, _ := extractTag(ft.Tag.Get(fieldTagName)); col == name {
|
||||||
|
return rv.Field(i), ft.Name, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, name := range []string{
|
||||||
|
strings.Title(name),
|
||||||
|
toCamelCase(name),
|
||||||
|
strings.ToUpper(name),
|
||||||
|
} {
|
||||||
|
if field := rv.FieldByName(name); field.IsValid() {
|
||||||
|
return field, name, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
return reflect.New(rv.Type().Elem()).Elem(), name, true
|
||||||
|
}
|
||||||
|
return field, "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTag(tag string) (col, rest string) {
|
||||||
|
tags := strings.SplitN(tag, ",", 2)
|
||||||
|
if len(tags) == 2 {
|
||||||
|
return strings.TrimSpace(tags[0]), strings.TrimSpace(tags[1])
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(tags[0]), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableName(prefix, name string) string {
|
||||||
|
if prefix != "" {
|
||||||
|
return prefix + string(tableSeparator) + name
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
Loading…
Reference in new issue