mirror of https://github.com/ledisdb/ledisdb.git
Dep for dependency management, newer go in CI, dependency updates (#325)
* Use Dep instead of Glide for managing dependencies * Run tests on the latest two versions of go * Use older goleveldb * Don't test vendor (default in go 1.9+) * Drop Go 1.6 from tests - it's old and I really don't want to deal with it. * Stop testing with Go 1.7
This commit is contained in:
parent
015411f5ce
commit
2ff56553d9
|
@ -1,7 +1,7 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.6
|
- 1.8
|
||||||
- 1.7
|
- 1.9
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make test
|
- make test
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/cupcake/rdb"
|
||||||
|
packages = [".","crc64","nopdecoder"]
|
||||||
|
revision = "43ba34106c765f2111c0dc7b74cdf8ee437411e0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/edsrzf/mmap-go"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/glendc/gopher-json"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "dc4743023d0c166c1b844da8fc688e57ec65fe0b"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/golang/snappy"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pelletier/go-toml"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "16398bac157da96aa88f98a2df640c7f32af1da2"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/peterh/liner"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3681c2a912330352991ecdd642f257efe5b85518"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/siddontang/go"
|
||||||
|
packages = ["bson","filelock","hack","ioutil2","log","num","snappy","sync2"]
|
||||||
|
revision = "cb568a3e5cc06256f91a2da5a87455f717eb33f4"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/siddontang/goredis"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "760763f78400635ed7b9b115511b8ed06035e908"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/siddontang/rdb"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "fc89ed2e418d27e3ea76e708e54276d2b44ae9cf"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/syndtr/goleveldb"
|
||||||
|
packages = ["leveldb","leveldb/cache","leveldb/comparer","leveldb/errors","leveldb/filter","leveldb/iterator","leveldb/journal","leveldb/memdb","leveldb/opt","leveldb/storage","leveldb/table","leveldb/util"]
|
||||||
|
revision = "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/ugorji/go"
|
||||||
|
packages = ["codec"]
|
||||||
|
revision = "84cb69a8af8316eed8cf4a3c9368a56977850062"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/yuin/gopher-lua"
|
||||||
|
packages = [".","ast","parse","pm"]
|
||||||
|
revision = "609c9cd2697344dec90fe0543c6493e3b8da3435"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
packages = ["context"]
|
||||||
|
revision = "fb018015d54fd2e3bfd5362a041991d350fde9d7"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "f11307c5e37b6809e5ced4d0ed85e2c52d7d6ee04c098c3644518596b62c7280"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/edsrzf/mmap-go"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/glendc/gopher-json"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/pelletier/go-toml"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/peterh/liner"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/siddontang/go"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/siddontang/goredis"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/siddontang/rdb"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/syndtr/goleveldb"
|
||||||
|
revision = "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/ugorji/go"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/yuin/gopher-lua"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "golang.org/x/net"
|
25
Makefile
25
Makefile
|
@ -14,19 +14,15 @@ export GO_BUILD_TAGS
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
rm -rf vendor && ln -s _vendor/vendor vendor
|
|
||||||
go build -o bin/ledis-server -tags '$(GO_BUILD_TAGS)' cmd/ledis-server/*
|
go build -o bin/ledis-server -tags '$(GO_BUILD_TAGS)' cmd/ledis-server/*
|
||||||
go build -o bin/ledis-cli -tags '$(GO_BUILD_TAGS)' cmd/ledis-cli/*
|
go build -o bin/ledis-cli -tags '$(GO_BUILD_TAGS)' cmd/ledis-cli/*
|
||||||
go build -o bin/ledis-benchmark -tags '$(GO_BUILD_TAGS)' cmd/ledis-benchmark/*
|
go build -o bin/ledis-benchmark -tags '$(GO_BUILD_TAGS)' cmd/ledis-benchmark/*
|
||||||
go build -o bin/ledis-dump -tags '$(GO_BUILD_TAGS)' cmd/ledis-dump/*
|
go build -o bin/ledis-dump -tags '$(GO_BUILD_TAGS)' cmd/ledis-dump/*
|
||||||
go build -o bin/ledis-load -tags '$(GO_BUILD_TAGS)' cmd/ledis-load/*
|
go build -o bin/ledis-load -tags '$(GO_BUILD_TAGS)' cmd/ledis-load/*
|
||||||
go build -o bin/ledis-repair -tags '$(GO_BUILD_TAGS)' cmd/ledis-repair/*
|
go build -o bin/ledis-repair -tags '$(GO_BUILD_TAGS)' cmd/ledis-repair/*
|
||||||
rm -rf vendor
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
rm -rf vendor && ln -s _vendor/vendor vendor
|
go test --race -tags '$(GO_BUILD_TAGS)' -timeout 2m $$(go list ./... | grep -v -e /vendor/)
|
||||||
go test --race -tags '$(GO_BUILD_TAGS)' -timeout 2m ./...
|
|
||||||
rm -rf vendor
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -35,17 +31,10 @@ clean:
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -w -s . 2>&1 | grep -vE 'vendor' | awk '{print} END{if(NR>0) {exit 1}}'
|
gofmt -w -s . 2>&1 | grep -vE 'vendor' | awk '{print} END{if(NR>0) {exit 1}}'
|
||||||
|
|
||||||
|
sync_vendor:
|
||||||
|
@which dep >/dev/null || go get -u github.com/golang/dep/cmd/dep
|
||||||
|
dep ensure && dep prune
|
||||||
|
|
||||||
update_vendor:
|
update_vendor:
|
||||||
which glide >/dev/null || curl https://glide.sh/get | sh
|
@which dep >/dev/null || go get -u github.com/golang/dep/cmd/dep
|
||||||
which glide-vc || go get -v -u github.com/sgotti/glide-vc
|
dep ensure -update && dep prune
|
||||||
rm -r vendor && mv _vendor/vendor vendor || true
|
|
||||||
rm -rf _vendor
|
|
||||||
ifdef PKG
|
|
||||||
glide get --strip-vendor --skip-test ${PKG}
|
|
||||||
else
|
|
||||||
glide update --strip-vendor --skip-test
|
|
||||||
endif
|
|
||||||
@echo "removing test files"
|
|
||||||
glide vc --only-code --no-tests
|
|
||||||
mkdir -p _vendor
|
|
||||||
mv vendor _vendor/vendor
|
|
|
@ -1,117 +0,0 @@
|
||||||
// Package buffruneio is a wrapper around bufio to provide buffered runes access with unlimited unreads.
|
|
||||||
package buffruneio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"container/list"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rune to indicate end of file.
|
|
||||||
const (
|
|
||||||
EOF = -(iota + 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNoRuneToUnread is returned by UnreadRune() when the read index is already at the beginning of the buffer.
|
|
||||||
var ErrNoRuneToUnread = errors.New("no rune to unwind")
|
|
||||||
|
|
||||||
// Reader implements runes buffering for an io.Reader object.
|
|
||||||
type Reader struct {
|
|
||||||
buffer *list.List
|
|
||||||
current *list.Element
|
|
||||||
input *bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a new Reader.
|
|
||||||
func NewReader(rd io.Reader) *Reader {
|
|
||||||
return &Reader{
|
|
||||||
buffer: list.New(),
|
|
||||||
input: bufio.NewReader(rd),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type runeWithSize struct {
|
|
||||||
r rune
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rd *Reader) feedBuffer() error {
|
|
||||||
r, size, err := rd.input.ReadRune()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r = EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
newRuneWithSize := runeWithSize{r, size}
|
|
||||||
|
|
||||||
rd.buffer.PushBack(newRuneWithSize)
|
|
||||||
if rd.current == nil {
|
|
||||||
rd.current = rd.buffer.Back()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadRune reads the next rune from buffer, or from the underlying reader if needed.
|
|
||||||
func (rd *Reader) ReadRune() (rune, int, error) {
|
|
||||||
if rd.current == rd.buffer.Back() || rd.current == nil {
|
|
||||||
err := rd.feedBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return EOF, 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runeWithSize := rd.current.Value.(runeWithSize)
|
|
||||||
rd.current = rd.current.Next()
|
|
||||||
return runeWithSize.r, runeWithSize.size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnreadRune pushes back the previously read rune in the buffer, extending it if needed.
|
|
||||||
func (rd *Reader) UnreadRune() error {
|
|
||||||
if rd.current == rd.buffer.Front() {
|
|
||||||
return ErrNoRuneToUnread
|
|
||||||
}
|
|
||||||
if rd.current == nil {
|
|
||||||
rd.current = rd.buffer.Back()
|
|
||||||
} else {
|
|
||||||
rd.current = rd.current.Prev()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget removes runes stored before the current stream position index.
|
|
||||||
func (rd *Reader) Forget() {
|
|
||||||
if rd.current == nil {
|
|
||||||
rd.current = rd.buffer.Back()
|
|
||||||
}
|
|
||||||
for ; rd.current != rd.buffer.Front(); rd.buffer.Remove(rd.current.Prev()) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeekRune returns at most the next n runes, reading from the uderlying source if
|
|
||||||
// needed. Does not move the current index. It includes EOF if reached.
|
|
||||||
func (rd *Reader) PeekRunes(n int) []rune {
|
|
||||||
res := make([]rune, 0, n)
|
|
||||||
cursor := rd.current
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
if cursor == nil {
|
|
||||||
err := rd.feedBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
cursor = rd.buffer.Back()
|
|
||||||
}
|
|
||||||
if cursor != nil {
|
|
||||||
r := cursor.Value.(runeWithSize).r
|
|
||||||
res = append(res, r)
|
|
||||||
if r == EOF {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
cursor = cursor.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,175 +0,0 @@
|
||||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
||||||
|
|
||||||
package codec
|
|
||||||
|
|
||||||
// DO NOT EDIT. THIS FILE IS AUTO-GENERATED FROM gen-dec-(map|array).go.tmpl
|
|
||||||
|
|
||||||
const genDecMapTmpl = `
|
|
||||||
{{var "v"}} := *{{ .Varname }}
|
|
||||||
{{var "l"}} := r.ReadMapStart()
|
|
||||||
{{var "bh"}} := z.DecBasicHandle()
|
|
||||||
if {{var "v"}} == nil {
|
|
||||||
{{var "rl"}}, _ := z.DecInferLen({{var "l"}}, {{var "bh"}}.MaxInitLen, {{ .Size }})
|
|
||||||
{{var "v"}} = make(map[{{ .KTyp }}]{{ .Typ }}, {{var "rl"}})
|
|
||||||
*{{ .Varname }} = {{var "v"}}
|
|
||||||
}
|
|
||||||
var {{var "mk"}} {{ .KTyp }}
|
|
||||||
var {{var "mv"}} {{ .Typ }}
|
|
||||||
var {{var "mg"}} {{if decElemKindPtr}}, {{var "ms"}}, {{var "mok"}}{{end}} bool
|
|
||||||
if {{var "bh"}}.MapValueReset {
|
|
||||||
{{if decElemKindPtr}}{{var "mg"}} = true
|
|
||||||
{{else if decElemKindIntf}}if !{{var "bh"}}.InterfaceReset { {{var "mg"}} = true }
|
|
||||||
{{else if not decElemKindImmutable}}{{var "mg"}} = true
|
|
||||||
{{end}} }
|
|
||||||
if {{var "l"}} > 0 {
|
|
||||||
for {{var "j"}} := 0; {{var "j"}} < {{var "l"}}; {{var "j"}}++ {
|
|
||||||
z.DecSendContainerState(codecSelfer_containerMapKey{{ .Sfx }})
|
|
||||||
{{ $x := printf "%vmk%v" .TempVar .Rand }}{{ decLineVarK $x }}
|
|
||||||
{{ if eq .KTyp "interface{}" }}{{/* // special case if a byte array. */}}if {{var "bv"}}, {{var "bok"}} := {{var "mk"}}.([]byte); {{var "bok"}} {
|
|
||||||
{{var "mk"}} = string({{var "bv"}})
|
|
||||||
}{{ end }}{{if decElemKindPtr}}
|
|
||||||
{{var "ms"}} = true{{end}}
|
|
||||||
if {{var "mg"}} {
|
|
||||||
{{if decElemKindPtr}}{{var "mv"}}, {{var "mok"}} = {{var "v"}}[{{var "mk"}}]
|
|
||||||
if {{var "mok"}} {
|
|
||||||
{{var "ms"}} = false
|
|
||||||
} {{else}}{{var "mv"}} = {{var "v"}}[{{var "mk"}}] {{end}}
|
|
||||||
} {{if not decElemKindImmutable}}else { {{var "mv"}} = {{decElemZero}} }{{end}}
|
|
||||||
z.DecSendContainerState(codecSelfer_containerMapValue{{ .Sfx }})
|
|
||||||
{{ $x := printf "%vmv%v" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
if {{if decElemKindPtr}} {{var "ms"}} && {{end}} {{var "v"}} != nil {
|
|
||||||
{{var "v"}}[{{var "mk"}}] = {{var "mv"}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if {{var "l"}} < 0 {
|
|
||||||
for {{var "j"}} := 0; !r.CheckBreak(); {{var "j"}}++ {
|
|
||||||
z.DecSendContainerState(codecSelfer_containerMapKey{{ .Sfx }})
|
|
||||||
{{ $x := printf "%vmk%v" .TempVar .Rand }}{{ decLineVarK $x }}
|
|
||||||
{{ if eq .KTyp "interface{}" }}{{/* // special case if a byte array. */}}if {{var "bv"}}, {{var "bok"}} := {{var "mk"}}.([]byte); {{var "bok"}} {
|
|
||||||
{{var "mk"}} = string({{var "bv"}})
|
|
||||||
}{{ end }}{{if decElemKindPtr}}
|
|
||||||
{{var "ms"}} = true {{ end }}
|
|
||||||
if {{var "mg"}} {
|
|
||||||
{{if decElemKindPtr}}{{var "mv"}}, {{var "mok"}} = {{var "v"}}[{{var "mk"}}]
|
|
||||||
if {{var "mok"}} {
|
|
||||||
{{var "ms"}} = false
|
|
||||||
} {{else}}{{var "mv"}} = {{var "v"}}[{{var "mk"}}] {{end}}
|
|
||||||
} {{if not decElemKindImmutable}}else { {{var "mv"}} = {{decElemZero}} }{{end}}
|
|
||||||
z.DecSendContainerState(codecSelfer_containerMapValue{{ .Sfx }})
|
|
||||||
{{ $x := printf "%vmv%v" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
if {{if decElemKindPtr}} {{var "ms"}} && {{end}} {{var "v"}} != nil {
|
|
||||||
{{var "v"}}[{{var "mk"}}] = {{var "mv"}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // else len==0: TODO: Should we clear map entries?
|
|
||||||
z.DecSendContainerState(codecSelfer_containerMapEnd{{ .Sfx }})
|
|
||||||
`
|
|
||||||
|
|
||||||
const genDecListTmpl = `
|
|
||||||
{{var "v"}} := {{if not isArray}}*{{end}}{{ .Varname }}
|
|
||||||
{{var "h"}}, {{var "l"}} := z.DecSliceHelperStart() {{/* // helper, containerLenS */}}{{if not isArray}}
|
|
||||||
var {{var "c"}} bool {{/* // changed */}}
|
|
||||||
_ = {{var "c"}}{{end}}
|
|
||||||
if {{var "l"}} == 0 {
|
|
||||||
{{if isSlice }}if {{var "v"}} == nil {
|
|
||||||
{{var "v"}} = []{{ .Typ }}{}
|
|
||||||
{{var "c"}} = true
|
|
||||||
} else if len({{var "v"}}) != 0 {
|
|
||||||
{{var "v"}} = {{var "v"}}[:0]
|
|
||||||
{{var "c"}} = true
|
|
||||||
} {{end}} {{if isChan }}if {{var "v"}} == nil {
|
|
||||||
{{var "v"}} = make({{ .CTyp }}, 0)
|
|
||||||
{{var "c"}} = true
|
|
||||||
} {{end}}
|
|
||||||
} else if {{var "l"}} > 0 {
|
|
||||||
{{if isChan }}if {{var "v"}} == nil {
|
|
||||||
{{var "rl"}}, _ = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }})
|
|
||||||
{{var "v"}} = make({{ .CTyp }}, {{var "rl"}})
|
|
||||||
{{var "c"}} = true
|
|
||||||
}
|
|
||||||
for {{var "r"}} := 0; {{var "r"}} < {{var "l"}}; {{var "r"}}++ {
|
|
||||||
{{var "h"}}.ElemContainerState({{var "r"}})
|
|
||||||
var {{var "t"}} {{ .Typ }}
|
|
||||||
{{ $x := printf "%st%s" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
{{var "v"}} <- {{var "t"}}
|
|
||||||
}
|
|
||||||
{{ else }} var {{var "rr"}}, {{var "rl"}} int {{/* // num2read, length of slice/array/chan */}}
|
|
||||||
var {{var "rt"}} bool {{/* truncated */}}
|
|
||||||
_, _ = {{var "rl"}}, {{var "rt"}}
|
|
||||||
{{var "rr"}} = {{var "l"}} // len({{var "v"}})
|
|
||||||
if {{var "l"}} > cap({{var "v"}}) {
|
|
||||||
{{if isArray }}z.DecArrayCannotExpand(len({{var "v"}}), {{var "l"}})
|
|
||||||
{{ else }}{{if not .Immutable }}
|
|
||||||
{{var "rg"}} := len({{var "v"}}) > 0
|
|
||||||
{{var "v2"}} := {{var "v"}} {{end}}
|
|
||||||
{{var "rl"}}, {{var "rt"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }})
|
|
||||||
if {{var "rt"}} {
|
|
||||||
if {{var "rl"}} <= cap({{var "v"}}) {
|
|
||||||
{{var "v"}} = {{var "v"}}[:{{var "rl"}}]
|
|
||||||
} else {
|
|
||||||
{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}})
|
|
||||||
}
|
|
||||||
{{var "c"}} = true
|
|
||||||
{{var "rr"}} = len({{var "v"}}) {{if not .Immutable }}
|
|
||||||
if {{var "rg"}} { copy({{var "v"}}, {{var "v2"}}) } {{end}} {{end}}{{/* end not Immutable, isArray */}}
|
|
||||||
} {{if isSlice }} else if {{var "l"}} != len({{var "v"}}) {
|
|
||||||
{{var "v"}} = {{var "v"}}[:{{var "l"}}]
|
|
||||||
{{var "c"}} = true
|
|
||||||
} {{end}} {{/* end isSlice:47 */}}
|
|
||||||
{{var "j"}} := 0
|
|
||||||
for ; {{var "j"}} < {{var "rr"}} ; {{var "j"}}++ {
|
|
||||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
|
||||||
{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
}
|
|
||||||
{{if isArray }}for ; {{var "j"}} < {{var "l"}} ; {{var "j"}}++ {
|
|
||||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
|
||||||
z.DecSwallow()
|
|
||||||
}
|
|
||||||
{{ else }}if {{var "rt"}} {
|
|
||||||
for ; {{var "j"}} < {{var "l"}} ; {{var "j"}}++ {
|
|
||||||
{{var "v"}} = append({{var "v"}}, {{ zero}})
|
|
||||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
|
||||||
{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
}
|
|
||||||
} {{end}} {{/* end isArray:56 */}}
|
|
||||||
{{end}} {{/* end isChan:16 */}}
|
|
||||||
} else { {{/* len < 0 */}}
|
|
||||||
{{var "j"}} := 0
|
|
||||||
for ; !r.CheckBreak(); {{var "j"}}++ {
|
|
||||||
{{if isChan }}
|
|
||||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
|
||||||
var {{var "t"}} {{ .Typ }}
|
|
||||||
{{ $x := printf "%st%s" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
{{var "v"}} <- {{var "t"}}
|
|
||||||
{{ else }}
|
|
||||||
if {{var "j"}} >= len({{var "v"}}) {
|
|
||||||
{{if isArray }}z.DecArrayCannotExpand(len({{var "v"}}), {{var "j"}}+1)
|
|
||||||
{{ else }}{{var "v"}} = append({{var "v"}}, {{zero}})// var {{var "z"}} {{ .Typ }}
|
|
||||||
{{var "c"}} = true {{end}}
|
|
||||||
}
|
|
||||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
|
||||||
if {{var "j"}} < len({{var "v"}}) {
|
|
||||||
{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }}
|
|
||||||
} else {
|
|
||||||
z.DecSwallow()
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
{{if isSlice }}if {{var "j"}} < len({{var "v"}}) {
|
|
||||||
{{var "v"}} = {{var "v"}}[:{{var "j"}}]
|
|
||||||
{{var "c"}} = true
|
|
||||||
} else if {{var "j"}} == 0 && {{var "v"}} == nil {
|
|
||||||
{{var "v"}} = []{{ .Typ }}{}
|
|
||||||
{{var "c"}} = true
|
|
||||||
}{{end}}
|
|
||||||
}
|
|
||||||
{{var "h"}}.End()
|
|
||||||
{{if not isArray }}if {{var "c"}} {
|
|
||||||
*{{ .Varname }} = {{var "v"}}
|
|
||||||
}{{end}}
|
|
||||||
`
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
//+build !unsafe
|
|
||||||
|
|
||||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
||||||
|
|
||||||
package codec
|
|
||||||
|
|
||||||
// stringView returns a view of the []byte as a string.
|
|
||||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
|
||||||
// In regular safe mode, it is an allocation and copy.
|
|
||||||
func stringView(v []byte) string {
|
|
||||||
return string(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesView returns a view of the string as a []byte.
|
|
||||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
|
||||||
// In regular safe mode, it is an allocation and copy.
|
|
||||||
func bytesView(v string) []byte {
|
|
||||||
return []byte(v)
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
//+build unsafe
|
|
||||||
|
|
||||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
||||||
|
|
||||||
package codec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file has unsafe variants of some helper methods.
|
|
||||||
|
|
||||||
type unsafeString struct {
|
|
||||||
Data uintptr
|
|
||||||
Len int
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsafeSlice struct {
|
|
||||||
Data uintptr
|
|
||||||
Len int
|
|
||||||
Cap int
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringView returns a view of the []byte as a string.
|
|
||||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
|
||||||
// In regular safe mode, it is an allocation and copy.
|
|
||||||
func stringView(v []byte) string {
|
|
||||||
if len(v) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
bx := (*unsafeSlice)(unsafe.Pointer(&v))
|
|
||||||
sx := unsafeString{bx.Data, bx.Len}
|
|
||||||
return *(*string)(unsafe.Pointer(&sx))
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesView returns a view of the string as a []byte.
|
|
||||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
|
||||||
// In regular safe mode, it is an allocation and copy.
|
|
||||||
func bytesView(v string) []byte {
|
|
||||||
if len(v) == 0 {
|
|
||||||
return zeroByteSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
sx := (*unsafeString)(unsafe.Pointer(&v))
|
|
||||||
bx := unsafeSlice{sx.Data, sx.Len, sx.Len}
|
|
||||||
return *(*[]byte)(unsafe.Pointer(&bx))
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +0,0 @@
|
||||||
package codec
|
|
||||||
|
|
||||||
//go:generate bash prebuild.sh
|
|
|
@ -1,180 +0,0 @@
|
||||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
||||||
|
|
||||||
package codec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"net/rpc"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// rpcEncodeTerminator allows a handler specify a []byte terminator to send after each Encode.
|
|
||||||
//
|
|
||||||
// Some codecs like json need to put a space after each encoded value, to serve as a
|
|
||||||
// delimiter for things like numbers (else json codec will continue reading till EOF).
|
|
||||||
type rpcEncodeTerminator interface {
|
|
||||||
rpcEncodeTerminate() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rpc provides a rpc Server or Client Codec for rpc communication.
|
|
||||||
type Rpc interface {
|
|
||||||
ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec
|
|
||||||
ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
|
|
||||||
}
|
|
||||||
|
|
||||||
// RpcCodecBuffered allows access to the underlying bufio.Reader/Writer
|
|
||||||
// used by the rpc connection. It accomodates use-cases where the connection
|
|
||||||
// should be used by rpc and non-rpc functions, e.g. streaming a file after
|
|
||||||
// sending an rpc response.
|
|
||||||
type RpcCodecBuffered interface {
|
|
||||||
BufferedReader() *bufio.Reader
|
|
||||||
BufferedWriter() *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------
|
|
||||||
|
|
||||||
// rpcCodec defines the struct members and common methods.
|
|
||||||
type rpcCodec struct {
|
|
||||||
rwc io.ReadWriteCloser
|
|
||||||
dec *Decoder
|
|
||||||
enc *Encoder
|
|
||||||
bw *bufio.Writer
|
|
||||||
br *bufio.Reader
|
|
||||||
mu sync.Mutex
|
|
||||||
h Handle
|
|
||||||
|
|
||||||
cls bool
|
|
||||||
clsmu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec {
|
|
||||||
bw := bufio.NewWriter(conn)
|
|
||||||
br := bufio.NewReader(conn)
|
|
||||||
return rpcCodec{
|
|
||||||
rwc: conn,
|
|
||||||
bw: bw,
|
|
||||||
br: br,
|
|
||||||
enc: NewEncoder(bw, h),
|
|
||||||
dec: NewDecoder(br, h),
|
|
||||||
h: h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) BufferedReader() *bufio.Reader {
|
|
||||||
return c.br
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) BufferedWriter() *bufio.Writer {
|
|
||||||
return c.bw
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) {
|
|
||||||
if c.isClosed() {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
if err = c.enc.Encode(obj1); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t, tOk := c.h.(rpcEncodeTerminator)
|
|
||||||
if tOk {
|
|
||||||
c.bw.Write(t.rpcEncodeTerminate())
|
|
||||||
}
|
|
||||||
if writeObj2 {
|
|
||||||
if err = c.enc.Encode(obj2); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if tOk {
|
|
||||||
c.bw.Write(t.rpcEncodeTerminate())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if doFlush {
|
|
||||||
return c.bw.Flush()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) read(obj interface{}) (err error) {
|
|
||||||
if c.isClosed() {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
//If nil is passed in, we should still attempt to read content to nowhere.
|
|
||||||
if obj == nil {
|
|
||||||
var obj2 interface{}
|
|
||||||
return c.dec.Decode(&obj2)
|
|
||||||
}
|
|
||||||
return c.dec.Decode(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) isClosed() bool {
|
|
||||||
c.clsmu.RLock()
|
|
||||||
x := c.cls
|
|
||||||
c.clsmu.RUnlock()
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) Close() error {
|
|
||||||
if c.isClosed() {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
c.clsmu.Lock()
|
|
||||||
c.cls = true
|
|
||||||
c.clsmu.Unlock()
|
|
||||||
return c.rwc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcCodec) ReadResponseBody(body interface{}) error {
|
|
||||||
return c.read(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------
|
|
||||||
|
|
||||||
type goRpcCodec struct {
|
|
||||||
rpcCodec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
|
|
||||||
// Must protect for concurrent access as per API
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.write(r, body, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.write(r, body, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error {
|
|
||||||
return c.read(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *goRpcCodec) ReadRequestHeader(r *rpc.Request) error {
|
|
||||||
return c.read(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *goRpcCodec) ReadRequestBody(body interface{}) error {
|
|
||||||
return c.read(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------
|
|
||||||
|
|
||||||
// goRpc is the implementation of Rpc that uses the communication protocol
|
|
||||||
// as defined in net/rpc package.
|
|
||||||
type goRpc struct{}
|
|
||||||
|
|
||||||
// GoRpc implements Rpc using the communication protocol defined in net/rpc package.
|
|
||||||
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered.
|
|
||||||
var GoRpc goRpc
|
|
||||||
|
|
||||||
func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
|
|
||||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
|
|
||||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered
|
|
|
@ -1,65 +0,0 @@
|
||||||
hash: 83229e7ecea008add55b2547fb28a23d247a4740fc30b4659f58bf5d495e3584
|
|
||||||
updated: 2017-08-16T11:06:05.978126639+02:00
|
|
||||||
imports:
|
|
||||||
- name: github.com/cupcake/rdb
|
|
||||||
version: 43ba34106c765f2111c0dc7b74cdf8ee437411e0
|
|
||||||
subpackages:
|
|
||||||
- crc64
|
|
||||||
- nopdecoder
|
|
||||||
- name: github.com/edsrzf/mmap-go
|
|
||||||
version: 935e0e8a636ca4ba70b713f3e38a19e1b77739e8
|
|
||||||
- name: github.com/glendc/gopher-json
|
|
||||||
version: dc4743023d0c166c1b844da8fc688e57ec65fe0b
|
|
||||||
- name: github.com/golang/snappy
|
|
||||||
version: 553a641470496b2327abcac10b36396bd98e45c9
|
|
||||||
- name: github.com/pelletier/go-buffruneio
|
|
||||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
|
||||||
- name: github.com/pelletier/go-toml
|
|
||||||
version: fe7536c3dee2596cdd23ee9976a17c22bdaae286
|
|
||||||
- name: github.com/peterh/liner
|
|
||||||
version: bf27d3ba8e1d9899d45a457ffac16c953eb2d647
|
|
||||||
- name: github.com/siddontang/go
|
|
||||||
version: 354e14e6c093c661abb29fd28403b3c19cff5514
|
|
||||||
subpackages:
|
|
||||||
- bson
|
|
||||||
- filelock
|
|
||||||
- hack
|
|
||||||
- ioutil2
|
|
||||||
- log
|
|
||||||
- num
|
|
||||||
- snappy
|
|
||||||
- sync2
|
|
||||||
- name: github.com/siddontang/goredis
|
|
||||||
version: 760763f78400635ed7b9b115511b8ed06035e908
|
|
||||||
- name: github.com/siddontang/rdb
|
|
||||||
version: fc89ed2e418d27e3ea76e708e54276d2b44ae9cf
|
|
||||||
- name: github.com/syndtr/goleveldb
|
|
||||||
version: cfa635847112c5dc4782e128fa7e0d05fdbfb394
|
|
||||||
subpackages:
|
|
||||||
- leveldb
|
|
||||||
- leveldb/cache
|
|
||||||
- leveldb/comparer
|
|
||||||
- leveldb/errors
|
|
||||||
- leveldb/filter
|
|
||||||
- leveldb/iterator
|
|
||||||
- leveldb/journal
|
|
||||||
- leveldb/memdb
|
|
||||||
- leveldb/opt
|
|
||||||
- leveldb/storage
|
|
||||||
- leveldb/table
|
|
||||||
- leveldb/util
|
|
||||||
- name: github.com/ugorji/go
|
|
||||||
version: b94837a2404ab90efe9289e77a70694c355739cb
|
|
||||||
subpackages:
|
|
||||||
- codec
|
|
||||||
- name: github.com/yuin/gopher-lua
|
|
||||||
version: b402f3114ec730d8bddb074a6c137309f561aa78
|
|
||||||
subpackages:
|
|
||||||
- ast
|
|
||||||
- parse
|
|
||||||
- pm
|
|
||||||
- name: golang.org/x/net
|
|
||||||
version: 1c05540f6879653db88113bc4a2b70aec4bd491f
|
|
||||||
subpackages:
|
|
||||||
- context
|
|
||||||
testImports: []
|
|
39
glide.yaml
39
glide.yaml
|
@ -1,39 +0,0 @@
|
||||||
package: github.com/siddontang/ledisdb
|
|
||||||
import:
|
|
||||||
- package: github.com/pelletier/go-toml
|
|
||||||
version: fe7536c3dee2596cdd23ee9976a17c22bdaae286
|
|
||||||
- package: github.com/edsrzf/mmap-go
|
|
||||||
version: 935e0e8a636ca4ba70b713f3e38a19e1b77739e8
|
|
||||||
- package: github.com/peterh/liner
|
|
||||||
version: bf27d3ba8e1d9899d45a457ffac16c953eb2d647
|
|
||||||
- package: github.com/siddontang/go
|
|
||||||
version: 354e14e6c093c661abb29fd28403b3c19cff5514
|
|
||||||
subpackages:
|
|
||||||
- bson
|
|
||||||
- filelock
|
|
||||||
- hack
|
|
||||||
- ioutil2
|
|
||||||
- log
|
|
||||||
- num
|
|
||||||
- snappy
|
|
||||||
- sync2
|
|
||||||
- package: github.com/siddontang/goredis
|
|
||||||
version: 760763f78400635ed7b9b115511b8ed06035e908
|
|
||||||
- package: github.com/siddontang/rdb
|
|
||||||
version: fc89ed2e418d27e3ea76e708e54276d2b44ae9cf
|
|
||||||
- package: github.com/syndtr/goleveldb
|
|
||||||
version: cfa635847112c5dc4782e128fa7e0d05fdbfb394
|
|
||||||
subpackages:
|
|
||||||
- leveldb
|
|
||||||
- leveldb/cache
|
|
||||||
- leveldb/filter
|
|
||||||
- leveldb/iterator
|
|
||||||
- leveldb/opt
|
|
||||||
- leveldb/storage
|
|
||||||
- leveldb/util
|
|
||||||
- package: github.com/ugorji/go
|
|
||||||
version: b94837a2404ab90efe9289e77a70694c355739cb
|
|
||||||
subpackages:
|
|
||||||
- codec
|
|
||||||
- package: github.com/yuin/gopher-lua
|
|
||||||
version: b402f3114ec730d8bddb074a6c137309f561aa78
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# Project-specific files
|
||||||
|
diff
|
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- tip
|
||||||
|
before_install:
|
||||||
|
- go get gopkg.in/check.v1
|
|
@ -0,0 +1,17 @@
|
||||||
|
# rdb [![Build Status](https://travis-ci.org/cupcake/rdb.png?branch=master)](https://travis-ci.org/cupcake/rdb)
|
||||||
|
|
||||||
|
rdb is a Go package that implements parsing and encoding of the
|
||||||
|
[Redis](http://redis.io) [RDB file
|
||||||
|
format](https://github.com/sripathikrishnan/redis-rdb-tools/blob/master/docs/RDB_File_Format.textile).
|
||||||
|
|
||||||
|
This package was heavily inspired by
|
||||||
|
[redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools) by
|
||||||
|
[Sripathi Krishnan](https://github.com/sripathikrishnan).
|
||||||
|
|
||||||
|
[**Documentation**](http://godoc.org/github.com/cupcake/rdb)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/cupcake/rdb
|
||||||
|
```
|
|
@ -0,0 +1,347 @@
|
||||||
|
package rdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cupcake/rdb"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hook gocheck into the gotest runner.
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type DecoderSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&DecoderSuite{})
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestEmptyRDB(c *C) {
|
||||||
|
r := decodeRDB("empty_database")
|
||||||
|
c.Assert(r.started, Equals, 1)
|
||||||
|
c.Assert(r.ended, Equals, 1)
|
||||||
|
c.Assert(len(r.dbs), Equals, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestMultipleDatabases(c *C) {
|
||||||
|
r := decodeRDB("multiple_databases")
|
||||||
|
c.Assert(len(r.dbs), Equals, 2)
|
||||||
|
_, ok := r.dbs[1]
|
||||||
|
c.Assert(ok, Equals, false)
|
||||||
|
c.Assert(r.dbs[0]["key_in_zeroth_database"], Equals, "zero")
|
||||||
|
c.Assert(r.dbs[2]["key_in_second_database"], Equals, "second")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestExpiry(c *C) {
|
||||||
|
r := decodeRDB("keys_with_expiry")
|
||||||
|
c.Assert(r.expiries[0]["expires_ms_precision"], Equals, int64(1671963072573))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestMixedExpiry(c *C) {
|
||||||
|
r := decodeRDB("keys_with_mixed_expiry")
|
||||||
|
c.Assert(r.expiries[0]["key01"], Not(Equals), int64(0))
|
||||||
|
c.Assert(r.expiries[0]["key04"], Not(Equals), int64(0))
|
||||||
|
|
||||||
|
c.Assert(r.expiries[0]["key02"], Equals, int64(0))
|
||||||
|
c.Assert(r.expiries[0]["key03"], Equals, int64(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestIntegerKeys(c *C) {
|
||||||
|
r := decodeRDB("integer_keys")
|
||||||
|
c.Assert(r.dbs[0]["125"], Equals, "Positive 8 bit integer")
|
||||||
|
c.Assert(r.dbs[0]["43947"], Equals, "Positive 16 bit integer")
|
||||||
|
c.Assert(r.dbs[0]["183358245"], Equals, "Positive 32 bit integer")
|
||||||
|
c.Assert(r.dbs[0]["-123"], Equals, "Negative 8 bit integer")
|
||||||
|
c.Assert(r.dbs[0]["-29477"], Equals, "Negative 16 bit integer")
|
||||||
|
c.Assert(r.dbs[0]["-183358245"], Equals, "Negative 32 bit integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestStringKeyWithCompression(c *C) {
|
||||||
|
r := decodeRDB("easily_compressible_string_key")
|
||||||
|
c.Assert(r.dbs[0][strings.Repeat("a", 200)], Equals, "Key that redis should compress easily")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZipmapWithCompression(c *C) {
|
||||||
|
r := decodeRDB("zipmap_that_compresses_easily")
|
||||||
|
zm := r.dbs[0]["zipmap_compresses_easily"].(map[string]string)
|
||||||
|
c.Assert(zm["a"], Equals, "aa")
|
||||||
|
c.Assert(zm["aa"], Equals, "aaaa")
|
||||||
|
c.Assert(zm["aaaaa"], Equals, "aaaaaaaaaaaaaa")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZipmap(c *C) {
|
||||||
|
r := decodeRDB("zipmap_that_doesnt_compress")
|
||||||
|
zm := r.dbs[0]["zimap_doesnt_compress"].(map[string]string)
|
||||||
|
c.Assert(zm["MKD1G6"], Equals, "2")
|
||||||
|
c.Assert(zm["YNNXK"], Equals, "F7TI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZipmapWitBigValues(c *C) {
|
||||||
|
r := decodeRDB("zipmap_with_big_values")
|
||||||
|
zm := r.dbs[0]["zipmap_with_big_values"].(map[string]string)
|
||||||
|
c.Assert(len(zm["253bytes"]), Equals, 253)
|
||||||
|
c.Assert(len(zm["254bytes"]), Equals, 254)
|
||||||
|
c.Assert(len(zm["255bytes"]), Equals, 255)
|
||||||
|
c.Assert(len(zm["300bytes"]), Equals, 300)
|
||||||
|
c.Assert(len(zm["20kbytes"]), Equals, 20000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestHashZiplist(c *C) {
|
||||||
|
r := decodeRDB("hash_as_ziplist")
|
||||||
|
zm := r.dbs[0]["zipmap_compresses_easily"].(map[string]string)
|
||||||
|
c.Assert(zm["a"], Equals, "aa")
|
||||||
|
c.Assert(zm["aa"], Equals, "aaaa")
|
||||||
|
c.Assert(zm["aaaaa"], Equals, "aaaaaaaaaaaaaa")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestDictionary(c *C) {
|
||||||
|
r := decodeRDB("dictionary")
|
||||||
|
d := r.dbs[0]["force_dictionary"].(map[string]string)
|
||||||
|
c.Assert(len(d), Equals, 1000)
|
||||||
|
c.Assert(d["ZMU5WEJDG7KU89AOG5LJT6K7HMNB3DEI43M6EYTJ83VRJ6XNXQ"], Equals, "T63SOS8DQJF0Q0VJEZ0D1IQFCYTIPSBOUIAI9SB0OV57MQR1FI")
|
||||||
|
c.Assert(d["UHS5ESW4HLK8XOGTM39IK1SJEUGVV9WOPK6JYA5QBZSJU84491"], Equals, "6VULTCV52FXJ8MGVSFTZVAGK2JXZMGQ5F8OVJI0X6GEDDR27RZ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZiplistWithCompression(c *C) {
|
||||||
|
r := decodeRDB("ziplist_that_compresses_easily")
|
||||||
|
for i, length := range []int{6, 12, 18, 24, 30, 36} {
|
||||||
|
c.Assert(r.dbs[0]["ziplist_compresses_easily"].([]string)[i], Equals, strings.Repeat("a", length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZiplist(c *C) {
|
||||||
|
r := decodeRDB("ziplist_that_doesnt_compress")
|
||||||
|
l := r.dbs[0]["ziplist_doesnt_compress"].([]string)
|
||||||
|
c.Assert(l[0], Equals, "aj2410")
|
||||||
|
c.Assert(l[1], Equals, "cc953a17a8e096e76a44169ad3f9ac87c5f8248a403274416179aa9fbd852344")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZiplistWithInts(c *C) {
|
||||||
|
r := decodeRDB("ziplist_with_integers")
|
||||||
|
expected := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "-2", "13", "25", "-61", "63", "16380", "-16000", "65535", "-65523", "4194304", "9223372036854775807"}
|
||||||
|
for i, x := range expected {
|
||||||
|
c.Assert(r.dbs[0]["ziplist_with_integers"].([]string)[i], Equals, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestIntSet16(c *C) {
|
||||||
|
r := decodeRDB("intset_16")
|
||||||
|
for i, x := range []string{"32764", "32765", "32766"} {
|
||||||
|
c.Assert(r.dbs[0]["intset_16"].([]string)[i], Equals, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestIntSet32(c *C) {
|
||||||
|
r := decodeRDB("intset_32")
|
||||||
|
for i, x := range []string{"2147418108", "2147418109", "2147418110"} {
|
||||||
|
c.Assert(r.dbs[0]["intset_32"].([]string)[i], Equals, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestIntSet64(c *C) {
|
||||||
|
r := decodeRDB("intset_64")
|
||||||
|
for i, x := range []string{"9223090557583032316", "9223090557583032317", "9223090557583032318"} {
|
||||||
|
c.Assert(r.dbs[0]["intset_64"].([]string)[i], Equals, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestSet(c *C) {
|
||||||
|
r := decodeRDB("regular_set")
|
||||||
|
for i, x := range []string{"beta", "delta", "alpha", "phi", "gamma", "kappa"} {
|
||||||
|
c.Assert(r.dbs[0]["regular_set"].([]string)[i], Equals, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestZSetZiplist(c *C) {
|
||||||
|
r := decodeRDB("sorted_set_as_ziplist")
|
||||||
|
z := r.dbs[0]["sorted_set_as_ziplist"].(map[string]float64)
|
||||||
|
c.Assert(z["8b6ba6718a786daefa69438148361901"], Equals, float64(1))
|
||||||
|
c.Assert(z["cb7a24bb7528f934b841b34c3a73e0c7"], Equals, float64(2.37))
|
||||||
|
c.Assert(z["523af537946b79c4f8369ed39ba78605"], Equals, float64(3.423))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestRDBv5(c *C) {
|
||||||
|
r := decodeRDB("rdb_version_5_with_checksum")
|
||||||
|
c.Assert(r.dbs[0]["abcd"], Equals, "efgh")
|
||||||
|
c.Assert(r.dbs[0]["foo"], Equals, "bar")
|
||||||
|
c.Assert(r.dbs[0]["bar"], Equals, "baz")
|
||||||
|
c.Assert(r.dbs[0]["abcdef"], Equals, "abcdef")
|
||||||
|
c.Assert(r.dbs[0]["longerstring"], Equals, "thisisalongerstring.idontknowwhatitmeans")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestRDBv7(c *C) {
|
||||||
|
r := decodeRDB("rdb_v7_list_quicklist")
|
||||||
|
c.Assert(r.aux["redis-ver"], Equals, "3.2.0")
|
||||||
|
c.Assert(r.dbSize[0], Equals, uint32(1))
|
||||||
|
c.Assert(r.expiresSize[0], Equals, uint32(0))
|
||||||
|
z := r.dbs[0]["foo"].([]string)
|
||||||
|
c.Assert(z[0], Equals, "bar")
|
||||||
|
c.Assert(z[1], Equals, "baz")
|
||||||
|
c.Assert(z[2], Equals, "boo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DecoderSuite) TestDumpDecoder(c *C) {
|
||||||
|
r := &FakeRedis{}
|
||||||
|
err := rdb.DecodeDump([]byte("\u0000\xC0\n\u0006\u0000\xF8r?\xC5\xFB\xFB_("), 1, []byte("test"), 123, r)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.Assert(r.dbs[1]["test"], Equals, "10")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRDB(name string) *FakeRedis {
|
||||||
|
r := &FakeRedis{}
|
||||||
|
f, err := os.Open("fixtures/" + name + ".rdb")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = rdb.Decode(f, r)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeRedis struct {
|
||||||
|
dbs map[int]map[string]interface{}
|
||||||
|
lengths map[int]map[string]int
|
||||||
|
expiries map[int]map[string]int64
|
||||||
|
dbSize map[int]uint32
|
||||||
|
expiresSize map[int]uint32
|
||||||
|
|
||||||
|
cdb int
|
||||||
|
started int
|
||||||
|
ended int
|
||||||
|
|
||||||
|
aux map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) setExpiry(key []byte, expiry int64) {
|
||||||
|
r.expiries[r.cdb][string(key)] = expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) setLength(key []byte, length int64) {
|
||||||
|
r.lengths[r.cdb][string(key)] = int(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) getLength(key []byte) int {
|
||||||
|
return int(r.lengths[r.cdb][string(key)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) db() map[string]interface{} {
|
||||||
|
return r.dbs[r.cdb]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartRDB() {
|
||||||
|
r.started++
|
||||||
|
r.dbs = make(map[int]map[string]interface{})
|
||||||
|
r.expiries = make(map[int]map[string]int64)
|
||||||
|
r.lengths = make(map[int]map[string]int)
|
||||||
|
r.aux = make(map[string]string)
|
||||||
|
r.dbSize = make(map[int]uint32)
|
||||||
|
r.expiresSize = make(map[int]uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartDatabase(n int) {
|
||||||
|
r.dbs[n] = make(map[string]interface{})
|
||||||
|
r.expiries[n] = make(map[string]int64)
|
||||||
|
r.lengths[n] = make(map[string]int)
|
||||||
|
r.cdb = n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Set(key, value []byte, expiry int64) {
|
||||||
|
r.setExpiry(key, expiry)
|
||||||
|
r.db()[string(key)] = string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartHash(key []byte, length, expiry int64) {
|
||||||
|
r.setExpiry(key, expiry)
|
||||||
|
r.setLength(key, length)
|
||||||
|
r.db()[string(key)] = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Hset(key, field, value []byte) {
|
||||||
|
r.db()[string(key)].(map[string]string)[string(field)] = string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndHash(key []byte) {
|
||||||
|
actual := len(r.db()[string(key)].(map[string]string))
|
||||||
|
if actual != r.getLength(key) {
|
||||||
|
panic(fmt.Sprintf("wrong length for key %s got %d, expected %d", key, actual, r.getLength(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartSet(key []byte, cardinality, expiry int64) {
|
||||||
|
r.setExpiry(key, expiry)
|
||||||
|
r.setLength(key, cardinality)
|
||||||
|
r.db()[string(key)] = make([]string, 0, cardinality)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Sadd(key, member []byte) {
|
||||||
|
r.db()[string(key)] = append(r.db()[string(key)].([]string), string(member))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndSet(key []byte) {
|
||||||
|
actual := len(r.db()[string(key)].([]string))
|
||||||
|
if actual != r.getLength(key) {
|
||||||
|
panic(fmt.Sprintf("wrong length for key %s got %d, expected %d", key, actual, r.getLength(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartList(key []byte, length, expiry int64) {
|
||||||
|
r.setExpiry(key, expiry)
|
||||||
|
r.setLength(key, length)
|
||||||
|
cap := length
|
||||||
|
if length < 0 {
|
||||||
|
cap = 1
|
||||||
|
}
|
||||||
|
r.db()[string(key)] = make([]string, 0, cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Rpush(key, value []byte) {
|
||||||
|
r.db()[string(key)] = append(r.db()[string(key)].([]string), string(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndList(key []byte) {
|
||||||
|
actual := len(r.db()[string(key)].([]string))
|
||||||
|
if actual != r.getLength(key) && r.getLength(key) >= 0 {
|
||||||
|
panic(fmt.Sprintf("wrong length for key %s got %d, expected %d", key, actual, r.getLength(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) StartZSet(key []byte, cardinality, expiry int64) {
|
||||||
|
r.setExpiry(key, expiry)
|
||||||
|
r.setLength(key, cardinality)
|
||||||
|
r.db()[string(key)] = make(map[string]float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Zadd(key []byte, score float64, member []byte) {
|
||||||
|
r.db()[string(key)].(map[string]float64)[string(member)] = score
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndZSet(key []byte) {
|
||||||
|
actual := len(r.db()[string(key)].(map[string]float64))
|
||||||
|
if actual != r.getLength(key) {
|
||||||
|
panic(fmt.Sprintf("wrong length for key %s got %d, expected %d", key, actual, r.getLength(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndDatabase(n int) {
|
||||||
|
if n != r.cdb {
|
||||||
|
panic(fmt.Sprintf("database end called with %d, expected %d", n, r.cdb))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) EndRDB() {
|
||||||
|
r.ended++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) Aux(key, value []byte) {
|
||||||
|
r.aux[string(key)] = string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeRedis) ResizeDatabase(dbSize, expiresSize uint32) {
|
||||||
|
r.dbSize[r.cdb] = dbSize
|
||||||
|
r.expiresSize[r.cdb] = expiresSize
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package rdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/cupcake/rdb"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EncoderSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&EncoderSuite{})
|
||||||
|
|
||||||
|
var stringEncodingTests = []struct {
|
||||||
|
str string
|
||||||
|
res string
|
||||||
|
}{
|
||||||
|
{"0", "AMAABgAOrc/4DQU/mw=="},
|
||||||
|
{"127", "AMB/BgCbWIOxpwH5hw=="},
|
||||||
|
{"-128", "AMCABgAPi1rt2llnSg=="},
|
||||||
|
{"128", "AMGAAAYAfZfbNeWad/Y="},
|
||||||
|
{"-129", "AMF//wYAgY3qqKHVuBM="},
|
||||||
|
{"32767", "AMH/fwYA37dfWuKh6bg="},
|
||||||
|
{"-32768", "AMEAgAYAI61ux6buJl0="},
|
||||||
|
{"-32768", "AMEAgAYAI61ux6buJl0="},
|
||||||
|
{"2147483647", "AML///9/BgC6mY0eFXuRMg=="},
|
||||||
|
{"-2147483648", "AMIAAACABgBRou++xgC9FA=="},
|
||||||
|
{"a", "AAFhBgApE4cbemNBJw=="},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EncoderSuite) TestStringEncoding(c *C) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
for _, t := range stringEncodingTests {
|
||||||
|
e := rdb.NewEncoder(buf)
|
||||||
|
e.EncodeType(rdb.TypeString)
|
||||||
|
e.EncodeString([]byte(t.str))
|
||||||
|
e.EncodeDumpFooter()
|
||||||
|
expected, _ := base64.StdEncoding.DecodeString(t.res)
|
||||||
|
c.Assert(buf.Bytes(), DeepEquals, expected, Commentf("%s - expected: %x, actual: %x", t.str, expected, buf.Bytes()))
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
*.out
|
||||||
|
*.5
|
||||||
|
*.6
|
||||||
|
*.8
|
||||||
|
*.swp
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
testdata
|
|
@ -0,0 +1,12 @@
|
||||||
|
mmap-go
|
||||||
|
=======
|
||||||
|
|
||||||
|
mmap-go is a portable mmap package for the [Go programming language](http://golang.org).
|
||||||
|
It has been tested on Linux (386, amd64), OS X, and Windows (386). It should also
|
||||||
|
work on other Unix-like platforms, but hasn't been tested with them. I'm interested
|
||||||
|
to hear about the results.
|
||||||
|
|
||||||
|
I haven't been able to add more features without adding significant complexity,
|
||||||
|
so mmap-go doesn't support mprotect, mincore, and maybe a few other things.
|
||||||
|
If you're running on a Unix-like platform and need some of these features,
|
||||||
|
I suggest Gustavo Niemeyer's [gommap](http://labix.org/gommap).
|
|
@ -54,6 +54,10 @@ func Map(f *os.File, prot, flags int) (MMap, error) {
|
||||||
// If length < 0, the entire file will be mapped.
|
// If length < 0, the entire file will be mapped.
|
||||||
// If ANON is set in flags, f is ignored.
|
// If ANON is set in flags, f is ignored.
|
||||||
func MapRegion(f *os.File, length int, prot, flags int, offset int64) (MMap, error) {
|
func MapRegion(f *os.File, length int, prot, flags int, offset int64) (MMap, error) {
|
||||||
|
if offset%int64(os.Getpagesize()) != 0 {
|
||||||
|
return nil, errors.New("offset parameter must be a multiple of the system's page size")
|
||||||
|
}
|
||||||
|
|
||||||
var fd uintptr
|
var fd uintptr
|
||||||
if flags&ANON == 0 {
|
if flags&ANON == 0 {
|
||||||
fd = uintptr(f.Fd())
|
fd = uintptr(f.Fd())
|
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright 2011 Evan Shaw. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// These tests are adapted from gommap: http://labix.org/gommap
|
||||||
|
// Copyright (c) 2010, Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
|
||||||
|
package mmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testData = []byte("0123456789ABCDEF")
|
||||||
|
var testPath = filepath.Join(os.TempDir(), "testdata")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := openFile(os.O_RDWR | os.O_CREATE | os.O_TRUNC)
|
||||||
|
f.Write(testData)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func openFile(flags int) *os.File {
|
||||||
|
f, err := os.OpenFile(testPath, flags, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmap(t *testing.T) {
|
||||||
|
f := openFile(os.O_RDONLY)
|
||||||
|
defer f.Close()
|
||||||
|
mmap, err := Map(f, RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error mapping: %s", err)
|
||||||
|
}
|
||||||
|
if err := mmap.Unmap(); err != nil {
|
||||||
|
t.Errorf("error unmapping: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadWrite(t *testing.T) {
|
||||||
|
f := openFile(os.O_RDWR)
|
||||||
|
defer f.Close()
|
||||||
|
mmap, err := Map(f, RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error mapping: %s", err)
|
||||||
|
}
|
||||||
|
defer mmap.Unmap()
|
||||||
|
if !bytes.Equal(testData, mmap) {
|
||||||
|
t.Errorf("mmap != testData: %q, %q", mmap, testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
mmap[9] = 'X'
|
||||||
|
mmap.Flush()
|
||||||
|
|
||||||
|
fileData, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error reading file: %s", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(fileData, []byte("012345678XABCDEF")) {
|
||||||
|
t.Errorf("file wasn't modified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// leave things how we found them
|
||||||
|
mmap[9] = '9'
|
||||||
|
mmap.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtFlagsAndErr(t *testing.T) {
|
||||||
|
f := openFile(os.O_RDONLY)
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := Map(f, RDWR, 0); err == nil {
|
||||||
|
t.Errorf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlags(t *testing.T) {
|
||||||
|
f := openFile(os.O_RDWR)
|
||||||
|
defer f.Close()
|
||||||
|
mmap, err := Map(f, COPY, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error mapping: %s", err)
|
||||||
|
}
|
||||||
|
defer mmap.Unmap()
|
||||||
|
|
||||||
|
mmap[9] = 'X'
|
||||||
|
mmap.Flush()
|
||||||
|
|
||||||
|
fileData, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error reading file: %s", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(fileData, testData) {
|
||||||
|
t.Errorf("file was modified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we can map files from non-0 offsets
|
||||||
|
// The page size on most Unixes is 4KB, but on Windows it's 64KB
|
||||||
|
func TestNonZeroOffset(t *testing.T) {
|
||||||
|
const pageSize = 65536
|
||||||
|
|
||||||
|
// Create a 2-page sized file
|
||||||
|
bigFilePath := filepath.Join(os.TempDir(), "nonzero")
|
||||||
|
fileobj, err := os.OpenFile(bigFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
bigData := make([]byte, 2*pageSize, 2*pageSize)
|
||||||
|
fileobj.Write(bigData)
|
||||||
|
fileobj.Close()
|
||||||
|
|
||||||
|
// Map the first page by itself
|
||||||
|
fileobj, err = os.OpenFile(bigFilePath, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
m, err := MapRegion(fileobj, pageSize, RDONLY, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error mapping file: %s", err)
|
||||||
|
}
|
||||||
|
m.Unmap()
|
||||||
|
fileobj.Close()
|
||||||
|
|
||||||
|
// Map the second page by itself
|
||||||
|
fileobj, err = os.OpenFile(bigFilePath, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
m, err = MapRegion(fileobj, pageSize, RDONLY, 0, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error mapping file: %s", err)
|
||||||
|
}
|
||||||
|
err = m.Unmap()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err = MapRegion(fileobj, pageSize, RDONLY, 0, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expect error because offset is not multiple of page size")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileobj.Close()
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
# gopher-json [![GoDoc](https://godoc.org/layeh.com/gopher-json?status.svg)](https://godoc.org/layeh.com/gopher-json)
|
||||||
|
|
||||||
|
Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Public domain.
|
|
@ -0,0 +1,75 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimple(t *testing.T) {
|
||||||
|
const str = `
|
||||||
|
local json = require("json")
|
||||||
|
assert(type(json) == "table")
|
||||||
|
assert(type(json.decode) == "function")
|
||||||
|
assert(type(json.encode) == "function")
|
||||||
|
|
||||||
|
assert(json.encode(true) == "true")
|
||||||
|
assert(json.encode(1) == "1")
|
||||||
|
assert(json.encode(-10) == "-10")
|
||||||
|
assert(json.encode(nil) == "{}")
|
||||||
|
|
||||||
|
local obj = {"a",1,"b",2,"c",3}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
for i = 1, #obj do
|
||||||
|
assert(obj[i] == jsonObj[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
local obj = {name="Tim",number=12345}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
assert(obj.name == jsonObj.name)
|
||||||
|
assert(obj.number == jsonObj.number)
|
||||||
|
|
||||||
|
local obj = {"a","b",what="c",[5]="asd"}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
assert(obj[1] == jsonObj["1"])
|
||||||
|
assert(obj[2] == jsonObj["2"])
|
||||||
|
assert(obj.what == jsonObj["what"])
|
||||||
|
assert(obj[5] == jsonObj["5"])
|
||||||
|
|
||||||
|
assert(json.decode("null") == nil)
|
||||||
|
|
||||||
|
assert(json.decode(json.encode({person={name = "tim",}})).person.name == "tim")
|
||||||
|
|
||||||
|
local obj = {
|
||||||
|
abc = 123,
|
||||||
|
def = nil,
|
||||||
|
}
|
||||||
|
local obj2 = {
|
||||||
|
obj = obj,
|
||||||
|
}
|
||||||
|
obj.obj2 = obj2
|
||||||
|
assert(json.encode(obj) == nil)
|
||||||
|
`
|
||||||
|
s := lua.NewState()
|
||||||
|
Preload(s)
|
||||||
|
if err := s.DoString(str); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomRequire(t *testing.T) {
|
||||||
|
const str = `
|
||||||
|
local j = require("JSON")
|
||||||
|
assert(type(j) == "table")
|
||||||
|
assert(type(j.decode) == "function")
|
||||||
|
assert(type(j.encode) == "function")
|
||||||
|
`
|
||||||
|
s := lua.NewState()
|
||||||
|
s.PreloadModule("JSON", Loader)
|
||||||
|
if err := s.DoString(str); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
cmd/snappytool/snappytool
|
||||||
|
testdata/bench
|
||||||
|
|
||||||
|
# These explicitly listed benchmark data files are for an obsolete version of
|
||||||
|
# snappy_test.go.
|
||||||
|
testdata/alice29.txt
|
||||||
|
testdata/asyoulik.txt
|
||||||
|
testdata/fireworks.jpeg
|
||||||
|
testdata/geo.protodata
|
||||||
|
testdata/html
|
||||||
|
testdata/html_x_4
|
||||||
|
testdata/kppkn.gtb
|
||||||
|
testdata/lcet10.txt
|
||||||
|
testdata/paper-100k.pdf
|
||||||
|
testdata/plrabn12.txt
|
||||||
|
testdata/urls.10K
|
|
@ -0,0 +1,15 @@
|
||||||
|
# This is the official list of Snappy-Go authors for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Google Inc.
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
|
@ -0,0 +1,37 @@
|
||||||
|
# This is the official list of people who can contribute
|
||||||
|
# (and typically have contributed) code to the Snappy-Go repository.
|
||||||
|
# The AUTHORS file lists the copyright holders; this file
|
||||||
|
# lists people. For example, Google employees are listed here
|
||||||
|
# but not in AUTHORS, because Google holds the copyright.
|
||||||
|
#
|
||||||
|
# The submission process automatically checks to make sure
|
||||||
|
# that people submitting code are listed in this file (by email address).
|
||||||
|
#
|
||||||
|
# Names should be added to this file only after verifying that
|
||||||
|
# the individual or the individual's organization has agreed to
|
||||||
|
# the appropriate Contributor License Agreement, found here:
|
||||||
|
#
|
||||||
|
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||||
|
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||||
|
#
|
||||||
|
# The agreement for individuals can be filled out on the web.
|
||||||
|
#
|
||||||
|
# When adding J Random Contributor's name to this file,
|
||||||
|
# either J's name or J's organization's name should be
|
||||||
|
# added to the AUTHORS file, depending on whether the
|
||||||
|
# individual or corporate CLA was used.
|
||||||
|
|
||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Kai Backman <kaib@golang.org>
|
||||||
|
Marc-Antoine Ruel <maruel@chromium.org>
|
||||||
|
Nigel Tao <nigeltao@golang.org>
|
||||||
|
Rob Pike <r@golang.org>
|
||||||
|
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||||
|
Russ Cox <rsc@golang.org>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
|
@ -0,0 +1,107 @@
|
||||||
|
The Snappy compression format in the Go programming language.
|
||||||
|
|
||||||
|
To download and install from source:
|
||||||
|
$ go get github.com/golang/snappy
|
||||||
|
|
||||||
|
Unless otherwise noted, the Snappy-Go source files are distributed
|
||||||
|
under the BSD-style license found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Benchmarks.
|
||||||
|
|
||||||
|
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
|
||||||
|
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
|
||||||
|
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
|
||||||
|
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
|
||||||
|
|
||||||
|
"go test -test.bench=."
|
||||||
|
|
||||||
|
_UFlat0-8 2.19GB/s ± 0% html
|
||||||
|
_UFlat1-8 1.41GB/s ± 0% urls
|
||||||
|
_UFlat2-8 23.5GB/s ± 2% jpg
|
||||||
|
_UFlat3-8 1.91GB/s ± 0% jpg_200
|
||||||
|
_UFlat4-8 14.0GB/s ± 1% pdf
|
||||||
|
_UFlat5-8 1.97GB/s ± 0% html4
|
||||||
|
_UFlat6-8 814MB/s ± 0% txt1
|
||||||
|
_UFlat7-8 785MB/s ± 0% txt2
|
||||||
|
_UFlat8-8 857MB/s ± 0% txt3
|
||||||
|
_UFlat9-8 719MB/s ± 1% txt4
|
||||||
|
_UFlat10-8 2.84GB/s ± 0% pb
|
||||||
|
_UFlat11-8 1.05GB/s ± 0% gaviota
|
||||||
|
|
||||||
|
_ZFlat0-8 1.04GB/s ± 0% html
|
||||||
|
_ZFlat1-8 534MB/s ± 0% urls
|
||||||
|
_ZFlat2-8 15.7GB/s ± 1% jpg
|
||||||
|
_ZFlat3-8 740MB/s ± 3% jpg_200
|
||||||
|
_ZFlat4-8 9.20GB/s ± 1% pdf
|
||||||
|
_ZFlat5-8 991MB/s ± 0% html4
|
||||||
|
_ZFlat6-8 379MB/s ± 0% txt1
|
||||||
|
_ZFlat7-8 352MB/s ± 0% txt2
|
||||||
|
_ZFlat8-8 396MB/s ± 1% txt3
|
||||||
|
_ZFlat9-8 327MB/s ± 1% txt4
|
||||||
|
_ZFlat10-8 1.33GB/s ± 1% pb
|
||||||
|
_ZFlat11-8 605MB/s ± 1% gaviota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"go test -test.bench=. -tags=noasm"
|
||||||
|
|
||||||
|
_UFlat0-8 621MB/s ± 2% html
|
||||||
|
_UFlat1-8 494MB/s ± 1% urls
|
||||||
|
_UFlat2-8 23.2GB/s ± 1% jpg
|
||||||
|
_UFlat3-8 1.12GB/s ± 1% jpg_200
|
||||||
|
_UFlat4-8 4.35GB/s ± 1% pdf
|
||||||
|
_UFlat5-8 609MB/s ± 0% html4
|
||||||
|
_UFlat6-8 296MB/s ± 0% txt1
|
||||||
|
_UFlat7-8 288MB/s ± 0% txt2
|
||||||
|
_UFlat8-8 309MB/s ± 1% txt3
|
||||||
|
_UFlat9-8 280MB/s ± 1% txt4
|
||||||
|
_UFlat10-8 753MB/s ± 0% pb
|
||||||
|
_UFlat11-8 400MB/s ± 0% gaviota
|
||||||
|
|
||||||
|
_ZFlat0-8 409MB/s ± 1% html
|
||||||
|
_ZFlat1-8 250MB/s ± 1% urls
|
||||||
|
_ZFlat2-8 12.3GB/s ± 1% jpg
|
||||||
|
_ZFlat3-8 132MB/s ± 0% jpg_200
|
||||||
|
_ZFlat4-8 2.92GB/s ± 0% pdf
|
||||||
|
_ZFlat5-8 405MB/s ± 1% html4
|
||||||
|
_ZFlat6-8 179MB/s ± 1% txt1
|
||||||
|
_ZFlat7-8 170MB/s ± 1% txt2
|
||||||
|
_ZFlat8-8 189MB/s ± 1% txt3
|
||||||
|
_ZFlat9-8 164MB/s ± 1% txt4
|
||||||
|
_ZFlat10-8 479MB/s ± 1% pb
|
||||||
|
_ZFlat11-8 270MB/s ± 1% gaviota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
|
||||||
|
are the numbers from C++ Snappy's
|
||||||
|
|
||||||
|
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
|
||||||
|
|
||||||
|
BM_UFlat/0 2.4GB/s html
|
||||||
|
BM_UFlat/1 1.4GB/s urls
|
||||||
|
BM_UFlat/2 21.8GB/s jpg
|
||||||
|
BM_UFlat/3 1.5GB/s jpg_200
|
||||||
|
BM_UFlat/4 13.3GB/s pdf
|
||||||
|
BM_UFlat/5 2.1GB/s html4
|
||||||
|
BM_UFlat/6 1.0GB/s txt1
|
||||||
|
BM_UFlat/7 959.4MB/s txt2
|
||||||
|
BM_UFlat/8 1.0GB/s txt3
|
||||||
|
BM_UFlat/9 864.5MB/s txt4
|
||||||
|
BM_UFlat/10 2.9GB/s pb
|
||||||
|
BM_UFlat/11 1.2GB/s gaviota
|
||||||
|
|
||||||
|
BM_ZFlat/0 944.3MB/s html (22.31 %)
|
||||||
|
BM_ZFlat/1 501.6MB/s urls (47.78 %)
|
||||||
|
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
|
||||||
|
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
|
||||||
|
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
|
||||||
|
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
|
||||||
|
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
|
||||||
|
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
|
||||||
|
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
|
||||||
|
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
|
||||||
|
BM_ZFlat/10 1.2GB/s pb (19.68 %)
|
||||||
|
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
test_program/test_program_bin
|
|
@ -0,0 +1,23 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7.6
|
||||||
|
- 1.8.3
|
||||||
|
- 1.9
|
||||||
|
- tip
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
script:
|
||||||
|
- if [ -n "$(go fmt ./...)" ]; then exit 1; fi
|
||||||
|
- ./test.sh
|
||||||
|
- ./benchmark.sh $TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git
|
||||||
|
before_install:
|
||||||
|
- go get github.com/axw/gocov/gocov
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||||
|
branches:
|
||||||
|
only: [master]
|
||||||
|
after_success:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out -repotoken $COVERALLS_TOKEN
|
|
@ -0,0 +1,119 @@
|
||||||
|
# go-toml
|
||||||
|
|
||||||
|
Go library for the [TOML](https://github.com/mojombo/toml) format.
|
||||||
|
|
||||||
|
This library supports TOML version
|
||||||
|
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml)
|
||||||
|
[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE)
|
||||||
|
[![Build Status](https://travis-ci.org/pelletier/go-toml.svg?branch=master)](https://travis-ci.org/pelletier/go-toml)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/pelletier/go-toml/badge.svg?branch=master)](https://coveralls.io/github/pelletier/go-toml?branch=master)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Go-toml provides the following features for using data parsed from TOML documents:
|
||||||
|
|
||||||
|
* Load TOML documents from files and string data
|
||||||
|
* Easily navigate TOML structure using Tree
|
||||||
|
* Mashaling and unmarshaling to and from data structures
|
||||||
|
* Line & column position data for all parsed elements
|
||||||
|
* [Query support similar to JSON-Path](query/)
|
||||||
|
* Syntax errors contain line and column numbers
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/pelletier/go-toml"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
Read a TOML document:
|
||||||
|
|
||||||
|
```go
|
||||||
|
config, _ := toml.Load(`
|
||||||
|
[postgres]
|
||||||
|
user = "pelletier"
|
||||||
|
password = "mypassword"`)
|
||||||
|
// retrieve data directly
|
||||||
|
user := config.Get("postgres.user").(string)
|
||||||
|
|
||||||
|
// or using an intermediate object
|
||||||
|
postgresConfig := config.Get("postgres").(*toml.Tree)
|
||||||
|
password := postgresConfig.Get("password").(string)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use Unmarshal:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Postgres struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
type Config struct {
|
||||||
|
Postgres Postgres
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := []byte(`
|
||||||
|
[postgres]
|
||||||
|
user = "pelletier"
|
||||||
|
password = "mypassword"`)
|
||||||
|
|
||||||
|
config := Config{}
|
||||||
|
toml.Unmarshal(doc, &config)
|
||||||
|
fmt.Println("user=", config.Postgres.User)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use a query:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// use a query to gather elements without walking the tree
|
||||||
|
q, _ := query.Compile("$..[user,password]")
|
||||||
|
results := q.Execute(config)
|
||||||
|
for ii, item := range results.Values() {
|
||||||
|
fmt.Println("Query result %d: %v", ii, item)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The documentation and additional examples are available at
|
||||||
|
[godoc.org](http://godoc.org/github.com/pelletier/go-toml).
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
Go-toml provides two handy command line tools:
|
||||||
|
|
||||||
|
* `tomll`: Reads TOML files and lint them.
|
||||||
|
|
||||||
|
```
|
||||||
|
go install github.com/pelletier/go-toml/cmd/tomll
|
||||||
|
tomll --help
|
||||||
|
```
|
||||||
|
* `tomljson`: Reads a TOML file and outputs its JSON representation.
|
||||||
|
|
||||||
|
```
|
||||||
|
go install github.com/pelletier/go-toml/cmd/tomljson
|
||||||
|
tomljson --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
Feel free to report bugs and patches using GitHub's pull requests system on
|
||||||
|
[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be
|
||||||
|
much appreciated!
|
||||||
|
|
||||||
|
### Run tests
|
||||||
|
|
||||||
|
You have to make sure two kind of tests run:
|
||||||
|
|
||||||
|
1. The Go unit tests
|
||||||
|
2. The TOML examples base
|
||||||
|
|
||||||
|
You can run both of them using `./test.sh`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT). Read [LICENSE](LICENSE).
|
|
@ -0,0 +1,164 @@
|
||||||
|
{
|
||||||
|
"array": {
|
||||||
|
"key1": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"key2": [
|
||||||
|
"red",
|
||||||
|
"yellow",
|
||||||
|
"green"
|
||||||
|
],
|
||||||
|
"key3": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"key4": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"key5": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"key6": [
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"boolean": {
|
||||||
|
"False": false,
|
||||||
|
"True": true
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"key1": "1979-05-27T07:32:00Z",
|
||||||
|
"key2": "1979-05-27T00:32:00-07:00",
|
||||||
|
"key3": "1979-05-27T00:32:00.999999-07:00"
|
||||||
|
},
|
||||||
|
"float": {
|
||||||
|
"both": {
|
||||||
|
"key": 6.626e-34
|
||||||
|
},
|
||||||
|
"exponent": {
|
||||||
|
"key1": 5e+22,
|
||||||
|
"key2": 1000000,
|
||||||
|
"key3": -0.02
|
||||||
|
},
|
||||||
|
"fractional": {
|
||||||
|
"key1": 1,
|
||||||
|
"key2": 3.1415,
|
||||||
|
"key3": -0.01
|
||||||
|
},
|
||||||
|
"underscores": {
|
||||||
|
"key1": 9224617.445991227,
|
||||||
|
"key2": 1e+100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fruit": [{
|
||||||
|
"name": "apple",
|
||||||
|
"physical": {
|
||||||
|
"color": "red",
|
||||||
|
"shape": "round"
|
||||||
|
},
|
||||||
|
"variety": [{
|
||||||
|
"name": "red delicious"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "granny smith"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "banana",
|
||||||
|
"variety": [{
|
||||||
|
"name": "plantain"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"integer": {
|
||||||
|
"key1": 99,
|
||||||
|
"key2": 42,
|
||||||
|
"key3": 0,
|
||||||
|
"key4": -17,
|
||||||
|
"underscores": {
|
||||||
|
"key1": 1000,
|
||||||
|
"key2": 5349221,
|
||||||
|
"key3": 12345
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"products": [{
|
||||||
|
"name": "Hammer",
|
||||||
|
"sku": 738594937
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"color": "gray",
|
||||||
|
"name": "Nail",
|
||||||
|
"sku": 284758393
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"string": {
|
||||||
|
"basic": {
|
||||||
|
"basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
|
||||||
|
},
|
||||||
|
"literal": {
|
||||||
|
"multiline": {
|
||||||
|
"lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n",
|
||||||
|
"regex2": "I [dw]on't need \\d{2} apples"
|
||||||
|
},
|
||||||
|
"quoted": "Tom \"Dubs\" Preston-Werner",
|
||||||
|
"regex": "\u003c\\i\\c*\\s*\u003e",
|
||||||
|
"winpath": "C:\\Users\\nodejs\\templates",
|
||||||
|
"winpath2": "\\\\ServerX\\admin$\\system32\\"
|
||||||
|
},
|
||||||
|
"multiline": {
|
||||||
|
"continued": {
|
||||||
|
"key1": "The quick brown fox jumps over the lazy dog.",
|
||||||
|
"key2": "The quick brown fox jumps over the lazy dog.",
|
||||||
|
"key3": "The quick brown fox jumps over the lazy dog."
|
||||||
|
},
|
||||||
|
"key1": "One\nTwo",
|
||||||
|
"key2": "One\nTwo",
|
||||||
|
"key3": "One\nTwo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"inline": {
|
||||||
|
"name": {
|
||||||
|
"first": "Tom",
|
||||||
|
"last": "Preston-Werner"
|
||||||
|
},
|
||||||
|
"point": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"key": "value",
|
||||||
|
"subtable": {
|
||||||
|
"key": "another value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"y": {
|
||||||
|
"z": {
|
||||||
|
"w": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
reference_ref=${1:-master}
|
||||||
|
reference_git=${2:-.}
|
||||||
|
|
||||||
|
if ! `hash benchstat 2>/dev/null`; then
|
||||||
|
echo "Installing benchstat"
|
||||||
|
go get golang.org/x/perf/cmd/benchstat
|
||||||
|
go install golang.org/x/perf/cmd/benchstat
|
||||||
|
fi
|
||||||
|
|
||||||
|
tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX`
|
||||||
|
ref_tempdir="${tempdir}/ref"
|
||||||
|
ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt"
|
||||||
|
local_benchmark="`pwd`/benchmark-local.txt"
|
||||||
|
|
||||||
|
echo "=== ${reference_ref} (${ref_tempdir})"
|
||||||
|
git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null
|
||||||
|
pushd ${ref_tempdir} >/dev/null
|
||||||
|
git checkout ${reference_ref} >/dev/null 2>/dev/null
|
||||||
|
go test -bench=. -benchmem | tee ${ref_benchmark}
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== local"
|
||||||
|
go test -bench=. -benchmem | tee ${local_benchmark}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== diff"
|
||||||
|
benchstat -delta-test=none ${ref_benchmark} ${local_benchmark}
|
|
@ -0,0 +1,244 @@
|
||||||
|
################################################################################
|
||||||
|
## Comment
|
||||||
|
|
||||||
|
# Speak your mind with the hash symbol. They go from the symbol to the end of
|
||||||
|
# the line.
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Table
|
||||||
|
|
||||||
|
# Tables (also known as hash tables or dictionaries) are collections of
|
||||||
|
# key/value pairs. They appear in square brackets on a line by themselves.
|
||||||
|
|
||||||
|
[table]
|
||||||
|
|
||||||
|
key = "value" # Yeah, you can do this.
|
||||||
|
|
||||||
|
# Nested tables are denoted by table names with dots in them. Name your tables
|
||||||
|
# whatever crap you please, just don't use #, ., [ or ].
|
||||||
|
|
||||||
|
[table.subtable]
|
||||||
|
|
||||||
|
key = "another value"
|
||||||
|
|
||||||
|
# You don't need to specify all the super-tables if you don't want to. TOML
|
||||||
|
# knows how to do it for you.
|
||||||
|
|
||||||
|
# [x] you
|
||||||
|
# [x.y] don't
|
||||||
|
# [x.y.z] need these
|
||||||
|
[x.y.z.w] # for this to work
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Inline Table
|
||||||
|
|
||||||
|
# Inline tables provide a more compact syntax for expressing tables. They are
|
||||||
|
# especially useful for grouped data that can otherwise quickly become verbose.
|
||||||
|
# Inline tables are enclosed in curly braces `{` and `}`. No newlines are
|
||||||
|
# allowed between the curly braces unless they are valid within a value.
|
||||||
|
|
||||||
|
[table.inline]
|
||||||
|
|
||||||
|
name = { first = "Tom", last = "Preston-Werner" }
|
||||||
|
point = { x = 1, y = 2 }
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## String
|
||||||
|
|
||||||
|
# There are four ways to express strings: basic, multi-line basic, literal, and
|
||||||
|
# multi-line literal. All strings must contain only valid UTF-8 characters.
|
||||||
|
|
||||||
|
[string.basic]
|
||||||
|
|
||||||
|
basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
|
||||||
|
|
||||||
|
[string.multiline]
|
||||||
|
|
||||||
|
# The following strings are byte-for-byte equivalent:
|
||||||
|
key1 = "One\nTwo"
|
||||||
|
key2 = """One\nTwo"""
|
||||||
|
key3 = """
|
||||||
|
One
|
||||||
|
Two"""
|
||||||
|
|
||||||
|
[string.multiline.continued]
|
||||||
|
|
||||||
|
# The following strings are byte-for-byte equivalent:
|
||||||
|
key1 = "The quick brown fox jumps over the lazy dog."
|
||||||
|
|
||||||
|
key2 = """
|
||||||
|
The quick brown \
|
||||||
|
|
||||||
|
|
||||||
|
fox jumps over \
|
||||||
|
the lazy dog."""
|
||||||
|
|
||||||
|
key3 = """\
|
||||||
|
The quick brown \
|
||||||
|
fox jumps over \
|
||||||
|
the lazy dog.\
|
||||||
|
"""
|
||||||
|
|
||||||
|
[string.literal]
|
||||||
|
|
||||||
|
# What you see is what you get.
|
||||||
|
winpath = 'C:\Users\nodejs\templates'
|
||||||
|
winpath2 = '\\ServerX\admin$\system32\'
|
||||||
|
quoted = 'Tom "Dubs" Preston-Werner'
|
||||||
|
regex = '<\i\c*\s*>'
|
||||||
|
|
||||||
|
|
||||||
|
[string.literal.multiline]
|
||||||
|
|
||||||
|
regex2 = '''I [dw]on't need \d{2} apples'''
|
||||||
|
lines = '''
|
||||||
|
The first newline is
|
||||||
|
trimmed in raw strings.
|
||||||
|
All other whitespace
|
||||||
|
is preserved.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Integer
|
||||||
|
|
||||||
|
# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
|
||||||
|
# Negative numbers are prefixed with a minus sign.
|
||||||
|
|
||||||
|
[integer]
|
||||||
|
|
||||||
|
key1 = +99
|
||||||
|
key2 = 42
|
||||||
|
key3 = 0
|
||||||
|
key4 = -17
|
||||||
|
|
||||||
|
[integer.underscores]
|
||||||
|
|
||||||
|
# For large numbers, you may use underscores to enhance readability. Each
|
||||||
|
# underscore must be surrounded by at least one digit.
|
||||||
|
key1 = 1_000
|
||||||
|
key2 = 5_349_221
|
||||||
|
key3 = 1_2_3_4_5 # valid but inadvisable
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Float
|
||||||
|
|
||||||
|
# A float consists of an integer part (which may be prefixed with a plus or
|
||||||
|
# minus sign) followed by a fractional part and/or an exponent part.
|
||||||
|
|
||||||
|
[float.fractional]
|
||||||
|
|
||||||
|
key1 = +1.0
|
||||||
|
key2 = 3.1415
|
||||||
|
key3 = -0.01
|
||||||
|
|
||||||
|
[float.exponent]
|
||||||
|
|
||||||
|
key1 = 5e+22
|
||||||
|
key2 = 1e6
|
||||||
|
key3 = -2E-2
|
||||||
|
|
||||||
|
[float.both]
|
||||||
|
|
||||||
|
key = 6.626e-34
|
||||||
|
|
||||||
|
[float.underscores]
|
||||||
|
|
||||||
|
key1 = 9_224_617.445_991_228_313
|
||||||
|
key2 = 1e1_00
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Boolean
|
||||||
|
|
||||||
|
# Booleans are just the tokens you're used to. Always lowercase.
|
||||||
|
|
||||||
|
[boolean]
|
||||||
|
|
||||||
|
True = true
|
||||||
|
False = false
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Datetime
|
||||||
|
|
||||||
|
# Datetimes are RFC 3339 dates.
|
||||||
|
|
||||||
|
[datetime]
|
||||||
|
|
||||||
|
key1 = 1979-05-27T07:32:00Z
|
||||||
|
key2 = 1979-05-27T00:32:00-07:00
|
||||||
|
key3 = 1979-05-27T00:32:00.999999-07:00
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Array
|
||||||
|
|
||||||
|
# Arrays are square brackets with other primitives inside. Whitespace is
|
||||||
|
# ignored. Elements are separated by commas. Data types may not be mixed.
|
||||||
|
|
||||||
|
[array]
|
||||||
|
|
||||||
|
key1 = [ 1, 2, 3 ]
|
||||||
|
key2 = [ "red", "yellow", "green" ]
|
||||||
|
key3 = [ [ 1, 2 ], [3, 4, 5] ]
|
||||||
|
#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
|
||||||
|
|
||||||
|
# Arrays can also be multiline. So in addition to ignoring whitespace, arrays
|
||||||
|
# also ignore newlines between the brackets. Terminating commas are ok before
|
||||||
|
# the closing bracket.
|
||||||
|
|
||||||
|
key5 = [
|
||||||
|
1, 2, 3
|
||||||
|
]
|
||||||
|
key6 = [
|
||||||
|
1,
|
||||||
|
2, # this is ok
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Array of Tables
|
||||||
|
|
||||||
|
# These can be expressed by using a table name in double brackets. Each table
|
||||||
|
# with the same double bracketed name will be an element in the array. The
|
||||||
|
# tables are inserted in the order encountered.
|
||||||
|
|
||||||
|
[[products]]
|
||||||
|
|
||||||
|
name = "Hammer"
|
||||||
|
sku = 738594937
|
||||||
|
|
||||||
|
[[products]]
|
||||||
|
|
||||||
|
[[products]]
|
||||||
|
|
||||||
|
name = "Nail"
|
||||||
|
sku = 284758393
|
||||||
|
color = "gray"
|
||||||
|
|
||||||
|
|
||||||
|
# You can create nested arrays of tables as well.
|
||||||
|
|
||||||
|
[[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"
|
|
@ -0,0 +1,121 @@
|
||||||
|
---
|
||||||
|
array:
|
||||||
|
key1:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
key2:
|
||||||
|
- red
|
||||||
|
- yellow
|
||||||
|
- green
|
||||||
|
key3:
|
||||||
|
- - 1
|
||||||
|
- 2
|
||||||
|
- - 3
|
||||||
|
- 4
|
||||||
|
- 5
|
||||||
|
key4:
|
||||||
|
- - 1
|
||||||
|
- 2
|
||||||
|
- - a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
key5:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
key6:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
boolean:
|
||||||
|
'False': false
|
||||||
|
'True': true
|
||||||
|
datetime:
|
||||||
|
key1: '1979-05-27T07:32:00Z'
|
||||||
|
key2: '1979-05-27T00:32:00-07:00'
|
||||||
|
key3: '1979-05-27T00:32:00.999999-07:00'
|
||||||
|
float:
|
||||||
|
both:
|
||||||
|
key: 6.626e-34
|
||||||
|
exponent:
|
||||||
|
key1: 5.0e+22
|
||||||
|
key2: 1000000
|
||||||
|
key3: -0.02
|
||||||
|
fractional:
|
||||||
|
key1: 1
|
||||||
|
key2: 3.1415
|
||||||
|
key3: -0.01
|
||||||
|
underscores:
|
||||||
|
key1: 9224617.445991227
|
||||||
|
key2: 1.0e+100
|
||||||
|
fruit:
|
||||||
|
- name: apple
|
||||||
|
physical:
|
||||||
|
color: red
|
||||||
|
shape: round
|
||||||
|
variety:
|
||||||
|
- name: red delicious
|
||||||
|
- name: granny smith
|
||||||
|
- name: banana
|
||||||
|
variety:
|
||||||
|
- name: plantain
|
||||||
|
integer:
|
||||||
|
key1: 99
|
||||||
|
key2: 42
|
||||||
|
key3: 0
|
||||||
|
key4: -17
|
||||||
|
underscores:
|
||||||
|
key1: 1000
|
||||||
|
key2: 5349221
|
||||||
|
key3: 12345
|
||||||
|
products:
|
||||||
|
- name: Hammer
|
||||||
|
sku: 738594937
|
||||||
|
- {}
|
||||||
|
- color: gray
|
||||||
|
name: Nail
|
||||||
|
sku: 284758393
|
||||||
|
string:
|
||||||
|
basic:
|
||||||
|
basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
|
||||||
|
literal:
|
||||||
|
multiline:
|
||||||
|
lines: |
|
||||||
|
The first newline is
|
||||||
|
trimmed in raw strings.
|
||||||
|
All other whitespace
|
||||||
|
is preserved.
|
||||||
|
regex2: I [dw]on't need \d{2} apples
|
||||||
|
quoted: Tom "Dubs" Preston-Werner
|
||||||
|
regex: "<\\i\\c*\\s*>"
|
||||||
|
winpath: C:\Users\nodejs\templates
|
||||||
|
winpath2: "\\\\ServerX\\admin$\\system32\\"
|
||||||
|
multiline:
|
||||||
|
continued:
|
||||||
|
key1: The quick brown fox jumps over the lazy dog.
|
||||||
|
key2: The quick brown fox jumps over the lazy dog.
|
||||||
|
key3: The quick brown fox jumps over the lazy dog.
|
||||||
|
key1: |-
|
||||||
|
One
|
||||||
|
Two
|
||||||
|
key2: |-
|
||||||
|
One
|
||||||
|
Two
|
||||||
|
key3: |-
|
||||||
|
One
|
||||||
|
Two
|
||||||
|
table:
|
||||||
|
inline:
|
||||||
|
name:
|
||||||
|
first: Tom
|
||||||
|
last: Preston-Werner
|
||||||
|
point:
|
||||||
|
x: 1
|
||||||
|
y: 2
|
||||||
|
key: value
|
||||||
|
subtable:
|
||||||
|
key: another value
|
||||||
|
x:
|
||||||
|
y:
|
||||||
|
z:
|
||||||
|
w: {}
|
|
@ -0,0 +1,192 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
burntsushi "github.com/BurntSushi/toml"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type benchmarkDoc struct {
|
||||||
|
Table struct {
|
||||||
|
Key string
|
||||||
|
Subtable struct {
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
Inline struct {
|
||||||
|
Name struct {
|
||||||
|
First string
|
||||||
|
Last string
|
||||||
|
}
|
||||||
|
Point struct {
|
||||||
|
X int64
|
||||||
|
U int64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String struct {
|
||||||
|
Basic struct {
|
||||||
|
Basic string
|
||||||
|
}
|
||||||
|
Multiline struct {
|
||||||
|
Key1 string
|
||||||
|
Key2 string
|
||||||
|
Key3 string
|
||||||
|
Continued struct {
|
||||||
|
Key1 string
|
||||||
|
Key2 string
|
||||||
|
Key3 string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Literal struct {
|
||||||
|
Winpath string
|
||||||
|
Winpath2 string
|
||||||
|
Quoted string
|
||||||
|
Regex string
|
||||||
|
Multiline struct {
|
||||||
|
Regex2 string
|
||||||
|
Lines string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Integer struct {
|
||||||
|
Key1 int64
|
||||||
|
Key2 int64
|
||||||
|
Key3 int64
|
||||||
|
Key4 int64
|
||||||
|
Underscores struct {
|
||||||
|
Key1 int64
|
||||||
|
Key2 int64
|
||||||
|
Key3 int64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Float struct {
|
||||||
|
Fractional struct {
|
||||||
|
Key1 float64
|
||||||
|
Key2 float64
|
||||||
|
Key3 float64
|
||||||
|
}
|
||||||
|
Exponent struct {
|
||||||
|
Key1 float64
|
||||||
|
Key2 float64
|
||||||
|
Key3 float64
|
||||||
|
}
|
||||||
|
Both struct {
|
||||||
|
Key float64
|
||||||
|
}
|
||||||
|
Underscores struct {
|
||||||
|
Key1 float64
|
||||||
|
Key2 float64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Boolean struct {
|
||||||
|
True bool
|
||||||
|
False bool
|
||||||
|
}
|
||||||
|
Datetime struct {
|
||||||
|
Key1 time.Time
|
||||||
|
Key2 time.Time
|
||||||
|
Key3 time.Time
|
||||||
|
}
|
||||||
|
Array struct {
|
||||||
|
Key1 []int64
|
||||||
|
Key2 []string
|
||||||
|
Key3 [][]int64
|
||||||
|
// TODO: Key4 not supported by go-toml's Unmarshal
|
||||||
|
Key5 []int64
|
||||||
|
Key6 []int64
|
||||||
|
}
|
||||||
|
Products []struct {
|
||||||
|
Name string
|
||||||
|
Sku int64
|
||||||
|
Color string
|
||||||
|
}
|
||||||
|
Fruit []struct {
|
||||||
|
Name string
|
||||||
|
Physical struct {
|
||||||
|
Color string
|
||||||
|
Shape string
|
||||||
|
Variety []struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseToml(b *testing.B) {
|
||||||
|
fileBytes, err := ioutil.ReadFile("benchmark.toml")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := LoadReader(bytes.NewReader(fileBytes))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalToml(b *testing.B) {
|
||||||
|
bytes, err := ioutil.ReadFile("benchmark.toml")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := benchmarkDoc{}
|
||||||
|
err := Unmarshal(bytes, &target)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBurntSushiToml(b *testing.B) {
|
||||||
|
bytes, err := ioutil.ReadFile("benchmark.toml")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := benchmarkDoc{}
|
||||||
|
err := burntsushi.Unmarshal(bytes, &target)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalJson(b *testing.B) {
|
||||||
|
bytes, err := ioutil.ReadFile("benchmark.json")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := benchmarkDoc{}
|
||||||
|
err := json.Unmarshal(bytes, &target)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalYaml(b *testing.B) {
|
||||||
|
bytes, err := ioutil.ReadFile("benchmark.yml")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := benchmarkDoc{}
|
||||||
|
err := yaml.Unmarshal(bytes, &target)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// code examples for godoc
|
||||||
|
|
||||||
|
package toml_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
toml "github.com/pelletier/go-toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_tree() {
|
||||||
|
config, err := toml.LoadFile("config.toml")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error ", err.Error())
|
||||||
|
} else {
|
||||||
|
// retrieve data directly
|
||||||
|
user := config.Get("postgres.user").(string)
|
||||||
|
password := config.Get("postgres.password").(string)
|
||||||
|
|
||||||
|
// or using an intermediate object
|
||||||
|
configTree := config.Get("postgres").(*toml.Tree)
|
||||||
|
user = configTree.Get("user").(string)
|
||||||
|
password = configTree.Get("password").(string)
|
||||||
|
fmt.Println("User is", user, " and password is", password)
|
||||||
|
|
||||||
|
// show where elements are in the file
|
||||||
|
fmt.Printf("User position: %v\n", configTree.GetPosition("user"))
|
||||||
|
fmt.Printf("Password position: %v\n", configTree.GetPosition("password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_unmarshal() {
|
||||||
|
type Employer struct {
|
||||||
|
Name string
|
||||||
|
Phone string
|
||||||
|
}
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int64
|
||||||
|
Employer Employer
|
||||||
|
}
|
||||||
|
|
||||||
|
document := []byte(`
|
||||||
|
name = "John"
|
||||||
|
age = 30
|
||||||
|
[employer]
|
||||||
|
name = "Company Inc."
|
||||||
|
phone = "+1 234 567 89012"
|
||||||
|
`)
|
||||||
|
|
||||||
|
person := Person{}
|
||||||
|
toml.Unmarshal(document, &person)
|
||||||
|
fmt.Println(person.Name, "is", person.Age, "and works at", person.Employer.Name)
|
||||||
|
// Output:
|
||||||
|
// John is 30 and works at Company Inc.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMarshal() {
|
||||||
|
type Postgres struct {
|
||||||
|
User string `toml:"user"`
|
||||||
|
Password string `toml:"password"`
|
||||||
|
}
|
||||||
|
type Config struct {
|
||||||
|
Postgres Postgres `toml:"postgres"`
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{Postgres{User: "pelletier", Password: "mypassword"}}
|
||||||
|
b, err := toml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
// Output:
|
||||||
|
// [postgres]
|
||||||
|
// password = "mypassword"
|
||||||
|
// user = "pelletier"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleUnmarshal() {
|
||||||
|
type Postgres struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
type Config struct {
|
||||||
|
Postgres Postgres
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := []byte(`
|
||||||
|
[postgres]
|
||||||
|
user = "pelletier"
|
||||||
|
password = "mypassword"`)
|
||||||
|
|
||||||
|
config := Config{}
|
||||||
|
toml.Unmarshal(doc, &config)
|
||||||
|
fmt.Println("user=", config.Postgres.User)
|
||||||
|
// Output:
|
||||||
|
// user= pelletier
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
# This is a TOML document. Boom.
|
||||||
|
|
||||||
|
title = "TOML Example"
|
||||||
|
|
||||||
|
[owner]
|
||||||
|
name = "Tom Preston-Werner"
|
||||||
|
organization = "GitHub"
|
||||||
|
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||||
|
dob = 1979-05-27T07:32:00Z # 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] ] # just an update to make sure parsers support it
|
|
@ -0,0 +1,29 @@
|
||||||
|
# This is a TOML document. Boom.
|
||||||
|
|
||||||
|
title = "TOML Example"
|
||||||
|
|
||||||
|
[owner]
|
||||||
|
name = "Tom Preston-Werner"
|
||||||
|
organization = "GitHub"
|
||||||
|
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||||
|
dob = 1979-05-27T07:32:00Z # 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] ] # just an update to make sure parsers support it
|
|
@ -0,0 +1,56 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testResult(t *testing.T, key string, expected []string) {
|
||||||
|
parsed, err := parseKey(key)
|
||||||
|
t.Logf("key=%s expected=%s parsed=%s", key, expected, parsed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
if len(expected) != len(parsed) {
|
||||||
|
t.Fatal("Expected length", len(expected), "but", len(parsed), "parsed")
|
||||||
|
}
|
||||||
|
for index, expectedKey := range expected {
|
||||||
|
if expectedKey != parsed[index] {
|
||||||
|
t.Fatal("Expected", expectedKey, "at index", index, "but found", parsed[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testError(t *testing.T, key string, expectedError string) {
|
||||||
|
_, err := parseKey(key)
|
||||||
|
if fmt.Sprintf("%s", err) != expectedError {
|
||||||
|
t.Fatalf("Expected error \"%s\", but got \"%s\".", expectedError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBareKeyBasic(t *testing.T) {
|
||||||
|
testResult(t, "test", []string{"test"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBareKeyDotted(t *testing.T) {
|
||||||
|
testResult(t, "this.is.a.key", []string{"this", "is", "a", "key"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDottedKeyBasic(t *testing.T) {
|
||||||
|
testResult(t, "\"a.dotted.key\"", []string{"a.dotted.key"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseKeyPound(t *testing.T) {
|
||||||
|
testError(t, "hello#world", "invalid bare character: #")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuotedKeys(t *testing.T) {
|
||||||
|
testResult(t, `hello."foo".bar`, []string{"hello", "foo", "bar"})
|
||||||
|
testResult(t, `"hello!"`, []string{"hello!"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyKey(t *testing.T) {
|
||||||
|
testError(t, "", "empty key")
|
||||||
|
testError(t, " ", "empty key")
|
||||||
|
testResult(t, `""`, []string{""})
|
||||||
|
}
|
|
@ -9,12 +9,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pelletier/go-buffruneio"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var dateRegexp *regexp.Regexp
|
var dateRegexp *regexp.Regexp
|
||||||
|
@ -24,9 +21,11 @@ type tomlLexStateFn func() tomlLexStateFn
|
||||||
|
|
||||||
// Define lexer
|
// Define lexer
|
||||||
type tomlLexer struct {
|
type tomlLexer struct {
|
||||||
input *buffruneio.Reader // Textual source
|
inputIdx int
|
||||||
buffer bytes.Buffer // Runes composing the current token
|
input []rune // Textual source
|
||||||
tokens chan token
|
currentTokenStart int
|
||||||
|
currentTokenStop int
|
||||||
|
tokens []token
|
||||||
depth int
|
depth int
|
||||||
line int
|
line int
|
||||||
col int
|
col int
|
||||||
|
@ -37,16 +36,14 @@ type tomlLexer struct {
|
||||||
// Basic read operations on input
|
// Basic read operations on input
|
||||||
|
|
||||||
func (l *tomlLexer) read() rune {
|
func (l *tomlLexer) read() rune {
|
||||||
r, _, err := l.input.ReadRune()
|
r := l.peek()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if r == '\n' {
|
if r == '\n' {
|
||||||
l.endbufferLine++
|
l.endbufferLine++
|
||||||
l.endbufferCol = 1
|
l.endbufferCol = 1
|
||||||
} else {
|
} else {
|
||||||
l.endbufferCol++
|
l.endbufferCol++
|
||||||
}
|
}
|
||||||
|
l.inputIdx++
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,13 +51,13 @@ func (l *tomlLexer) next() rune {
|
||||||
r := l.read()
|
r := l.read()
|
||||||
|
|
||||||
if r != eof {
|
if r != eof {
|
||||||
l.buffer.WriteRune(r)
|
l.currentTokenStop++
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) ignore() {
|
func (l *tomlLexer) ignore() {
|
||||||
l.buffer.Reset()
|
l.currentTokenStart = l.currentTokenStop
|
||||||
l.line = l.endbufferLine
|
l.line = l.endbufferLine
|
||||||
l.col = l.endbufferCol
|
l.col = l.endbufferCol
|
||||||
}
|
}
|
||||||
|
@ -77,49 +74,46 @@ func (l *tomlLexer) fastForward(n int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) emitWithValue(t tokenType, value string) {
|
func (l *tomlLexer) emitWithValue(t tokenType, value string) {
|
||||||
l.tokens <- token{
|
l.tokens = append(l.tokens, token{
|
||||||
Position: Position{l.line, l.col},
|
Position: Position{l.line, l.col},
|
||||||
typ: t,
|
typ: t,
|
||||||
val: value,
|
val: value,
|
||||||
}
|
})
|
||||||
l.ignore()
|
l.ignore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) emit(t tokenType) {
|
func (l *tomlLexer) emit(t tokenType) {
|
||||||
l.emitWithValue(t, l.buffer.String())
|
l.emitWithValue(t, string(l.input[l.currentTokenStart:l.currentTokenStop]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) peek() rune {
|
func (l *tomlLexer) peek() rune {
|
||||||
r, _, err := l.input.ReadRune()
|
if l.inputIdx >= len(l.input) {
|
||||||
if err != nil {
|
return eof
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
l.input.UnreadRune()
|
return l.input[l.inputIdx]
|
||||||
return r
|
}
|
||||||
|
|
||||||
|
func (l *tomlLexer) peekString(size int) string {
|
||||||
|
maxIdx := len(l.input)
|
||||||
|
upperIdx := l.inputIdx + size // FIXME: potential overflow
|
||||||
|
if upperIdx > maxIdx {
|
||||||
|
upperIdx = maxIdx
|
||||||
|
}
|
||||||
|
return string(l.input[l.inputIdx:upperIdx])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) follow(next string) bool {
|
func (l *tomlLexer) follow(next string) bool {
|
||||||
for _, expectedRune := range next {
|
return next == l.peekString(len(next))
|
||||||
r, _, err := l.input.ReadRune()
|
|
||||||
defer l.input.UnreadRune()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if expectedRune != r {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error management
|
// Error management
|
||||||
|
|
||||||
func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn {
|
func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn {
|
||||||
l.tokens <- token{
|
l.tokens = append(l.tokens, token{
|
||||||
Position: Position{l.line, l.col},
|
Position: Position{l.line, l.col},
|
||||||
typ: tokenError,
|
typ: tokenError,
|
||||||
val: fmt.Sprintf(format, args...),
|
val: fmt.Sprintf(format, args...),
|
||||||
}
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +214,7 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
possibleDate := string(l.input.PeekRunes(35))
|
possibleDate := l.peekString(35)
|
||||||
dateMatch := dateRegexp.FindString(possibleDate)
|
dateMatch := dateRegexp.FindString(possibleDate)
|
||||||
if dateMatch != "" {
|
if dateMatch != "" {
|
||||||
l.fastForward(len(dateMatch))
|
l.fastForward(len(dateMatch))
|
||||||
|
@ -537,7 +531,7 @@ func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn {
|
||||||
for r := l.peek(); r != eof; r = l.peek() {
|
for r := l.peek(); r != eof; r = l.peek() {
|
||||||
switch r {
|
switch r {
|
||||||
case ']':
|
case ']':
|
||||||
if l.buffer.Len() > 0 {
|
if l.currentTokenStop > l.currentTokenStart {
|
||||||
l.emit(tokenKeyGroupArray)
|
l.emit(tokenKeyGroupArray)
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
|
@ -560,7 +554,7 @@ func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn {
|
||||||
for r := l.peek(); r != eof; r = l.peek() {
|
for r := l.peek(); r != eof; r = l.peek() {
|
||||||
switch r {
|
switch r {
|
||||||
case ']':
|
case ']':
|
||||||
if l.buffer.Len() > 0 {
|
if l.currentTokenStop > l.currentTokenStart {
|
||||||
l.emit(tokenKeyGroup)
|
l.emit(tokenKeyGroup)
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
|
@ -635,7 +629,6 @@ func (l *tomlLexer) run() {
|
||||||
for state := l.lexVoid; state != nil; {
|
for state := l.lexVoid; state != nil; {
|
||||||
state = state()
|
state = state()
|
||||||
}
|
}
|
||||||
close(l.tokens)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -643,16 +636,16 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
func lexToml(input io.Reader) chan token {
|
func lexToml(inputBytes []byte) []token {
|
||||||
bufferedInput := buffruneio.NewReader(input)
|
runes := bytes.Runes(inputBytes)
|
||||||
l := &tomlLexer{
|
l := &tomlLexer{
|
||||||
input: bufferedInput,
|
input: runes,
|
||||||
tokens: make(chan token),
|
tokens: make([]token, 0, 256),
|
||||||
line: 1,
|
line: 1,
|
||||||
col: 1,
|
col: 1,
|
||||||
endbufferLine: 1,
|
endbufferLine: 1,
|
||||||
endbufferCol: 1,
|
endbufferCol: 1,
|
||||||
}
|
}
|
||||||
go l.run()
|
l.run()
|
||||||
return l.tokens
|
return l.tokens
|
||||||
}
|
}
|
|
@ -0,0 +1,750 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testFlow(t *testing.T, input string, expectedFlow []token) {
|
||||||
|
tokens := lexToml([]byte(input))
|
||||||
|
if !reflect.DeepEqual(tokens, expectedFlow) {
|
||||||
|
t.Fatal("Different flows. Expected\n", expectedFlow, "\nGot:\n", tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidKeyGroup(t *testing.T) {
|
||||||
|
testFlow(t, "[hello world]", []token{
|
||||||
|
{Position{1, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 2}, tokenKeyGroup, "hello world"},
|
||||||
|
{Position{1, 13}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 14}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedQuotedUnicodeKeyGroup(t *testing.T) {
|
||||||
|
testFlow(t, `[ j . "ʞ" . l ]`, []token{
|
||||||
|
{Position{1, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 2}, tokenKeyGroup, ` j . "ʞ" . l `},
|
||||||
|
{Position{1, 15}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnclosedKeyGroup(t *testing.T) {
|
||||||
|
testFlow(t, "[hello world", []token{
|
||||||
|
{Position{1, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 2}, tokenError, "unclosed table key"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComment(t *testing.T) {
|
||||||
|
testFlow(t, "# blahblah", []token{
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyGroupComment(t *testing.T) {
|
||||||
|
testFlow(t, "[hello world] # blahblah", []token{
|
||||||
|
{Position{1, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 2}, tokenKeyGroup, "hello world"},
|
||||||
|
{Position{1, 13}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 25}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleKeyGroupsComment(t *testing.T) {
|
||||||
|
testFlow(t, "[hello world] # blahblah\n[test]", []token{
|
||||||
|
{Position{1, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 2}, tokenKeyGroup, "hello world"},
|
||||||
|
{Position{1, 13}, tokenRightBracket, "]"},
|
||||||
|
{Position{2, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{2, 2}, tokenKeyGroup, "test"},
|
||||||
|
{Position{2, 6}, tokenRightBracket, "]"},
|
||||||
|
{Position{2, 7}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleWindowsCRLF(t *testing.T) {
|
||||||
|
testFlow(t, "a=4\r\nb=2", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 2}, tokenEqual, "="},
|
||||||
|
{Position{1, 3}, tokenInteger, "4"},
|
||||||
|
{Position{2, 1}, tokenKey, "b"},
|
||||||
|
{Position{2, 2}, tokenEqual, "="},
|
||||||
|
{Position{2, 3}, tokenInteger, "2"},
|
||||||
|
{Position{2, 4}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKey(t *testing.T) {
|
||||||
|
testFlow(t, "hello", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "hello"},
|
||||||
|
{Position{1, 6}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKeyWithUnderscore(t *testing.T) {
|
||||||
|
testFlow(t, "hello_hello", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "hello_hello"},
|
||||||
|
{Position{1, 12}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKeyWithDash(t *testing.T) {
|
||||||
|
testFlow(t, "hello-world", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "hello-world"},
|
||||||
|
{Position{1, 12}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKeyWithUppercaseMix(t *testing.T) {
|
||||||
|
testFlow(t, "helloHELLOHello", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "helloHELLOHello"},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKeyWithInternationalCharacters(t *testing.T) {
|
||||||
|
testFlow(t, "héllÖ", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "héllÖ"},
|
||||||
|
{Position{1, 6}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKeyAndEqual(t *testing.T) {
|
||||||
|
testFlow(t, "hello =", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "hello"},
|
||||||
|
{Position{1, 7}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyWithSharpAndEqual(t *testing.T) {
|
||||||
|
testFlow(t, "key#name = 5", []token{
|
||||||
|
{Position{1, 1}, tokenError, "keys cannot contain # character"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyWithSymbolsAndEqual(t *testing.T) {
|
||||||
|
testFlow(t, "~!@$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{
|
||||||
|
{Position{1, 1}, tokenError, "keys cannot contain ~ character"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualStringEscape(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "hello\""`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "hello\""},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualStringUnfinished(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "bar`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unclosed string"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "bar"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "bar"},
|
||||||
|
{Position{1, 12}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualTrue(t *testing.T) {
|
||||||
|
testFlow(t, "foo = true", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenTrue, "true"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualFalse(t *testing.T) {
|
||||||
|
testFlow(t, "foo = false", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenFalse, "false"},
|
||||||
|
{Position{1, 12}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayNestedString(t *testing.T) {
|
||||||
|
testFlow(t, `a = [ ["hello", "world"] ]`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 7}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 9}, tokenString, "hello"},
|
||||||
|
{Position{1, 15}, tokenComma, ","},
|
||||||
|
{Position{1, 18}, tokenString, "world"},
|
||||||
|
{Position{1, 24}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 26}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 27}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayNestedInts(t *testing.T) {
|
||||||
|
testFlow(t, "a = [ [42, 21], [10] ]", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 7}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 8}, tokenInteger, "42"},
|
||||||
|
{Position{1, 10}, tokenComma, ","},
|
||||||
|
{Position{1, 12}, tokenInteger, "21"},
|
||||||
|
{Position{1, 14}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 15}, tokenComma, ","},
|
||||||
|
{Position{1, 17}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 18}, tokenInteger, "10"},
|
||||||
|
{Position{1, 20}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 22}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 23}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayInts(t *testing.T) {
|
||||||
|
testFlow(t, "a = [ 42, 21, 10, ]", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 7}, tokenInteger, "42"},
|
||||||
|
{Position{1, 9}, tokenComma, ","},
|
||||||
|
{Position{1, 11}, tokenInteger, "21"},
|
||||||
|
{Position{1, 13}, tokenComma, ","},
|
||||||
|
{Position{1, 15}, tokenInteger, "10"},
|
||||||
|
{Position{1, 17}, tokenComma, ","},
|
||||||
|
{Position{1, 19}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 20}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineArrayComments(t *testing.T) {
|
||||||
|
testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 6}, tokenInteger, "1"},
|
||||||
|
{Position{1, 7}, tokenComma, ","},
|
||||||
|
{Position{2, 1}, tokenInteger, "2"},
|
||||||
|
{Position{2, 2}, tokenComma, ","},
|
||||||
|
{Position{3, 1}, tokenInteger, "3"},
|
||||||
|
{Position{3, 2}, tokenComma, ","},
|
||||||
|
{Position{4, 1}, tokenRightBracket, "]"},
|
||||||
|
{Position{4, 2}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedArraysComment(t *testing.T) {
|
||||||
|
toml := `
|
||||||
|
someArray = [
|
||||||
|
# does not work
|
||||||
|
["entry1"]
|
||||||
|
]`
|
||||||
|
testFlow(t, toml, []token{
|
||||||
|
{Position{2, 1}, tokenKey, "someArray"},
|
||||||
|
{Position{2, 11}, tokenEqual, "="},
|
||||||
|
{Position{2, 13}, tokenLeftBracket, "["},
|
||||||
|
{Position{4, 1}, tokenLeftBracket, "["},
|
||||||
|
{Position{4, 3}, tokenString, "entry1"},
|
||||||
|
{Position{4, 10}, tokenRightBracket, "]"},
|
||||||
|
{Position{5, 1}, tokenRightBracket, "]"},
|
||||||
|
{Position{5, 2}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualArrayBools(t *testing.T) {
|
||||||
|
testFlow(t, "foo = [true, false, true]", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 8}, tokenTrue, "true"},
|
||||||
|
{Position{1, 12}, tokenComma, ","},
|
||||||
|
{Position{1, 14}, tokenFalse, "false"},
|
||||||
|
{Position{1, 19}, tokenComma, ","},
|
||||||
|
{Position{1, 21}, tokenTrue, "true"},
|
||||||
|
{Position{1, 25}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 26}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualArrayBoolsWithComments(t *testing.T) {
|
||||||
|
testFlow(t, "foo = [true, false, true] # YEAH", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 8}, tokenTrue, "true"},
|
||||||
|
{Position{1, 12}, tokenComma, ","},
|
||||||
|
{Position{1, 14}, tokenFalse, "false"},
|
||||||
|
{Position{1, 19}, tokenComma, ","},
|
||||||
|
{Position{1, 21}, tokenTrue, "true"},
|
||||||
|
{Position{1, 25}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 33}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateRegexp(t *testing.T) {
|
||||||
|
if dateRegexp.FindString("1979-05-27T07:32:00Z") == "" {
|
||||||
|
t.Error("basic lexing")
|
||||||
|
}
|
||||||
|
if dateRegexp.FindString("1979-05-27T00:32:00-07:00") == "" {
|
||||||
|
t.Error("offset lexing")
|
||||||
|
}
|
||||||
|
if dateRegexp.FindString("1979-05-27T00:32:00.999999-07:00") == "" {
|
||||||
|
t.Error("nano precision lexing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualDate(t *testing.T) {
|
||||||
|
testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenDate, "1979-05-27T07:32:00Z"},
|
||||||
|
{Position{1, 27}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, "foo = 1979-05-27T00:32:00-07:00", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenDate, "1979-05-27T00:32:00-07:00"},
|
||||||
|
{Position{1, 32}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, "foo = 1979-05-27T00:32:00.999999-07:00", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenDate, "1979-05-27T00:32:00.999999-07:00"},
|
||||||
|
{Position{1, 39}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatEndingWithDot(t *testing.T) {
|
||||||
|
testFlow(t, "foo = 42.", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenError, "float cannot end with a dot"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithTwoDots(t *testing.T) {
|
||||||
|
testFlow(t, "foo = 4.2.", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenError, "cannot have two dots in one float"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithExponent1(t *testing.T) {
|
||||||
|
testFlow(t, "a = 5e+22", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenFloat, "5e+22"},
|
||||||
|
{Position{1, 10}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithExponent2(t *testing.T) {
|
||||||
|
testFlow(t, "a = 5E+22", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenFloat, "5E+22"},
|
||||||
|
{Position{1, 10}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithExponent3(t *testing.T) {
|
||||||
|
testFlow(t, "a = -5e+22", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenFloat, "-5e+22"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithExponent4(t *testing.T) {
|
||||||
|
testFlow(t, "a = -5e-22", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenFloat, "-5e-22"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatWithExponent5(t *testing.T) {
|
||||||
|
testFlow(t, "a = 6.626e-34", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenFloat, "6.626e-34"},
|
||||||
|
{Position{1, 14}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidEsquapeSequence(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "\x"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "invalid escape sequence: \\x"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedArrays(t *testing.T) {
|
||||||
|
testFlow(t, "foo = [[[]]]", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 8}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 9}, tokenLeftBracket, "["},
|
||||||
|
{Position{1, 10}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 11}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 12}, tokenRightBracket, "]"},
|
||||||
|
{Position{1, 13}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualNumber(t *testing.T) {
|
||||||
|
testFlow(t, "foo = 42", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "42"},
|
||||||
|
{Position{1, 9}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = +42", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "+42"},
|
||||||
|
{Position{1, 10}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = -42", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "-42"},
|
||||||
|
{Position{1, 10}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = 4.2", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenFloat, "4.2"},
|
||||||
|
{Position{1, 10}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = +4.2", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenFloat, "+4.2"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = -4.2", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenFloat, "-4.2"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = 1_000", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "1_000"},
|
||||||
|
{Position{1, 12}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = 5_349_221", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "5_349_221"},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = 1_2_3_4_5", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "1_2_3_4_5"},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "flt8 = 9_224_617.445_991_228_313", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "flt8"},
|
||||||
|
{Position{1, 6}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenFloat, "9_224_617.445_991_228_313"},
|
||||||
|
{Position{1, 33}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = +", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenError, "no digit in that number"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiline(t *testing.T) {
|
||||||
|
testFlow(t, "foo = 42\nbar=21", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 7}, tokenInteger, "42"},
|
||||||
|
{Position{2, 1}, tokenKey, "bar"},
|
||||||
|
{Position{2, 4}, tokenEqual, "="},
|
||||||
|
{Position{2, 5}, tokenInteger, "21"},
|
||||||
|
{Position{2, 7}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualStringUnicodeEscape(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "hello \u2665"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "hello ♥"},
|
||||||
|
{Position{1, 21}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "hello \U000003B4"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "hello δ"},
|
||||||
|
{Position{1, 25}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\uabcd"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "\uabcd"},
|
||||||
|
{Position{1, 15}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\uABCD"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "\uABCD"},
|
||||||
|
{Position{1, 15}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\U000bcdef"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "\U000bcdef"},
|
||||||
|
{Position{1, 19}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\U000BCDEF"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "\U000BCDEF"},
|
||||||
|
{Position{1, 19}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\u2"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unfinished unicode escape"},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = "\U2"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unfinished unicode escape"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyEqualStringNoEscape(t *testing.T) {
|
||||||
|
testFlow(t, "foo = \"hello \u0002\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unescaped control character U+0002"},
|
||||||
|
})
|
||||||
|
testFlow(t, "foo = \"hello \u001F\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unescaped control character U+001F"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiteralString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = 'C:\Users\nodejs\templates'`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, `C:\Users\nodejs\templates`},
|
||||||
|
{Position{1, 34}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = '\\ServerX\admin$\system32\'`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, `\\ServerX\admin$\system32\`},
|
||||||
|
{Position{1, 35}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = 'Tom "Dubs" Preston-Werner'`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, `Tom "Dubs" Preston-Werner`},
|
||||||
|
{Position{1, 34}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = '<\i\c*\s*>'`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, `<\i\c*\s*>`},
|
||||||
|
{Position{1, 19}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, `foo = 'C:\Users\nodejs\unfinis`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenError, "unclosed string"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineLiteralString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = '''hello 'literal' world'''`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 10}, tokenString, `hello 'literal' world`},
|
||||||
|
{Position{1, 34}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = '''\nhello\n'literal'\nworld'''", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{2, 1}, tokenString, "hello\n'literal'\nworld"},
|
||||||
|
{Position{4, 9}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
testFlow(t, "foo = '''\r\nhello\r\n'literal'\r\nworld'''", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{2, 1}, tokenString, "hello\r\n'literal'\r\nworld"},
|
||||||
|
{Position{4, 9}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = """hello "literal" world"""`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 10}, tokenString, `hello "literal" world`},
|
||||||
|
{Position{1, 34}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = \"\"\"\r\nhello\\\r\n\"literal\"\\\nworld\"\"\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{2, 1}, tokenString, "hello\"literal\"world"},
|
||||||
|
{Position{4, 9}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "foo = \"\"\"\\\n \\\n \\\n hello\\\nmultiline\\\nworld\"\"\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 10}, tokenString, "hellomultilineworld"},
|
||||||
|
{Position{6, 9}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "key2 = \"\"\"\nThe quick brown \\\n\n\n fox jumps over \\\n the lazy dog.\"\"\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "key2"},
|
||||||
|
{Position{1, 6}, tokenEqual, "="},
|
||||||
|
{Position{2, 1}, tokenString, "The quick brown fox jumps over the lazy dog."},
|
||||||
|
{Position{6, 21}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "key2 = \"\"\"\\\n The quick brown \\\n fox jumps over \\\n the lazy dog.\\\n \"\"\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "key2"},
|
||||||
|
{Position{1, 6}, tokenEqual, "="},
|
||||||
|
{Position{1, 11}, tokenString, "The quick brown fox jumps over the lazy dog."},
|
||||||
|
{Position{5, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, `key2 = "Roses are red\nViolets are blue"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "key2"},
|
||||||
|
{Position{1, 6}, tokenEqual, "="},
|
||||||
|
{Position{1, 9}, tokenString, "Roses are red\nViolets are blue"},
|
||||||
|
{Position{1, 41}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, "key2 = \"\"\"\nRoses are red\nViolets are blue\"\"\"", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "key2"},
|
||||||
|
{Position{1, 6}, tokenEqual, "="},
|
||||||
|
{Position{2, 1}, tokenString, "Roses are red\nViolets are blue"},
|
||||||
|
{Position{3, 20}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnicodeString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "hello ♥ world"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "hello ♥ world"},
|
||||||
|
{Position{1, 22}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func TestEscapeInString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "\b\f\/"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "\b\f/"},
|
||||||
|
{Position{1, 15}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyGroupArray(t *testing.T) {
|
||||||
|
testFlow(t, "[[foo]]", []token{
|
||||||
|
{Position{1, 1}, tokenDoubleLeftBracket, "[["},
|
||||||
|
{Position{1, 3}, tokenKeyGroupArray, "foo"},
|
||||||
|
{Position{1, 6}, tokenDoubleRightBracket, "]]"},
|
||||||
|
{Position{1, 8}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuotedKey(t *testing.T) {
|
||||||
|
testFlow(t, "\"a b\" = 42", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "\"a b\""},
|
||||||
|
{Position{1, 7}, tokenEqual, "="},
|
||||||
|
{Position{1, 9}, tokenInteger, "42"},
|
||||||
|
{Position{1, 11}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyNewline(t *testing.T) {
|
||||||
|
testFlow(t, "a\n= 4", []token{
|
||||||
|
{Position{1, 1}, tokenError, "keys cannot contain new lines"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidFloat(t *testing.T) {
|
||||||
|
testFlow(t, "a=7e1_", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 2}, tokenEqual, "="},
|
||||||
|
{Position{1, 3}, tokenFloat, "7e1_"},
|
||||||
|
{Position{1, 7}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexUnknownRvalue(t *testing.T) {
|
||||||
|
testFlow(t, `a = !b`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenError, "no value can start with !"},
|
||||||
|
})
|
||||||
|
|
||||||
|
testFlow(t, `a = \b`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "a"},
|
||||||
|
{Position{1, 3}, tokenEqual, "="},
|
||||||
|
{Position{1, 5}, tokenError, `no value can start with \`},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLexer(b *testing.B) {
|
||||||
|
sample := `title = "Hugo: A Fast and Flexible Website Generator"
|
||||||
|
baseurl = "http://gohugo.io/"
|
||||||
|
MetaDataFormat = "yaml"
|
||||||
|
pluralizeListTitles = false
|
||||||
|
|
||||||
|
[params]
|
||||||
|
description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go"
|
||||||
|
author = "Steve Francia (spf13) and friends"
|
||||||
|
release = "0.22-DEV"
|
||||||
|
|
||||||
|
[[menu.main]]
|
||||||
|
name = "Download Hugo"
|
||||||
|
pre = "<i class='fa fa-download'></i>"
|
||||||
|
url = "https://github.com/spf13/hugo/releases"
|
||||||
|
weight = -200
|
||||||
|
`
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
lexToml([]byte(sample))
|
||||||
|
}
|
||||||
|
}
|
|
@ -268,15 +268,20 @@ func valueFromTree(mtype reflect.Type, tval *Tree) (reflect.Value, error) {
|
||||||
mtypef := mtype.Field(i)
|
mtypef := mtype.Field(i)
|
||||||
opts := tomlOptions(mtypef)
|
opts := tomlOptions(mtypef)
|
||||||
if opts.include {
|
if opts.include {
|
||||||
key := opts.name
|
baseKey := opts.name
|
||||||
|
keysToTry := []string{baseKey, strings.ToLower(baseKey), strings.ToTitle(baseKey)}
|
||||||
|
for _, key := range keysToTry {
|
||||||
exists := tval.Has(key)
|
exists := tval.Has(key)
|
||||||
if exists {
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
val := tval.Get(key)
|
val := tval.Get(key)
|
||||||
mvalf, err := valueFromToml(mtypef.Type, val)
|
mvalf, err := valueFromToml(mtypef.Type, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mval, formatError(err, tval.GetPosition(key))
|
return mval, formatError(err, tval.GetPosition(key))
|
||||||
}
|
}
|
||||||
mval.Field(i).Set(mvalf)
|
mval.Field(i).Set(mvalf)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,600 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type basicMarshalTestStruct struct {
|
||||||
|
String string `toml:"string"`
|
||||||
|
StringList []string `toml:"strlist"`
|
||||||
|
Sub basicMarshalTestSubStruct `toml:"subdoc"`
|
||||||
|
SubList []basicMarshalTestSubStruct `toml:"sublist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicMarshalTestSubStruct struct {
|
||||||
|
String2 string
|
||||||
|
}
|
||||||
|
|
||||||
|
var basicTestData = basicMarshalTestStruct{
|
||||||
|
String: "Hello",
|
||||||
|
StringList: []string{"Howdy", "Hey There"},
|
||||||
|
Sub: basicMarshalTestSubStruct{"One"},
|
||||||
|
SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var basicTestToml = []byte(`string = "Hello"
|
||||||
|
strlist = ["Howdy","Hey There"]
|
||||||
|
|
||||||
|
[subdoc]
|
||||||
|
String2 = "One"
|
||||||
|
|
||||||
|
[[sublist]]
|
||||||
|
String2 = "Two"
|
||||||
|
|
||||||
|
[[sublist]]
|
||||||
|
String2 = "Three"
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestBasicMarshal(t *testing.T) {
|
||||||
|
result, err := Marshal(basicTestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := basicTestToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicUnmarshal(t *testing.T) {
|
||||||
|
result := basicMarshalTestStruct{}
|
||||||
|
err := Unmarshal(basicTestToml, &result)
|
||||||
|
expected := basicTestData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDoc struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Basics testDocBasics `toml:"basic"`
|
||||||
|
BasicLists testDocBasicLists `toml:"basic_lists"`
|
||||||
|
BasicMap map[string]string `toml:"basic_map"`
|
||||||
|
Subdocs testDocSubs `toml:"subdoc"`
|
||||||
|
SubDocList []testSubDoc `toml:"subdoclist"`
|
||||||
|
SubDocPtrs []*testSubDoc `toml:"subdocptrs"`
|
||||||
|
err int `toml:"shouldntBeHere"`
|
||||||
|
unexported int `toml:"shouldntBeHere"`
|
||||||
|
Unexported2 int `toml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDocBasics struct {
|
||||||
|
Bool bool `toml:"bool"`
|
||||||
|
Date time.Time `toml:"date"`
|
||||||
|
Float float32 `toml:"float"`
|
||||||
|
Int int `toml:"int"`
|
||||||
|
Uint uint `toml:"uint"`
|
||||||
|
String *string `toml:"string"`
|
||||||
|
unexported int `toml:"shouldntBeHere"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDocBasicLists struct {
|
||||||
|
Bools []bool `toml:"bools"`
|
||||||
|
Dates []time.Time `toml:"dates"`
|
||||||
|
Floats []*float32 `toml:"floats"`
|
||||||
|
Ints []int `toml:"ints"`
|
||||||
|
Strings []string `toml:"strings"`
|
||||||
|
UInts []uint `toml:"uints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDocSubs struct {
|
||||||
|
First testSubDoc `toml:"first"`
|
||||||
|
Second *testSubDoc `toml:"second"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSubDoc struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
unexported int `toml:"shouldntBeHere"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var biteMe = "Bite me"
|
||||||
|
var float1 float32 = 12.3
|
||||||
|
var float2 float32 = 45.6
|
||||||
|
var float3 float32 = 78.9
|
||||||
|
var subdoc = testSubDoc{"Second", 0}
|
||||||
|
|
||||||
|
var docData = testDoc{
|
||||||
|
Title: "TOML Marshal Testing",
|
||||||
|
unexported: 0,
|
||||||
|
Unexported2: 0,
|
||||||
|
Basics: testDocBasics{
|
||||||
|
Bool: true,
|
||||||
|
Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
Float: 123.4,
|
||||||
|
Int: 5000,
|
||||||
|
Uint: 5001,
|
||||||
|
String: &biteMe,
|
||||||
|
unexported: 0,
|
||||||
|
},
|
||||||
|
BasicLists: testDocBasicLists{
|
||||||
|
Bools: []bool{true, false, true},
|
||||||
|
Dates: []time.Time{
|
||||||
|
time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Floats: []*float32{&float1, &float2, &float3},
|
||||||
|
Ints: []int{8001, 8001, 8002},
|
||||||
|
Strings: []string{"One", "Two", "Three"},
|
||||||
|
UInts: []uint{5002, 5003},
|
||||||
|
},
|
||||||
|
BasicMap: map[string]string{
|
||||||
|
"one": "one",
|
||||||
|
"two": "two",
|
||||||
|
},
|
||||||
|
Subdocs: testDocSubs{
|
||||||
|
First: testSubDoc{"First", 0},
|
||||||
|
Second: &subdoc,
|
||||||
|
},
|
||||||
|
SubDocList: []testSubDoc{
|
||||||
|
testSubDoc{"List.First", 0},
|
||||||
|
testSubDoc{"List.Second", 0},
|
||||||
|
},
|
||||||
|
SubDocPtrs: []*testSubDoc{&subdoc},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocMarshal(t *testing.T) {
|
||||||
|
result, err := Marshal(docData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected, _ := ioutil.ReadFile("marshal_test.toml")
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocUnmarshal(t *testing.T) {
|
||||||
|
result := testDoc{}
|
||||||
|
tomlData, _ := ioutil.ReadFile("marshal_test.toml")
|
||||||
|
err := Unmarshal(tomlData, &result)
|
||||||
|
expected := docData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
resStr, _ := json.MarshalIndent(result, "", " ")
|
||||||
|
expStr, _ := json.MarshalIndent(expected, "", " ")
|
||||||
|
t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocPartialUnmarshal(t *testing.T) {
|
||||||
|
result := testDocSubs{}
|
||||||
|
|
||||||
|
tree, _ := LoadFile("marshal_test.toml")
|
||||||
|
subTree := tree.Get("subdoc").(*Tree)
|
||||||
|
err := subTree.Unmarshal(&result)
|
||||||
|
expected := docData.Subdocs
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
resStr, _ := json.MarshalIndent(result, "", " ")
|
||||||
|
expStr, _ := json.MarshalIndent(expected, "", " ")
|
||||||
|
t.Errorf("Bad partial unmartial: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tomlTypeCheckTest struct {
|
||||||
|
name string
|
||||||
|
item interface{}
|
||||||
|
typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeChecks(t *testing.T) {
|
||||||
|
tests := []tomlTypeCheckTest{
|
||||||
|
{"integer", 2, 0},
|
||||||
|
{"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0},
|
||||||
|
{"stringlist", []string{"hello", "hi"}, 1},
|
||||||
|
{"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1},
|
||||||
|
{"objectlist", []tomlTypeCheckTest{}, 2},
|
||||||
|
{"object", tomlTypeCheckTest{}, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
expected := []bool{false, false, false, false}
|
||||||
|
expected[test.typ] = true
|
||||||
|
result := []bool{
|
||||||
|
isPrimitive(reflect.TypeOf(test.item)),
|
||||||
|
isOtherSlice(reflect.TypeOf(test.item)),
|
||||||
|
isTreeSlice(reflect.TypeOf(test.item)),
|
||||||
|
isTree(reflect.TypeOf(test.item)),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, result) {
|
||||||
|
t.Errorf("Bad type check on %q: expected %v, got %v", test.name, expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexportedMarshalTestStruct struct {
|
||||||
|
String string `toml:"string"`
|
||||||
|
StringList []string `toml:"strlist"`
|
||||||
|
Sub basicMarshalTestSubStruct `toml:"subdoc"`
|
||||||
|
SubList []basicMarshalTestSubStruct `toml:"sublist"`
|
||||||
|
unexported int `toml:"shouldntBeHere"`
|
||||||
|
Unexported2 int `toml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var unexportedTestData = unexportedMarshalTestStruct{
|
||||||
|
String: "Hello",
|
||||||
|
StringList: []string{"Howdy", "Hey There"},
|
||||||
|
Sub: basicMarshalTestSubStruct{"One"},
|
||||||
|
SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}},
|
||||||
|
unexported: 0,
|
||||||
|
Unexported2: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var unexportedTestToml = []byte(`string = "Hello"
|
||||||
|
strlist = ["Howdy","Hey There"]
|
||||||
|
unexported = 1
|
||||||
|
shouldntBeHere = 2
|
||||||
|
|
||||||
|
[subdoc]
|
||||||
|
String2 = "One"
|
||||||
|
|
||||||
|
[[sublist]]
|
||||||
|
String2 = "Two"
|
||||||
|
|
||||||
|
[[sublist]]
|
||||||
|
String2 = "Three"
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestUnexportedUnmarshal(t *testing.T) {
|
||||||
|
result := unexportedMarshalTestStruct{}
|
||||||
|
err := Unmarshal(unexportedTestToml, &result)
|
||||||
|
expected := unexportedTestData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errStruct struct {
|
||||||
|
Bool bool `toml:"bool"`
|
||||||
|
Date time.Time `toml:"date"`
|
||||||
|
Float float64 `toml:"float"`
|
||||||
|
Int int16 `toml:"int"`
|
||||||
|
String *string `toml:"string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var errTomls = []string{
|
||||||
|
"bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
|
||||||
|
"bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"",
|
||||||
|
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1",
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapErr struct {
|
||||||
|
Vals map[string]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type intErr struct {
|
||||||
|
Int1 int
|
||||||
|
Int2 int8
|
||||||
|
Int3 int16
|
||||||
|
Int4 int32
|
||||||
|
Int5 int64
|
||||||
|
UInt1 uint
|
||||||
|
UInt2 uint8
|
||||||
|
UInt3 uint16
|
||||||
|
UInt4 uint32
|
||||||
|
UInt5 uint64
|
||||||
|
Flt1 float32
|
||||||
|
Flt2 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var intErrTomls = []string{
|
||||||
|
"Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0",
|
||||||
|
"Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrUnmarshal(t *testing.T) {
|
||||||
|
for ind, toml := range errTomls {
|
||||||
|
result := errStruct{}
|
||||||
|
err := Unmarshal([]byte(toml), &result)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected err from case %d\n", ind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result2 := mapErr{}
|
||||||
|
err := Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected err from map")
|
||||||
|
}
|
||||||
|
for ind, toml := range intErrTomls {
|
||||||
|
result3 := intErr{}
|
||||||
|
err := Unmarshal([]byte(toml), &result3)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected int err from case %d\n", ind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyMarshalTestStruct struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Bool bool `toml:"bool"`
|
||||||
|
Int int `toml:"int"`
|
||||||
|
String string `toml:"string"`
|
||||||
|
StringList []string `toml:"stringlist"`
|
||||||
|
Ptr *basicMarshalTestStruct `toml:"ptr"`
|
||||||
|
Map map[string]string `toml:"map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyTestData = emptyMarshalTestStruct{
|
||||||
|
Title: "Placeholder",
|
||||||
|
Bool: false,
|
||||||
|
Int: 0,
|
||||||
|
String: "",
|
||||||
|
StringList: []string{},
|
||||||
|
Ptr: nil,
|
||||||
|
Map: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyTestToml = []byte(`bool = false
|
||||||
|
int = 0
|
||||||
|
string = ""
|
||||||
|
stringlist = []
|
||||||
|
title = "Placeholder"
|
||||||
|
|
||||||
|
[map]
|
||||||
|
`)
|
||||||
|
|
||||||
|
type emptyMarshalTestStruct2 struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Bool bool `toml:"bool,omitempty"`
|
||||||
|
Int int `toml:"int, omitempty"`
|
||||||
|
String string `toml:"string,omitempty "`
|
||||||
|
StringList []string `toml:"stringlist,omitempty"`
|
||||||
|
Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"`
|
||||||
|
Map map[string]string `toml:"map,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyTestData2 = emptyMarshalTestStruct2{
|
||||||
|
Title: "Placeholder",
|
||||||
|
Bool: false,
|
||||||
|
Int: 0,
|
||||||
|
String: "",
|
||||||
|
StringList: []string{},
|
||||||
|
Ptr: nil,
|
||||||
|
Map: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyTestToml2 = []byte(`title = "Placeholder"
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestEmptyMarshal(t *testing.T) {
|
||||||
|
result, err := Marshal(emptyTestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := emptyTestToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad empty marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyMarshalOmit(t *testing.T) {
|
||||||
|
result, err := Marshal(emptyTestData2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := emptyTestToml2
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad empty omit marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyUnmarshal(t *testing.T) {
|
||||||
|
result := emptyMarshalTestStruct{}
|
||||||
|
err := Unmarshal(emptyTestToml, &result)
|
||||||
|
expected := emptyTestData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyUnmarshalOmit(t *testing.T) {
|
||||||
|
result := emptyMarshalTestStruct2{}
|
||||||
|
err := Unmarshal(emptyTestToml, &result)
|
||||||
|
expected := emptyTestData2
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pointerMarshalTestStruct struct {
|
||||||
|
Str *string
|
||||||
|
List *[]string
|
||||||
|
ListPtr *[]*string
|
||||||
|
Map *map[string]string
|
||||||
|
MapPtr *map[string]*string
|
||||||
|
EmptyStr *string
|
||||||
|
EmptyList *[]string
|
||||||
|
EmptyMap *map[string]string
|
||||||
|
DblPtr *[]*[]*string
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerStr = "Hello"
|
||||||
|
var pointerList = []string{"Hello back"}
|
||||||
|
var pointerListPtr = []*string{&pointerStr}
|
||||||
|
var pointerMap = map[string]string{"response": "Goodbye"}
|
||||||
|
var pointerMapPtr = map[string]*string{"alternate": &pointerStr}
|
||||||
|
var pointerTestData = pointerMarshalTestStruct{
|
||||||
|
Str: &pointerStr,
|
||||||
|
List: &pointerList,
|
||||||
|
ListPtr: &pointerListPtr,
|
||||||
|
Map: &pointerMap,
|
||||||
|
MapPtr: &pointerMapPtr,
|
||||||
|
EmptyStr: nil,
|
||||||
|
EmptyList: nil,
|
||||||
|
EmptyMap: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerTestToml = []byte(`List = ["Hello back"]
|
||||||
|
ListPtr = ["Hello"]
|
||||||
|
Str = "Hello"
|
||||||
|
|
||||||
|
[Map]
|
||||||
|
response = "Goodbye"
|
||||||
|
|
||||||
|
[MapPtr]
|
||||||
|
alternate = "Hello"
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestPointerMarshal(t *testing.T) {
|
||||||
|
result, err := Marshal(pointerTestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := pointerTestToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad pointer marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerUnmarshal(t *testing.T) {
|
||||||
|
result := pointerMarshalTestStruct{}
|
||||||
|
err := Unmarshal(pointerTestToml, &result)
|
||||||
|
expected := pointerTestData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nestedMarshalTestStruct struct {
|
||||||
|
String [][]string
|
||||||
|
//Struct [][]basicMarshalTestSubStruct
|
||||||
|
StringPtr *[]*[]*string
|
||||||
|
// StructPtr *[]*[]*basicMarshalTestSubStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
var str1 = "Three"
|
||||||
|
var str2 = "Four"
|
||||||
|
var strPtr = []*string{&str1, &str2}
|
||||||
|
var strPtr2 = []*[]*string{&strPtr}
|
||||||
|
|
||||||
|
var nestedTestData = nestedMarshalTestStruct{
|
||||||
|
String: [][]string{[]string{"Five", "Six"}, []string{"One", "Two"}},
|
||||||
|
StringPtr: &strPtr2,
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedTestToml = []byte(`String = [["Five","Six"],["One","Two"]]
|
||||||
|
StringPtr = [["Three","Four"]]
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestNestedMarshal(t *testing.T) {
|
||||||
|
result, err := Marshal(nestedTestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := nestedTestToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad nested marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedUnmarshal(t *testing.T) {
|
||||||
|
result := nestedMarshalTestStruct{}
|
||||||
|
err := Unmarshal(nestedTestToml, &result)
|
||||||
|
expected := nestedTestData
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type customMarshalerParent struct {
|
||||||
|
Self customMarshaler `toml:"me"`
|
||||||
|
Friends []customMarshaler `toml:"friends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type customMarshaler struct {
|
||||||
|
FirsName string
|
||||||
|
LastName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c customMarshaler) MarshalTOML() ([]byte, error) {
|
||||||
|
fullName := fmt.Sprintf("%s %s", c.FirsName, c.LastName)
|
||||||
|
return []byte(fullName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var customMarshalerData = customMarshaler{FirsName: "Sally", LastName: "Fields"}
|
||||||
|
var customMarshalerToml = []byte(`Sally Fields`)
|
||||||
|
var nestedCustomMarshalerData = customMarshalerParent{
|
||||||
|
Self: customMarshaler{FirsName: "Maiku", LastName: "Suteda"},
|
||||||
|
Friends: []customMarshaler{customMarshalerData},
|
||||||
|
}
|
||||||
|
var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"]
|
||||||
|
me = "Maiku Suteda"
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestCustomMarshaler(t *testing.T) {
|
||||||
|
result, err := Marshal(customMarshalerData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := customMarshalerToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedCustomMarshaler(t *testing.T) {
|
||||||
|
result, err := Marshal(nestedCustomMarshalerData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := nestedCustomMarshalerToml
|
||||||
|
if !bytes.Equal(result, expected) {
|
||||||
|
t.Errorf("Bad nested custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
title = "TOML Marshal Testing"
|
||||||
|
|
||||||
|
[basic]
|
||||||
|
bool = true
|
||||||
|
date = 1979-05-27T07:32:00Z
|
||||||
|
float = 123.4
|
||||||
|
int = 5000
|
||||||
|
string = "Bite me"
|
||||||
|
uint = 5001
|
||||||
|
|
||||||
|
[basic_lists]
|
||||||
|
bools = [true,false,true]
|
||||||
|
dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z]
|
||||||
|
floats = [12.3,45.6,78.9]
|
||||||
|
ints = [8001,8001,8002]
|
||||||
|
strings = ["One","Two","Three"]
|
||||||
|
uints = [5002,5003]
|
||||||
|
|
||||||
|
[basic_map]
|
||||||
|
one = "one"
|
||||||
|
two = "two"
|
||||||
|
|
||||||
|
[subdoc]
|
||||||
|
|
||||||
|
[subdoc.first]
|
||||||
|
name = "First"
|
||||||
|
|
||||||
|
[subdoc.second]
|
||||||
|
name = "Second"
|
||||||
|
|
||||||
|
[[subdoclist]]
|
||||||
|
name = "List.First"
|
||||||
|
|
||||||
|
[[subdoclist]]
|
||||||
|
name = "List.Second"
|
||||||
|
|
||||||
|
[[subdocptrs]]
|
||||||
|
name = "Second"
|
|
@ -13,9 +13,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type tomlParser struct {
|
type tomlParser struct {
|
||||||
flow chan token
|
flowIdx int
|
||||||
|
flow []token
|
||||||
tree *Tree
|
tree *Tree
|
||||||
tokensBuffer []token
|
|
||||||
currentTable []string
|
currentTable []string
|
||||||
seenTableKeys []string
|
seenTableKeys []string
|
||||||
}
|
}
|
||||||
|
@ -34,16 +34,10 @@ func (p *tomlParser) run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tomlParser) peek() *token {
|
func (p *tomlParser) peek() *token {
|
||||||
if len(p.tokensBuffer) != 0 {
|
if p.flowIdx >= len(p.flow) {
|
||||||
return &(p.tokensBuffer[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
tok, ok := <-p.flow
|
|
||||||
if !ok {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.tokensBuffer = append(p.tokensBuffer, tok)
|
return &p.flow[p.flowIdx]
|
||||||
return &tok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tomlParser) assume(typ tokenType) {
|
func (p *tomlParser) assume(typ tokenType) {
|
||||||
|
@ -57,16 +51,12 @@ func (p *tomlParser) assume(typ tokenType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tomlParser) getToken() *token {
|
func (p *tomlParser) getToken() *token {
|
||||||
if len(p.tokensBuffer) != 0 {
|
tok := p.peek()
|
||||||
tok := p.tokensBuffer[0]
|
if tok == nil {
|
||||||
p.tokensBuffer = p.tokensBuffer[1:]
|
|
||||||
return &tok
|
|
||||||
}
|
|
||||||
tok, ok := <-p.flow
|
|
||||||
if !ok {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &tok
|
p.flowIdx++
|
||||||
|
return tok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tomlParser) parseStart() tomlParserStateFn {
|
func (p *tomlParser) parseStart() tomlParserStateFn {
|
||||||
|
@ -374,13 +364,13 @@ func (p *tomlParser) parseArray() interface{} {
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToml(flow chan token) *Tree {
|
func parseToml(flow []token) *Tree {
|
||||||
result := newTree()
|
result := newTree()
|
||||||
result.position = Position{1, 1}
|
result.position = Position{1, 1}
|
||||||
parser := &tomlParser{
|
parser := &tomlParser{
|
||||||
|
flowIdx: 0,
|
||||||
flow: flow,
|
flow: flow,
|
||||||
tree: result,
|
tree: result,
|
||||||
tokensBuffer: make([]token, 0),
|
|
||||||
currentTable: make([]string, 0),
|
currentTable: make([]string, 0),
|
||||||
seenTableKeys: make([]string, 0),
|
seenTableKeys: make([]string, 0),
|
||||||
}
|
}
|
|
@ -0,0 +1,785 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertSubTree(t *testing.T, path []string, tree *Tree, err error, ref map[string]interface{}) {
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Non-nil error:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range ref {
|
||||||
|
nextPath := append(path, k)
|
||||||
|
t.Log("asserting path", nextPath)
|
||||||
|
// NOTE: directly access key instead of resolve by path
|
||||||
|
// NOTE: see TestSpecialKV
|
||||||
|
switch node := tree.GetPath([]string{k}).(type) {
|
||||||
|
case []*Tree:
|
||||||
|
t.Log("\tcomparing key", nextPath, "by array iteration")
|
||||||
|
for idx, item := range node {
|
||||||
|
assertSubTree(t, nextPath, item, err, v.([]map[string]interface{})[idx])
|
||||||
|
}
|
||||||
|
case *Tree:
|
||||||
|
t.Log("\tcomparing key", nextPath, "by subtree assestion")
|
||||||
|
assertSubTree(t, nextPath, node, err, v.(map[string]interface{}))
|
||||||
|
default:
|
||||||
|
t.Log("\tcomparing key", nextPath, "by string representation because it's of type", reflect.TypeOf(node))
|
||||||
|
if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) {
|
||||||
|
t.Errorf("was expecting %v at %v but got %v", v, k, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertTree(t *testing.T, tree *Tree, err error, ref map[string]interface{}) {
|
||||||
|
t.Log("Asserting tree:\n", spew.Sdump(tree))
|
||||||
|
assertSubTree(t, []string{}, tree, err, ref)
|
||||||
|
t.Log("Finished tree assertion.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSubTree(t *testing.T) {
|
||||||
|
tree := newTree()
|
||||||
|
tree.createSubTree([]string{"a", "b", "c"}, Position{})
|
||||||
|
tree.Set("a.b.c", 42)
|
||||||
|
if tree.Get("a.b.c") != 42 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleKV(t *testing.T) {
|
||||||
|
tree, err := Load("a = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, _ = Load("a = 42\nb = 21")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
"b": int64(21),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumberInKey(t *testing.T) {
|
||||||
|
tree, err := Load("hello2 = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"hello2": int64(42),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleNumbers(t *testing.T) {
|
||||||
|
tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
"b": int64(-21),
|
||||||
|
"c": float64(4.2),
|
||||||
|
"d": float64(-2.1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumbersWithUnderscores(t *testing.T) {
|
||||||
|
tree, err := Load("a = 1_000")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(1000),
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("a = 5_349_221")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(5349221),
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("a = 1_2_3_4_5")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": int64(12345),
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("flt8 = 9_224_617.445_991_228_313")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"flt8": float64(9224617.445991228313),
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("flt9 = 1e1_00")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"flt9": float64(1e100),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatsWithExponents(t *testing.T) {
|
||||||
|
tree, err := Load("a = 5e+22\nb = 5E+22\nc = -5e+22\nd = -5e-22\ne = 6.626e-34")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": float64(5e+22),
|
||||||
|
"b": float64(5E+22),
|
||||||
|
"c": float64(-5e+22),
|
||||||
|
"d": float64(-5e-22),
|
||||||
|
"e": float64(6.626e-34),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleDate(t *testing.T) {
|
||||||
|
tree, err := Load("a = 1979-05-27T07:32:00Z")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateOffset(t *testing.T) {
|
||||||
|
tree, err := Load("a = 1979-05-27T00:32:00-07:00")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": time.Date(1979, time.May, 27, 0, 32, 0, 0, time.FixedZone("", -7*60*60)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateNano(t *testing.T) {
|
||||||
|
tree, err := Load("a = 1979-05-27T00:32:00.999999999-07:00")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": time.Date(1979, time.May, 27, 0, 32, 0, 999999999, time.FixedZone("", -7*60*60)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleString(t *testing.T) {
|
||||||
|
tree, err := Load("a = \"hello world\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": "hello world",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpaceKey(t *testing.T) {
|
||||||
|
tree, err := Load("\"a b\" = \"hello world\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a b": "hello world",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringEscapables(t *testing.T) {
|
||||||
|
tree, err := Load("a = \"a \\n b\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": "a \n b",
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("a = \"a \\t b\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": "a \t b",
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("a = \"a \\r b\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": "a \r b",
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("a = \"a \\\\ b\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": "a \\ b",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyQuotedString(t *testing.T) {
|
||||||
|
tree, err := Load(`[""]
|
||||||
|
"" = 1`)
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"": map[string]interface{}{
|
||||||
|
"": int64(1),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBools(t *testing.T) {
|
||||||
|
tree, err := Load("a = true\nb = false")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": true,
|
||||||
|
"b": false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedKeys(t *testing.T) {
|
||||||
|
tree, err := Load("[a.b.c]\nd = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": map[string]interface{}{
|
||||||
|
"b": map[string]interface{}{
|
||||||
|
"c": map[string]interface{}{
|
||||||
|
"d": int64(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedQuotedUnicodeKeys(t *testing.T) {
|
||||||
|
tree, err := Load("[ j . \"ʞ\" . l ]\nd = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"j": map[string]interface{}{
|
||||||
|
"ʞ": map[string]interface{}{
|
||||||
|
"l": map[string]interface{}{
|
||||||
|
"d": int64(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("[ g . h . i ]\nd = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"g": map[string]interface{}{
|
||||||
|
"h": map[string]interface{}{
|
||||||
|
"i": map[string]interface{}{
|
||||||
|
"d": int64(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, err = Load("[ d.e.f ]\nk = 42")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"d": map[string]interface{}{
|
||||||
|
"e": map[string]interface{}{
|
||||||
|
"f": map[string]interface{}{
|
||||||
|
"k": int64(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayOne(t *testing.T) {
|
||||||
|
tree, err := Load("a = [1]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(1)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayZero(t *testing.T) {
|
||||||
|
tree, err := Load("a = []")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []interface{}{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySimple(t *testing.T) {
|
||||||
|
tree, err := Load("a = [42, 21, 10]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(42), int64(21), int64(10)},
|
||||||
|
})
|
||||||
|
|
||||||
|
tree, _ = Load("a = [42, 21, 10,]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(42), int64(21), int64(10)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayMultiline(t *testing.T) {
|
||||||
|
tree, err := Load("a = [42,\n21, 10,]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(42), int64(21), int64(10)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayNested(t *testing.T) {
|
||||||
|
tree, err := Load("a = [[42, 21], [10]]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": [][]int64{{int64(42), int64(21)}, {int64(10)}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedArrayComment(t *testing.T) {
|
||||||
|
tree, err := Load(`
|
||||||
|
someArray = [
|
||||||
|
# does not work
|
||||||
|
["entry1"]
|
||||||
|
]`)
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"someArray": [][]string{{"entry1"}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedEmptyArrays(t *testing.T) {
|
||||||
|
tree, err := Load("a = [[[]]]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": [][][]interface{}{{{}}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayMixedTypes(t *testing.T) {
|
||||||
|
_, err := Load("a = [42, 16.0]")
|
||||||
|
if err.Error() != "(1, 10): mixed types in array" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a = [42, \"hello\"]")
|
||||||
|
if err.Error() != "(1, 11): mixed types in array" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayNestedStrings(t *testing.T) {
|
||||||
|
tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"data": [][]string{{"gamma", "delta"}, {"Foo"}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUnknownRvalue(t *testing.T) {
|
||||||
|
_, err := Load("a = !bssss")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expecting a parse error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a = /b")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expecting a parse error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingValue(t *testing.T) {
|
||||||
|
_, err := Load("a = ")
|
||||||
|
if err.Error() != "(1, 5): expecting a value" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnterminatedArray(t *testing.T) {
|
||||||
|
_, err := Load("a = [1,")
|
||||||
|
if err.Error() != "(1, 8): unterminated array" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a = [1")
|
||||||
|
if err.Error() != "(1, 7): unterminated array" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a = [1 2")
|
||||||
|
if err.Error() != "(1, 8): missing comma" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewlinesInArrays(t *testing.T) {
|
||||||
|
tree, err := Load("a = [1,\n2,\n3]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(1), int64(2), int64(3)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayWithExtraComma(t *testing.T) {
|
||||||
|
tree, err := Load("a = [1,\n2,\n3,\n]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(1), int64(2), int64(3)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayWithExtraCommaComment(t *testing.T) {
|
||||||
|
tree, err := Load("a = [1, # wow\n2, # such items\n3, # so array\n]")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": []int64{int64(1), int64(2), int64(3)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleInlineGroup(t *testing.T) {
|
||||||
|
tree, err := Load("key = {a = 42}")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"key": map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoubleInlineGroup(t *testing.T) {
|
||||||
|
tree, err := Load("key = {a = 42, b = \"foo\"}")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"key": map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
"b": "foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleInlineGroup(t *testing.T) {
|
||||||
|
tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
|
||||||
|
point = { x = 1, y = 2 }`)
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"name": map[string]interface{}{
|
||||||
|
"first": "Tom",
|
||||||
|
"last": "Preston-Werner",
|
||||||
|
},
|
||||||
|
"point": map[string]interface{}{
|
||||||
|
"x": int64(1),
|
||||||
|
"y": int64(2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleInlineGroupInArray(t *testing.T) {
|
||||||
|
tree, err := Load(`points = [{ x = 1, y = 2 }]`)
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"points": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"x": int64(1),
|
||||||
|
"y": int64(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineTableUnterminated(t *testing.T) {
|
||||||
|
_, err := Load("foo = {")
|
||||||
|
if err.Error() != "(1, 8): unterminated inline table" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineTableCommaExpected(t *testing.T) {
|
||||||
|
_, err := Load("foo = {hello = 53 test = foo}")
|
||||||
|
if err.Error() != "(1, 19): comma expected between fields in inline table" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineTableCommaStart(t *testing.T) {
|
||||||
|
_, err := Load("foo = {, hello = 53}")
|
||||||
|
if err.Error() != "(1, 8): inline table cannot start with a comma" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineTableDoubleComma(t *testing.T) {
|
||||||
|
_, err := Load("foo = {hello = 53,, foo = 17}")
|
||||||
|
if err.Error() != "(1, 19): need field between two commas in inline table" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateGroups(t *testing.T) {
|
||||||
|
_, err := Load("[foo]\na=2\n[foo]b=3")
|
||||||
|
if err.Error() != "(3, 2): duplicated tables" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateKeys(t *testing.T) {
|
||||||
|
_, err := Load("foo = 2\nfoo = 3")
|
||||||
|
if err.Error() != "(2, 1): The following key was defined twice: foo" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyIntermediateTable(t *testing.T) {
|
||||||
|
_, err := Load("[foo..bar]")
|
||||||
|
if err.Error() != "(1, 2): invalid table array key: empty table key" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImplicitDeclarationBefore(t *testing.T) {
|
||||||
|
tree, err := Load("[a.b.c]\nanswer = 42\n[a]\nbetter = 43")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"a": map[string]interface{}{
|
||||||
|
"b": map[string]interface{}{
|
||||||
|
"c": map[string]interface{}{
|
||||||
|
"answer": int64(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"better": int64(43),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatsWithoutLeadingZeros(t *testing.T) {
|
||||||
|
_, err := Load("a = .42")
|
||||||
|
if err.Error() != "(1, 5): cannot start float with a dot" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a = -.42")
|
||||||
|
if err.Error() != "(1, 5): cannot start float with a dot" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingFile(t *testing.T) {
|
||||||
|
_, err := LoadFile("foo.toml")
|
||||||
|
if err.Error() != "open foo.toml: no such file or directory" &&
|
||||||
|
err.Error() != "open foo.toml: The system cannot find the file specified." {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFile(t *testing.T) {
|
||||||
|
tree, err := LoadFile("example.toml")
|
||||||
|
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"title": "TOML Example",
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"name": "Tom Preston-Werner",
|
||||||
|
"organization": "GitHub",
|
||||||
|
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||||||
|
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
"database": map[string]interface{}{
|
||||||
|
"server": "192.168.1.1",
|
||||||
|
"ports": []int64{8001, 8001, 8002},
|
||||||
|
"connection_max": 5000,
|
||||||
|
"enabled": true,
|
||||||
|
},
|
||||||
|
"servers": map[string]interface{}{
|
||||||
|
"alpha": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
"beta": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"clients": map[string]interface{}{
|
||||||
|
"data": []interface{}{
|
||||||
|
[]string{"gamma", "delta"},
|
||||||
|
[]int64{1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFileCRLF(t *testing.T) {
|
||||||
|
tree, err := LoadFile("example-crlf.toml")
|
||||||
|
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"title": "TOML Example",
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"name": "Tom Preston-Werner",
|
||||||
|
"organization": "GitHub",
|
||||||
|
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||||||
|
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
"database": map[string]interface{}{
|
||||||
|
"server": "192.168.1.1",
|
||||||
|
"ports": []int64{8001, 8001, 8002},
|
||||||
|
"connection_max": 5000,
|
||||||
|
"enabled": true,
|
||||||
|
},
|
||||||
|
"servers": map[string]interface{}{
|
||||||
|
"alpha": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
"beta": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"clients": map[string]interface{}{
|
||||||
|
"data": []interface{}{
|
||||||
|
[]string{"gamma", "delta"},
|
||||||
|
[]int64{1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKeyGroupArray(t *testing.T) {
|
||||||
|
tree, err := Load("[[foo.bar]] a = 42\n[[foo.bar]] a = 69")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": []map[string]interface{}{
|
||||||
|
{"a": int64(42)},
|
||||||
|
{"a": int64(69)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKeyGroupArrayUnfinished(t *testing.T) {
|
||||||
|
_, err := Load("[[foo.bar]\na = 42")
|
||||||
|
if err.Error() != "(1, 10): was expecting token [[, but got unclosed table array key instead" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("[[foo.[bar]\na = 42")
|
||||||
|
if err.Error() != "(1, 3): unexpected token table array key cannot contain ']', was expecting a table array key" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKeyGroupArrayQueryExample(t *testing.T) {
|
||||||
|
tree, err := Load(`
|
||||||
|
[[book]]
|
||||||
|
title = "The Stand"
|
||||||
|
author = "Stephen King"
|
||||||
|
[[book]]
|
||||||
|
title = "For Whom the Bell Tolls"
|
||||||
|
author = "Ernest Hemmingway"
|
||||||
|
[[book]]
|
||||||
|
title = "Neuromancer"
|
||||||
|
author = "William Gibson"
|
||||||
|
`)
|
||||||
|
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"book": []map[string]interface{}{
|
||||||
|
{"title": "The Stand", "author": "Stephen King"},
|
||||||
|
{"title": "For Whom the Bell Tolls", "author": "Ernest Hemmingway"},
|
||||||
|
{"title": "Neuromancer", "author": "William Gibson"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKeyGroupArraySpec(t *testing.T) {
|
||||||
|
tree, err := Load("[[fruit]]\n name=\"apple\"\n [fruit.physical]\n color=\"red\"\n shape=\"round\"\n [[fruit]]\n name=\"banana\"")
|
||||||
|
assertTree(t, tree, err, map[string]interface{}{
|
||||||
|
"fruit": []map[string]interface{}{
|
||||||
|
{"name": "apple", "physical": map[string]interface{}{"color": "red", "shape": "round"}},
|
||||||
|
{"name": "banana"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlValueStringRepresentation(t *testing.T) {
|
||||||
|
for idx, item := range []struct {
|
||||||
|
Value interface{}
|
||||||
|
Expect string
|
||||||
|
}{
|
||||||
|
{int64(12345), "12345"},
|
||||||
|
{uint64(50), "50"},
|
||||||
|
{float64(123.45), "123.45"},
|
||||||
|
{bool(true), "true"},
|
||||||
|
{"hello world", "\"hello world\""},
|
||||||
|
{"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""},
|
||||||
|
{"\x05", "\"\\u0005\""},
|
||||||
|
{time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
"1979-05-27T07:32:00Z"},
|
||||||
|
{[]interface{}{"gamma", "delta"},
|
||||||
|
"[\"gamma\",\"delta\"]"},
|
||||||
|
{nil, ""},
|
||||||
|
} {
|
||||||
|
result, err := tomlValueStringRepresentation(item.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d - unexpected error: %s", idx, err)
|
||||||
|
}
|
||||||
|
if result != item.Expect {
|
||||||
|
t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToStringMapStringString(t *testing.T) {
|
||||||
|
tree, err := TreeFromMap(map[string]interface{}{"m": map[string]interface{}{"v": "abc"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
want := "\n[m]\n v = \"abc\"\n"
|
||||||
|
got := tree.String()
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("want:\n%q\ngot:\n%q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPosition(t *testing.T, text string, ref map[string]Position) {
|
||||||
|
tree, err := Load(text)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error loading document text: `%v`", text)
|
||||||
|
t.Errorf("Error: %v", err)
|
||||||
|
}
|
||||||
|
for path, pos := range ref {
|
||||||
|
testPos := tree.GetPosition(path)
|
||||||
|
if testPos.Invalid() {
|
||||||
|
t.Errorf("Failed to query tree path or path has invalid position: %s", path)
|
||||||
|
} else if pos != testPos {
|
||||||
|
t.Errorf("Expected position %v, got %v instead", pos, testPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocumentPositions(t *testing.T) {
|
||||||
|
assertPosition(t,
|
||||||
|
"[foo]\nbar=42\nbaz=69",
|
||||||
|
map[string]Position{
|
||||||
|
"": {1, 1},
|
||||||
|
"foo": {1, 1},
|
||||||
|
"foo.bar": {2, 1},
|
||||||
|
"foo.baz": {3, 1},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocumentPositionsWithSpaces(t *testing.T) {
|
||||||
|
assertPosition(t,
|
||||||
|
" [foo]\n bar=42\n baz=69",
|
||||||
|
map[string]Position{
|
||||||
|
"": {1, 1},
|
||||||
|
"foo": {1, 3},
|
||||||
|
"foo.bar": {2, 3},
|
||||||
|
"foo.baz": {3, 3},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocumentPositionsWithGroupArray(t *testing.T) {
|
||||||
|
assertPosition(t,
|
||||||
|
"[[foo]]\nbar=42\nbaz=69",
|
||||||
|
map[string]Position{
|
||||||
|
"": {1, 1},
|
||||||
|
"foo": {1, 1},
|
||||||
|
"foo.bar": {2, 1},
|
||||||
|
"foo.baz": {3, 1},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedTreePosition(t *testing.T) {
|
||||||
|
assertPosition(t,
|
||||||
|
"[foo.bar]\na=42\nb=69",
|
||||||
|
map[string]Position{
|
||||||
|
"": {1, 1},
|
||||||
|
"foo": {1, 1},
|
||||||
|
"foo.bar": {1, 1},
|
||||||
|
"foo.bar.a": {2, 1},
|
||||||
|
"foo.bar.b": {3, 1},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidGroupArray(t *testing.T) {
|
||||||
|
_, err := Load("[table#key]\nanswer = 42")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("[foo.[bar]\na = 42")
|
||||||
|
if err.Error() != "(1, 2): unexpected token table key cannot contain ']', was expecting a table key" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoubleEqual(t *testing.T) {
|
||||||
|
_, err := Load("foo= = 2")
|
||||||
|
if err.Error() != "(1, 6): cannot have multiple equals for the same key" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupArrayReassign(t *testing.T) {
|
||||||
|
_, err := Load("[hello]\n[[hello]]")
|
||||||
|
if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type table array" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidFloatParsing(t *testing.T) {
|
||||||
|
_, err := Load("a=1e_2")
|
||||||
|
if err.Error() != "(1, 3): invalid use of _ in number" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a=1e2_")
|
||||||
|
if err.Error() != "(1, 3): invalid use of _ in number" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a=1__2")
|
||||||
|
if err.Error() != "(1, 3): invalid use of _ in number" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load("a=_1_2")
|
||||||
|
if err.Error() != "(1, 3): cannot start number with underscore" {
|
||||||
|
t.Error("Bad error message:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Testing support for go-toml
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPositionString(t *testing.T) {
|
||||||
|
p := Position{123, 456}
|
||||||
|
expected := "(123, 456)"
|
||||||
|
value := p.String()
|
||||||
|
|
||||||
|
if value != expected {
|
||||||
|
t.Errorf("Expected %v, got %v instead", expected, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalid(t *testing.T) {
|
||||||
|
for i, v := range []Position{
|
||||||
|
{0, 1234},
|
||||||
|
{1234, 0},
|
||||||
|
{0, 0},
|
||||||
|
} {
|
||||||
|
if !v.Invalid() {
|
||||||
|
t.Errorf("Position at %v is valid: %v", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# fail out of the script if anything here fails
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# set the path to the present working directory
|
||||||
|
export GOPATH=`pwd`
|
||||||
|
|
||||||
|
function git_clone() {
|
||||||
|
path=$1
|
||||||
|
branch=$2
|
||||||
|
version=$3
|
||||||
|
if [ ! -d "src/$path" ]; then
|
||||||
|
mkdir -p src/$path
|
||||||
|
git clone https://$path.git src/$path
|
||||||
|
fi
|
||||||
|
pushd src/$path
|
||||||
|
git checkout "$branch"
|
||||||
|
git reset --hard "$version"
|
||||||
|
popd
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove potential previous runs
|
||||||
|
rm -rf src test_program_bin toml-test
|
||||||
|
|
||||||
|
# Run go vet
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
go get github.com/pelletier/go-buffruneio
|
||||||
|
go get github.com/davecgh/go-spew/spew
|
||||||
|
go get gopkg.in/yaml.v2
|
||||||
|
go get github.com/BurntSushi/toml
|
||||||
|
|
||||||
|
# get code for BurntSushi TOML validation
|
||||||
|
# pinning all to 'HEAD' for version 0.3.x work (TODO: pin to commit hash when tests stabilize)
|
||||||
|
git_clone github.com/BurntSushi/toml master HEAD
|
||||||
|
git_clone github.com/BurntSushi/toml-test master HEAD #was: 0.2.0 HEAD
|
||||||
|
|
||||||
|
# build the BurntSushi test application
|
||||||
|
go build -o toml-test github.com/BurntSushi/toml-test
|
||||||
|
|
||||||
|
# vendorize the current lib for testing
|
||||||
|
# NOTE: this basically mocks an install without having to go back out to github for code
|
||||||
|
mkdir -p src/github.com/pelletier/go-toml/cmd
|
||||||
|
mkdir -p src/github.com/pelletier/go-toml/query
|
||||||
|
cp *.go *.toml src/github.com/pelletier/go-toml
|
||||||
|
cp -R cmd/* src/github.com/pelletier/go-toml/cmd
|
||||||
|
cp -R query/* src/github.com/pelletier/go-toml/query
|
||||||
|
go build -o test_program_bin src/github.com/pelletier/go-toml/cmd/test_program.go
|
||||||
|
|
||||||
|
# Run basic unit tests
|
||||||
|
go test github.com/pelletier/go-toml -covermode=count -coverprofile=coverage.out
|
||||||
|
go test github.com/pelletier/go-toml/cmd/tomljson
|
||||||
|
go test github.com/pelletier/go-toml/query
|
||||||
|
|
||||||
|
# run the entire BurntSushi test suite
|
||||||
|
if [[ $# -eq 0 ]] ; then
|
||||||
|
echo "Running all BurntSushi tests"
|
||||||
|
./toml-test ./test_program_bin | tee test_out
|
||||||
|
else
|
||||||
|
# run a specific test
|
||||||
|
test=$1
|
||||||
|
test_path='src/github.com/BurntSushi/toml-test/tests'
|
||||||
|
valid_test="$test_path/valid/$test"
|
||||||
|
invalid_test="$test_path/invalid/$test"
|
||||||
|
|
||||||
|
if [ -e "$valid_test.toml" ]; then
|
||||||
|
echo "Valid Test TOML for $test:"
|
||||||
|
echo "===="
|
||||||
|
cat "$valid_test.toml"
|
||||||
|
|
||||||
|
echo "Valid Test JSON for $test:"
|
||||||
|
echo "===="
|
||||||
|
cat "$valid_test.json"
|
||||||
|
|
||||||
|
echo "Go-TOML Output for $test:"
|
||||||
|
echo "===="
|
||||||
|
cat "$valid_test.toml" | ./test_program_bin
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$invalid_test.toml" ]; then
|
||||||
|
echo "Invalid Test TOML for $test:"
|
||||||
|
echo "===="
|
||||||
|
cat "$invalid_test.toml"
|
||||||
|
|
||||||
|
echo "Go-TOML Output for $test:"
|
||||||
|
echo "===="
|
||||||
|
echo "go-toml Output:"
|
||||||
|
cat "$invalid_test.toml" | ./test_program_bin
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,67 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTokenStringer(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
tt tokenType
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{tokenError, "Error"},
|
||||||
|
{tokenEOF, "EOF"},
|
||||||
|
{tokenComment, "Comment"},
|
||||||
|
{tokenKey, "Key"},
|
||||||
|
{tokenString, "String"},
|
||||||
|
{tokenInteger, "Integer"},
|
||||||
|
{tokenTrue, "True"},
|
||||||
|
{tokenFalse, "False"},
|
||||||
|
{tokenFloat, "Float"},
|
||||||
|
{tokenEqual, "="},
|
||||||
|
{tokenLeftBracket, "["},
|
||||||
|
{tokenRightBracket, "]"},
|
||||||
|
{tokenLeftCurlyBrace, "{"},
|
||||||
|
{tokenRightCurlyBrace, "}"},
|
||||||
|
{tokenLeftParen, "("},
|
||||||
|
{tokenRightParen, ")"},
|
||||||
|
{tokenDoubleLeftBracket, "]]"},
|
||||||
|
{tokenDoubleRightBracket, "[["},
|
||||||
|
{tokenDate, "Date"},
|
||||||
|
{tokenKeyGroup, "KeyGroup"},
|
||||||
|
{tokenKeyGroupArray, "KeyGroupArray"},
|
||||||
|
{tokenComma, ","},
|
||||||
|
{tokenColon, ":"},
|
||||||
|
{tokenDollar, "$"},
|
||||||
|
{tokenStar, "*"},
|
||||||
|
{tokenQuestion, "?"},
|
||||||
|
{tokenDot, "."},
|
||||||
|
{tokenDotDot, ".."},
|
||||||
|
{tokenEOL, "EOL"},
|
||||||
|
{tokenEOL + 1, "Unknown"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
got := test.tt.String()
|
||||||
|
if got != test.expect {
|
||||||
|
t.Errorf("[%d] invalid string of token type; got %q, expected %q", i, got, test.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenString(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
tok token
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{token{Position{1, 1}, tokenEOF, ""}, "EOF"},
|
||||||
|
{token{Position{1, 1}, tokenError, "Δt"}, "Δt"},
|
||||||
|
{token{Position{1, 1}, tokenString, "bar"}, `"bar"`},
|
||||||
|
{token{Position{1, 1}, tokenString, "123456789012345"}, `"123456789012345"`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
got := test.tok.String()
|
||||||
|
if got != test.expect {
|
||||||
|
t.Errorf("[%d] invalid of string token; got %q, expected %q", i, got, test.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -251,8 +252,8 @@ func (t *Tree) createSubTree(keys []string, pos Position) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadReader creates a Tree from any io.Reader.
|
// LoadBytes creates a Tree from a []byte.
|
||||||
func LoadReader(reader io.Reader) (tree *Tree, err error) {
|
func LoadBytes(b []byte) (tree *Tree, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if _, ok := r.(runtime.Error); ok {
|
if _, ok := r.(runtime.Error); ok {
|
||||||
|
@ -261,13 +262,23 @@ func LoadReader(reader io.Reader) (tree *Tree, err error) {
|
||||||
err = errors.New(r.(string))
|
err = errors.New(r.(string))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
tree = parseToml(lexToml(reader))
|
tree = parseToml(lexToml(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadReader creates a Tree from any io.Reader.
|
||||||
|
func LoadReader(reader io.Reader) (tree *Tree, err error) {
|
||||||
|
inputBytes, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tree, err = LoadBytes(inputBytes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load creates a Tree from a string.
|
// Load creates a Tree from a string.
|
||||||
func Load(content string) (tree *Tree, err error) {
|
func Load(content string) (tree *Tree, err error) {
|
||||||
return LoadReader(strings.NewReader(content))
|
return LoadBytes([]byte(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFile creates a Tree from a file.
|
// LoadFile creates a Tree from a file.
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Testing support for go-toml
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTomlHas(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[test]
|
||||||
|
key = "value"
|
||||||
|
`)
|
||||||
|
|
||||||
|
if !tree.Has("test.key") {
|
||||||
|
t.Errorf("Has - expected test.key to exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Has("") {
|
||||||
|
t.Errorf("Should return false if the key is not provided")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlGet(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[test]
|
||||||
|
key = "value"
|
||||||
|
`)
|
||||||
|
|
||||||
|
if tree.Get("") != tree {
|
||||||
|
t.Errorf("Get should return the tree itself when given an empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Get("test.key") != "value" {
|
||||||
|
t.Errorf("Get should return the value")
|
||||||
|
}
|
||||||
|
if tree.Get(`\`) != nil {
|
||||||
|
t.Errorf("should return nil when the key is malformed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlGetDefault(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[test]
|
||||||
|
key = "value"
|
||||||
|
`)
|
||||||
|
|
||||||
|
if tree.GetDefault("", "hello") != tree {
|
||||||
|
t.Error("GetDefault should return the tree itself when given an empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.GetDefault("test.key", "hello") != "value" {
|
||||||
|
t.Error("Get should return the value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.GetDefault("whatever", "hello") != "hello" {
|
||||||
|
t.Error("GetDefault should return the default value if the key does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlHasPath(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[test]
|
||||||
|
key = "value"
|
||||||
|
`)
|
||||||
|
|
||||||
|
if !tree.HasPath([]string{"test", "key"}) {
|
||||||
|
t.Errorf("HasPath - expected test.key to exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlGetPath(t *testing.T) {
|
||||||
|
node := newTree()
|
||||||
|
//TODO: set other node data
|
||||||
|
|
||||||
|
for idx, item := range []struct {
|
||||||
|
Path []string
|
||||||
|
Expected *Tree
|
||||||
|
}{
|
||||||
|
{ // empty path test
|
||||||
|
[]string{},
|
||||||
|
node,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
result := node.GetPath(item.Path)
|
||||||
|
if result != item.Expected {
|
||||||
|
t.Errorf("GetPath[%d] %v - expected %v, got %v instead.", idx, item.Path, item.Expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6")
|
||||||
|
if tree.GetPath([]string{"whatever"}) != nil {
|
||||||
|
t.Error("GetPath should return nil when the key does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlFromMap(t *testing.T) {
|
||||||
|
simpleMap := map[string]interface{}{"hello": 42}
|
||||||
|
tree, err := TreeFromMap(simpleMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error:", err)
|
||||||
|
}
|
||||||
|
if tree.Get("hello") != int64(42) {
|
||||||
|
t.Fatal("hello should be 42, not", tree.Get("hello"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type customString string
|
||||||
|
|
||||||
|
type stringer struct{}
|
||||||
|
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(t *testing.T, path string, object interface{}) {
|
||||||
|
switch o := object.(type) {
|
||||||
|
case *Tree:
|
||||||
|
for key, tree := range o.values {
|
||||||
|
validate(t, path+"."+key, tree)
|
||||||
|
}
|
||||||
|
case []*Tree:
|
||||||
|
for index, tree := range o {
|
||||||
|
validate(t, path+"."+strconv.Itoa(index), tree)
|
||||||
|
}
|
||||||
|
case *tomlValue:
|
||||||
|
switch o.value.(type) {
|
||||||
|
case int64, uint64, bool, string, float64, time.Time,
|
||||||
|
[]int64, []uint64, []bool, []string, []float64, []time.Time:
|
||||||
|
default:
|
||||||
|
t.Fatalf("tomlValue at key %s containing incorrect type %T", path, o.value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("value at key %s is of incorrect type %T", path, object)
|
||||||
|
}
|
||||||
|
t.Logf("validation ok %s as %T", path, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTree(t *testing.T, tree *Tree) {
|
||||||
|
validate(t, "", tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeCreateToTree(t *testing.T) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"a_string": "bar",
|
||||||
|
"an_int": 42,
|
||||||
|
"time": time.Now(),
|
||||||
|
"int8": int8(2),
|
||||||
|
"int16": int16(2),
|
||||||
|
"int32": int32(2),
|
||||||
|
"uint8": uint8(2),
|
||||||
|
"uint16": uint16(2),
|
||||||
|
"uint32": uint32(2),
|
||||||
|
"float32": float32(2),
|
||||||
|
"a_bool": false,
|
||||||
|
"stringer": stringer{},
|
||||||
|
"nested": map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
"array": []string{"a", "b", "c"},
|
||||||
|
"array_uint": []uint{uint(1), uint(2)},
|
||||||
|
"array_table": []map[string]interface{}{map[string]interface{}{"sub_map": 52}},
|
||||||
|
"array_times": []time.Time{time.Now(), time.Now()},
|
||||||
|
"map_times": map[string]time.Time{"now": time.Now()},
|
||||||
|
"custom_string_map_key": map[customString]interface{}{customString("custom"): "custom"},
|
||||||
|
}
|
||||||
|
tree, err := TreeFromMap(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error:", err)
|
||||||
|
}
|
||||||
|
validateTree(t, tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeCreateToTreeInvalidLeafType(t *testing.T) {
|
||||||
|
_, err := TreeFromMap(map[string]interface{}{"foo": t})
|
||||||
|
expected := "cannot convert type *testing.T to Tree"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeCreateToTreeInvalidMapKeyType(t *testing.T) {
|
||||||
|
_, err := TreeFromMap(map[string]interface{}{"foo": map[int]interface{}{2: 1}})
|
||||||
|
expected := "map key needs to be a string, not int (int)"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeCreateToTreeInvalidArrayMemberType(t *testing.T) {
|
||||||
|
_, err := TreeFromMap(map[string]interface{}{"foo": []*testing.T{t}})
|
||||||
|
expected := "cannot convert type *testing.T to Tree"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeCreateToTreeInvalidTableGroupType(t *testing.T) {
|
||||||
|
_, err := TreeFromMap(map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"hello": t}}})
|
||||||
|
expected := "cannot convert type *testing.T to Tree"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTripArrayOfTables(t *testing.T) {
|
||||||
|
orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\",\"b\"]\n"
|
||||||
|
tree, err := Load(orig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := tree.ToMap()
|
||||||
|
|
||||||
|
tree, err = TreeFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
want := orig
|
||||||
|
got := tree.String()
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("want:\n%s\ngot:\n%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -118,8 +118,7 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
|
||||||
return bytesCount, err
|
return bytesCount, err
|
||||||
}
|
}
|
||||||
|
|
||||||
kvRepr := indent + k + " = " + repr + "\n"
|
writtenBytesCount, err := writeStrings(w, indent, k, " = ", repr, "\n")
|
||||||
writtenBytesCount, err := w.Write([]byte(kvRepr))
|
|
||||||
bytesCount += int64(writtenBytesCount)
|
bytesCount += int64(writtenBytesCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bytesCount, err
|
return bytesCount, err
|
||||||
|
@ -137,8 +136,7 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
|
||||||
switch node := v.(type) {
|
switch node := v.(type) {
|
||||||
// node has to be of those two types given how keys are sorted above
|
// node has to be of those two types given how keys are sorted above
|
||||||
case *Tree:
|
case *Tree:
|
||||||
tableName := "\n" + indent + "[" + combinedKey + "]\n"
|
writtenBytesCount, err := writeStrings(w, "\n", indent, "[", combinedKey, "]\n")
|
||||||
writtenBytesCount, err := w.Write([]byte(tableName))
|
|
||||||
bytesCount += int64(writtenBytesCount)
|
bytesCount += int64(writtenBytesCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bytesCount, err
|
return bytesCount, err
|
||||||
|
@ -149,8 +147,7 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
|
||||||
}
|
}
|
||||||
case []*Tree:
|
case []*Tree:
|
||||||
for _, subTree := range node {
|
for _, subTree := range node {
|
||||||
tableArrayName := "\n" + indent + "[[" + combinedKey + "]]\n"
|
writtenBytesCount, err := writeStrings(w, "\n", indent, "[[", combinedKey, "]]\n")
|
||||||
writtenBytesCount, err := w.Write([]byte(tableArrayName))
|
|
||||||
bytesCount += int64(writtenBytesCount)
|
bytesCount += int64(writtenBytesCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bytesCount, err
|
return bytesCount, err
|
||||||
|
@ -167,6 +164,18 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
|
||||||
return bytesCount, nil
|
return bytesCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeStrings(w io.Writer, s ...string) (int, error) {
|
||||||
|
var n int
|
||||||
|
for i := range s {
|
||||||
|
b, err := io.WriteString(w, s[i])
|
||||||
|
n += b
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WriteTo encode the Tree as Toml and writes it to the writer w.
|
// WriteTo encode the Tree as Toml and writes it to the writer w.
|
||||||
// Returns the number of bytes written in case of success, or an error if anything happened.
|
// Returns the number of bytes written in case of success, or an error if anything happened.
|
||||||
func (t *Tree) WriteTo(w io.Writer) (int64, error) {
|
func (t *Tree) WriteTo(w io.Writer) (int64, error) {
|
|
@ -0,0 +1,358 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type failingWriter struct {
|
||||||
|
failAt int
|
||||||
|
written int
|
||||||
|
buffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *failingWriter) Write(p []byte) (n int, err error) {
|
||||||
|
count := len(p)
|
||||||
|
toWrite := f.failAt - (count + f.written)
|
||||||
|
if toWrite < 0 {
|
||||||
|
toWrite = 0
|
||||||
|
}
|
||||||
|
if toWrite > count {
|
||||||
|
f.written += count
|
||||||
|
f.buffer.Write(p)
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f.buffer.Write(p[:toWrite])
|
||||||
|
f.written = f.failAt
|
||||||
|
return toWrite, fmt.Errorf("failingWriter failed after writting %d bytes", f.written)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertErrorString(t *testing.T, expected string, err error) {
|
||||||
|
expectedErr := errors.New(expected)
|
||||||
|
if err == nil || err.Error() != expectedErr.Error() {
|
||||||
|
t.Errorf("expecting error %s, but got %s instead", expected, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToEmptyTable(t *testing.T) {
|
||||||
|
doc := `[[empty-tables]]
|
||||||
|
[[empty-tables]]`
|
||||||
|
|
||||||
|
toml, err := Load(doc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected Load error:", err)
|
||||||
|
}
|
||||||
|
tomlString, err := toml.ToTomlString()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected ToTomlString error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `
|
||||||
|
[[empty-tables]]
|
||||||
|
|
||||||
|
[[empty-tables]]
|
||||||
|
`
|
||||||
|
|
||||||
|
if tomlString != expected {
|
||||||
|
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToTomlString(t *testing.T) {
|
||||||
|
toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
|
||||||
|
points = { x = 1, y = 2 }`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tomlString, _ := toml.ToTomlString()
|
||||||
|
reparsedTree, err := Load(tomlString)
|
||||||
|
|
||||||
|
assertTree(t, reparsedTree, err, map[string]interface{}{
|
||||||
|
"name": map[string]interface{}{
|
||||||
|
"first": "Tom",
|
||||||
|
"last": "Preston-Werner",
|
||||||
|
},
|
||||||
|
"points": map[string]interface{}{
|
||||||
|
"x": int64(1),
|
||||||
|
"y": int64(2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToTomlStringSimple(t *testing.T) {
|
||||||
|
tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed to parse: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := tree.ToTomlString()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n"
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected got '%s', expected '%s'", result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToTomlStringKeysOrders(t *testing.T) {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
tree, _ := Load(`
|
||||||
|
foobar = true
|
||||||
|
bar = "baz"
|
||||||
|
foo = 1
|
||||||
|
[qux]
|
||||||
|
foo = 1
|
||||||
|
bar = "baz2"`)
|
||||||
|
|
||||||
|
stringRepr, _ := tree.ToTomlString()
|
||||||
|
|
||||||
|
t.Log("Intermediate string representation:")
|
||||||
|
t.Log(stringRepr)
|
||||||
|
|
||||||
|
r := strings.NewReader(stringRepr)
|
||||||
|
toml, err := LoadReader(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTree(t, toml, err, map[string]interface{}{
|
||||||
|
"foobar": true,
|
||||||
|
"bar": "baz",
|
||||||
|
"foo": 1,
|
||||||
|
"qux": map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
"bar": "baz2",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMaps(t *testing.T, actual, expected map[string]interface{}) {
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToMapSimple(t *testing.T) {
|
||||||
|
tree, _ := Load("a = 42\nb = 17")
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
"b": int64(17),
|
||||||
|
}
|
||||||
|
|
||||||
|
testMaps(t, tree.ToMap(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) {
|
||||||
|
tree := Tree{values: map[string]interface{}{"foo": int8(1)}}
|
||||||
|
_, err := tree.ToTomlString()
|
||||||
|
assertErrorString(t, "invalid value type at foo: int8", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) {
|
||||||
|
tree := Tree{values: map[string]interface{}{"foo": &tomlValue{int8(1), Position{}}}}
|
||||||
|
_, err := tree.ToTomlString()
|
||||||
|
assertErrorString(t, "unsupported value type int8: 1", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) {
|
||||||
|
tree := Tree{values: map[string]interface{}{"foo": &tomlValue{[]interface{}{int8(1)}, Position{}}}}
|
||||||
|
_, err := tree.ToTomlString()
|
||||||
|
assertErrorString(t, "unsupported value type int8: 1", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) {
|
||||||
|
toml, _ := Load(`a = 2`)
|
||||||
|
writer := failingWriter{failAt: 0, written: 0}
|
||||||
|
_, err := toml.WriteTo(&writer)
|
||||||
|
assertErrorString(t, "failingWriter failed after writting 0 bytes", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToFailingWriterInTable(t *testing.T) {
|
||||||
|
toml, _ := Load(`
|
||||||
|
[b]
|
||||||
|
a = 2`)
|
||||||
|
writer := failingWriter{failAt: 2, written: 0}
|
||||||
|
_, err := toml.WriteTo(&writer)
|
||||||
|
assertErrorString(t, "failingWriter failed after writting 2 bytes", err)
|
||||||
|
|
||||||
|
writer = failingWriter{failAt: 13, written: 0}
|
||||||
|
_, err = toml.WriteTo(&writer)
|
||||||
|
assertErrorString(t, "failingWriter failed after writting 13 bytes", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToFailingWriterInArray(t *testing.T) {
|
||||||
|
toml, _ := Load(`
|
||||||
|
[[b]]
|
||||||
|
a = 2`)
|
||||||
|
writer := failingWriter{failAt: 2, written: 0}
|
||||||
|
_, err := toml.WriteTo(&writer)
|
||||||
|
assertErrorString(t, "failingWriter failed after writting 2 bytes", err)
|
||||||
|
|
||||||
|
writer = failingWriter{failAt: 15, written: 0}
|
||||||
|
_, err = toml.WriteTo(&writer)
|
||||||
|
assertErrorString(t, "failingWriter failed after writting 15 bytes", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToMapExampleFile(t *testing.T) {
|
||||||
|
tree, _ := LoadFile("example.toml")
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"title": "TOML Example",
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"name": "Tom Preston-Werner",
|
||||||
|
"organization": "GitHub",
|
||||||
|
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||||||
|
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
"database": map[string]interface{}{
|
||||||
|
"server": "192.168.1.1",
|
||||||
|
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
|
||||||
|
"connection_max": int64(5000),
|
||||||
|
"enabled": true,
|
||||||
|
},
|
||||||
|
"servers": map[string]interface{}{
|
||||||
|
"alpha": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
"beta": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"clients": map[string]interface{}{
|
||||||
|
"data": []interface{}{
|
||||||
|
[]interface{}{"gamma", "delta"},
|
||||||
|
[]interface{}{int64(1), int64(2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testMaps(t, tree.ToMap(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[[menu.main]]
|
||||||
|
a = "menu 1"
|
||||||
|
b = "menu 2"
|
||||||
|
[[menu.main]]
|
||||||
|
c = "menu 3"
|
||||||
|
d = "menu 4"`)
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"menu": map[string]interface{}{
|
||||||
|
"main": []interface{}{
|
||||||
|
map[string]interface{}{"a": "menu 1", "b": "menu 2"},
|
||||||
|
map[string]interface{}{"c": "menu 3", "d": "menu 4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
treeMap := tree.ToMap()
|
||||||
|
|
||||||
|
testMaps(t, treeMap, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[params]
|
||||||
|
language_tabs = [
|
||||||
|
{ key = "shell", name = "Shell" },
|
||||||
|
{ key = "ruby", name = "Ruby" },
|
||||||
|
{ key = "python", name = "Python" }
|
||||||
|
]`)
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"params": map[string]interface{}{
|
||||||
|
"language_tabs": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"key": "shell",
|
||||||
|
"name": "Shell",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"key": "ruby",
|
||||||
|
"name": "Ruby",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"key": "python",
|
||||||
|
"name": "Python",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
treeMap := tree.ToMap()
|
||||||
|
testMaps(t, treeMap, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeWriteToFloat(t *testing.T) {
|
||||||
|
tree, err := Load(`a = 3.0`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
str, err := tree.ToTomlString()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := `a = 3.0`
|
||||||
|
if strings.TrimSpace(str) != strings.TrimSpace(expected) {
|
||||||
|
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTreeToTomlString(b *testing.B) {
|
||||||
|
toml, err := Load(sampleHard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := toml.ToTomlString()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleHard = `# Test file for TOML
|
||||||
|
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
||||||
|
# This part you'll really hate
|
||||||
|
|
||||||
|
[the]
|
||||||
|
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
||||||
|
|
||||||
|
[the.hard]
|
||||||
|
test_array = [ "] ", " # "] # ] There you go, parse this!
|
||||||
|
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
||||||
|
# You didn't think it'd as easy as chucking out the last #, did you?
|
||||||
|
another_test_string = " Same thing, but with a string #"
|
||||||
|
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
||||||
|
# Things will get harder
|
||||||
|
|
||||||
|
[the.hard."bit#"]
|
||||||
|
"what?" = "You don't think some user won't do that?"
|
||||||
|
multi_line_array = [
|
||||||
|
"]",
|
||||||
|
# ] Oh yes I did
|
||||||
|
]
|
||||||
|
|
||||||
|
# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
|
||||||
|
|
||||||
|
#[error] if you didn't catch this, your parser is broken
|
||||||
|
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this
|
||||||
|
#array = [
|
||||||
|
# "This might most likely happen in multiline arrays",
|
||||||
|
# Like here,
|
||||||
|
# "or here,
|
||||||
|
# and here"
|
||||||
|
# ] End of array comment, forgot the #
|
||||||
|
#number = 3.14 pi <--again forgot the # `
|
|
@ -0,0 +1,100 @@
|
||||||
|
Liner
|
||||||
|
=====
|
||||||
|
|
||||||
|
Liner is a command line editor with history. It was inspired by linenoise;
|
||||||
|
everything Unix-like is a VT100 (or is trying very hard to be). If your
|
||||||
|
terminal is not pretending to be a VT100, change it. Liner also support
|
||||||
|
Windows.
|
||||||
|
|
||||||
|
Liner is released under the X11 license (which is similar to the new BSD
|
||||||
|
license).
|
||||||
|
|
||||||
|
Line Editing
|
||||||
|
------------
|
||||||
|
|
||||||
|
The following line editing commands are supported on platforms and terminals
|
||||||
|
that Liner supports:
|
||||||
|
|
||||||
|
Keystroke | Action
|
||||||
|
--------- | ------
|
||||||
|
Ctrl-A, Home | Move cursor to beginning of line
|
||||||
|
Ctrl-E, End | Move cursor to end of line
|
||||||
|
Ctrl-B, Left | Move cursor one character left
|
||||||
|
Ctrl-F, Right| Move cursor one character right
|
||||||
|
Ctrl-Left, Alt-B | Move cursor to previous word
|
||||||
|
Ctrl-Right, Alt-F | Move cursor to next word
|
||||||
|
Ctrl-D, Del | (if line is *not* empty) Delete character under cursor
|
||||||
|
Ctrl-D | (if line *is* empty) End of File - usually quits application
|
||||||
|
Ctrl-C | Reset input (create new empty prompt)
|
||||||
|
Ctrl-L | Clear screen (line is unmodified)
|
||||||
|
Ctrl-T | Transpose previous character with current character
|
||||||
|
Ctrl-H, BackSpace | Delete character before cursor
|
||||||
|
Ctrl-W | Delete word leading up to cursor
|
||||||
|
Ctrl-K | Delete from cursor to end of line
|
||||||
|
Ctrl-U | Delete from start of line to cursor
|
||||||
|
Ctrl-P, Up | Previous match from history
|
||||||
|
Ctrl-N, Down | Next match from history
|
||||||
|
Ctrl-R | Reverse Search history (Ctrl-S forward, Ctrl-G cancel)
|
||||||
|
Ctrl-Y | Paste from Yank buffer (Alt-Y to paste next yank instead)
|
||||||
|
Tab | Next completion
|
||||||
|
Shift-Tab | (after Tab) Previous completion
|
||||||
|
|
||||||
|
Getting started
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/peterh/liner"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
history_fn = filepath.Join(os.TempDir(), ".liner_example_history")
|
||||||
|
names = []string{"john", "james", "mary", "nancy"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
line := liner.NewLiner()
|
||||||
|
defer line.Close()
|
||||||
|
|
||||||
|
line.SetCtrlCAborts(true)
|
||||||
|
|
||||||
|
line.SetCompleter(func(line string) (c []string) {
|
||||||
|
for _, n := range names {
|
||||||
|
if strings.HasPrefix(n, strings.ToLower(line)) {
|
||||||
|
c = append(c, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
if f, err := os.Open(history_fn); err == nil {
|
||||||
|
line.ReadHistory(f)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if name, err := line.Prompt("What is your name? "); err == nil {
|
||||||
|
log.Print("Got: ", name)
|
||||||
|
line.AppendHistory(name)
|
||||||
|
} else if err == liner.ErrPromptAborted {
|
||||||
|
log.Print("Aborted")
|
||||||
|
} else {
|
||||||
|
log.Print("Error reading line: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, err := os.Create(history_fn); err != nil {
|
||||||
|
log.Print("Error writing history file: ", err)
|
||||||
|
} else {
|
||||||
|
line.WriteHistory(f)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For documentation, see http://godoc.org/github.com/peterh/liner
|
|
@ -64,6 +64,11 @@ var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
|
||||||
// be colour codes on some platforms).
|
// be colour codes on some platforms).
|
||||||
var ErrInvalidPrompt = errors.New("invalid prompt")
|
var ErrInvalidPrompt = errors.New("invalid prompt")
|
||||||
|
|
||||||
|
// ErrInternal is returned when liner experiences an error that it cannot
|
||||||
|
// handle. For example, if the number of colums becomes zero during an
|
||||||
|
// active call to Prompt
|
||||||
|
var ErrInternal = errors.New("liner: internal error")
|
||||||
|
|
||||||
// KillRingMax is the max number of elements to save on the killring.
|
// KillRingMax is the max number of elements to save on the killring.
|
||||||
const KillRingMax = 60
|
const KillRingMax = 60
|
||||||
|
|
||||||
|
@ -156,7 +161,7 @@ func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the history lines matching the inteligent search
|
// Returns the history lines matching the intelligent search
|
||||||
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
|
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
|
||||||
if pattern == "" {
|
if pattern == "" {
|
||||||
return
|
return
|
|
@ -113,7 +113,7 @@ func (s *State) nextPending(timeout <-chan time.Time) (rune, error) {
|
||||||
select {
|
select {
|
||||||
case thing, ok := <-s.next:
|
case thing, ok := <-s.next:
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, errors.New("liner: internal error")
|
return 0, ErrInternal
|
||||||
}
|
}
|
||||||
if thing.err != nil {
|
if thing.err != nil {
|
||||||
return 0, thing.err
|
return 0, thing.err
|
||||||
|
@ -137,7 +137,7 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
select {
|
select {
|
||||||
case thing, ok := <-s.next:
|
case thing, ok := <-s.next:
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, errors.New("liner: internal error")
|
return 0, ErrInternal
|
||||||
}
|
}
|
||||||
if thing.err != nil {
|
if thing.err != nil {
|
||||||
return nil, thing.err
|
return nil, thing.err
|
||||||
|
@ -328,6 +328,9 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
case 'b':
|
case 'b':
|
||||||
s.pending = s.pending[:0] // escape code complete
|
s.pending = s.pending[:0] // escape code complete
|
||||||
return altB, nil
|
return altB, nil
|
||||||
|
case 'd':
|
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return altD, nil
|
||||||
case 'f':
|
case 'f':
|
||||||
s.pending = s.pending[:0] // escape code complete
|
s.pending = s.pending[:0] // escape code complete
|
||||||
return altF, nil
|
return altF, nil
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue