mirror of https://github.com/kubernetes/kops.git
More missing deps
This commit is contained in:
parent
68076b4f01
commit
17709f212a
|
|
@ -160,3 +160,18 @@
|
|||
[submodule "_vendor/golang.org/x/sys"]
|
||||
path = _vendor/golang.org/x/sys
|
||||
url = https://github.com/golang/sys/
|
||||
[submodule "_vendor/github.com/BurntSushi/toml"]
|
||||
path = _vendor/github.com/BurntSushi/toml
|
||||
url = https://github.com/BurntSushi/toml
|
||||
[submodule "_vendor/github.com/kr/fs"]
|
||||
path = _vendor/github.com/kr/fs
|
||||
url = https://github.com/kr/fs
|
||||
[submodule "_vendor/github.com/magiconair/properties"]
|
||||
path = _vendor/github.com/magiconair/properties
|
||||
url = https://github.com/magiconair/properties
|
||||
[submodule "_vendor/github.com/mitchellh/mapstructure"]
|
||||
path = _vendor/github.com/mitchellh/mapstructure
|
||||
url = https://github.com/mitchellh/mapstructure
|
||||
[submodule "_vendor/gopkg.in/yaml.v2"]
|
||||
path = _vendor/gopkg.in/yaml.v2
|
||||
url = https://github.com/go-yaml/yaml
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 99064174e013895bbd9b025c31100bd1d9b590ca
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2788f0dbd16903de03cb8186e5c7d97b69ad387b
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit b3f6dd549956e8a61ea4a686a1c02a33d5bdda4b
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 21a35fb16463dfb7c8eee579c65d995d95e64d1e
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e4d366fc3c7938e2958e662b4258c7a89e1f0e3e
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
TAGS
|
||||
tags
|
||||
.*.swp
|
||||
tomlcheck/tomlcheck
|
||||
toml.test
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
install:
|
||||
- go install ./...
|
||||
- go get github.com/BurntSushi/toml-test
|
||||
script:
|
||||
- export PATH="$PATH:$HOME/gopath/bin"
|
||||
- make test
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Compatible with TOML version
|
||||
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
install:
|
||||
go install ./...
|
||||
|
||||
test: install
|
||||
go test -v
|
||||
toml-test toml-test-decoder
|
||||
toml-test -encoder toml-test-encoder
|
||||
|
||||
fmt:
|
||||
gofmt -w *.go */*.go
|
||||
colcheck *.go */*.go
|
||||
|
||||
tags:
|
||||
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
||||
|
||||
push:
|
||||
git push origin master
|
||||
git push github master
|
||||
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
## TOML parser and encoder for Go with reflection
|
||||
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml`
|
||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||
representations. (There is an example of this below.)
|
||||
|
||||
Spec: https://github.com/mojombo/toml
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md)
|
||||
|
||||
Documentation: http://godoc.org/github.com/BurntSushi/toml
|
||||
|
||||
Installation:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml
|
||||
```
|
||||
|
||||
Try the toml validator:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/toml)
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
This package passes all tests in
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||
and the encoder.
|
||||
|
||||
### Examples
|
||||
|
||||
This package works similarly to how the Go standard library handles `XML`
|
||||
and `JSON`. Namely, data is loaded into Go values via reflection.
|
||||
|
||||
For the simplest example, consider some TOML file as just a list of keys
|
||||
and values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which could be defined in Go as:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time // requires `import time`
|
||||
}
|
||||
```
|
||||
|
||||
And then decoded with:
|
||||
|
||||
```go
|
||||
var conf Config
|
||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||
key value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
```
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
### Using the `encoding.TextUnmarshaler` interface
|
||||
|
||||
Here's an example that automatically parses duration strings into
|
||||
`time.Duration` values:
|
||||
|
||||
```toml
|
||||
[[song]]
|
||||
name = "Thunder Road"
|
||||
duration = "4m49s"
|
||||
|
||||
[[song]]
|
||||
name = "Stairway to Heaven"
|
||||
duration = "8m03s"
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type song struct {
|
||||
Name string
|
||||
Duration duration
|
||||
}
|
||||
type songs struct {
|
||||
Song []song
|
||||
}
|
||||
var favorites songs
|
||||
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range favorites.Song {
|
||||
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||
}
|
||||
```
|
||||
|
||||
And you'll also need a `duration` type that satisfies the
|
||||
`encoding.TextUnmarshaler` interface:
|
||||
|
||||
```go
|
||||
type duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *duration) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(text))
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### More complex usage
|
||||
|
||||
Here's an example of how to load the example from the official spec page:
|
||||
|
||||
```toml
|
||||
# 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
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
```
|
||||
|
||||
And the corresponding Go types are:
|
||||
|
||||
```go
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
```
|
||||
|
||||
Note that a case insensitive match will be tried if an exact match can't be
|
||||
found.
|
||||
|
||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
||||
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var config tomlConfig
|
||||
if _, err := toml.DecodeFile("example.toml", &config); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Title: %s\n", config.Title)
|
||||
fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
|
||||
config.Owner.Name, config.Owner.Org, config.Owner.Bio,
|
||||
config.Owner.DOB)
|
||||
fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
|
||||
config.DB.Server, config.DB.Ports, config.DB.ConnMax,
|
||||
config.DB.Enabled)
|
||||
for serverName, server := range config.Servers {
|
||||
fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
|
||||
}
|
||||
fmt.Printf("Client data: %v\n", config.Clients.Data)
|
||||
fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# 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
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# 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
|
||||
]
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# [x] you
|
||||
# [x.y] don't
|
||||
# [x.y.z] need these
|
||||
[x.y.z.w] # for this to work
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# DO NOT WANT
|
||||
[fruit]
|
||||
type = "apple"
|
||||
|
||||
[fruit.type]
|
||||
apple = "yes"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# This is an INVALID TOML document. Boom.
|
||||
# Can you spot the error without help?
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T7: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
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
|
|
@ -0,0 +1 @@
|
|||
some_key_NAME = "wat"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Implements the TOML test suite interface
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for my
|
||||
[toml parser written in Go](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
||||
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Command toml-test-decoder satisfies the toml-test interface for testing
|
||||
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
|
||||
log.Fatalf("Error decoding TOML: %s", err)
|
||||
}
|
||||
|
||||
typedTmp := translate(tmp)
|
||||
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
|
||||
log.Fatalf("Error encoding JSON: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(tomlData interface{}) interface{} {
|
||||
switch orig := tomlData.(type) {
|
||||
case map[string]interface{}:
|
||||
typed := make(map[string]interface{}, len(orig))
|
||||
for k, v := range orig {
|
||||
typed[k] = translate(v)
|
||||
}
|
||||
return typed
|
||||
case []map[string]interface{}:
|
||||
typed := make([]map[string]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v).(map[string]interface{})
|
||||
}
|
||||
return typed
|
||||
case []interface{}:
|
||||
typed := make([]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v)
|
||||
}
|
||||
|
||||
// We don't really need to tag arrays, but let's be future proof.
|
||||
// (If TOML ever supports tuples, we'll need this.)
|
||||
return tag("array", typed)
|
||||
case time.Time:
|
||||
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
|
||||
case bool:
|
||||
return tag("bool", fmt.Sprintf("%v", orig))
|
||||
case int64:
|
||||
return tag("integer", fmt.Sprintf("%d", orig))
|
||||
case float64:
|
||||
return tag("float", fmt.Sprintf("%v", orig))
|
||||
case string:
|
||||
return tag("string", orig)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unknown type: %T", tomlData))
|
||||
}
|
||||
|
||||
func tag(typeName string, data interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": typeName,
|
||||
"value": data,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Implements the TOML test suite interface for TOML encoders
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for the
|
||||
[TOML encoder](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
||||
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// Command toml-test-encoder satisfies the toml-test interface for testing
|
||||
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
|
||||
log.Fatalf("Error decoding JSON: %s", err)
|
||||
}
|
||||
|
||||
tomlData := translate(tmp)
|
||||
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
|
||||
log.Fatalf("Error encoding TOML: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(typedJson interface{}) interface{} {
|
||||
switch v := typedJson.(type) {
|
||||
case map[string]interface{}:
|
||||
if len(v) == 2 && in("type", v) && in("value", v) {
|
||||
return untag(v)
|
||||
}
|
||||
m := make(map[string]interface{}, len(v))
|
||||
for k, v2 := range v {
|
||||
m[k] = translate(v2)
|
||||
}
|
||||
return m
|
||||
case []interface{}:
|
||||
tabArray := make([]map[string]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := translate(v[i]).(map[string]interface{}); ok {
|
||||
tabArray[i] = m
|
||||
} else {
|
||||
log.Fatalf("JSON arrays may only contain objects. This " +
|
||||
"corresponds to only tables being allowed in " +
|
||||
"TOML table arrays.")
|
||||
}
|
||||
}
|
||||
return tabArray
|
||||
}
|
||||
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func untag(typed map[string]interface{}) interface{} {
|
||||
t := typed["type"].(string)
|
||||
v := typed["value"]
|
||||
switch t {
|
||||
case "string":
|
||||
return v.(string)
|
||||
case "integer":
|
||||
v := v.(string)
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
|
||||
}
|
||||
return n
|
||||
case "float":
|
||||
v := v.(string)
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
|
||||
}
|
||||
return f
|
||||
case "datetime":
|
||||
v := v.(string)
|
||||
t, err := time.Parse("2006-01-02T15:04:05Z", v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
|
||||
}
|
||||
return t
|
||||
case "bool":
|
||||
v := v.(string)
|
||||
switch v {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
}
|
||||
log.Fatalf("Could not parse '%s' as a boolean.", v)
|
||||
case "array":
|
||||
v := v.([]interface{})
|
||||
array := make([]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := v[i].(map[string]interface{}); ok {
|
||||
array[i] = untag(m)
|
||||
} else {
|
||||
log.Fatalf("Arrays may only contain other arrays or "+
|
||||
"primitive values, but found a '%T'.", m)
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
log.Fatalf("Unrecognized tag type '%s'.", t)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func in(key string, m map[string]interface{}) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# TOML Validator
|
||||
|
||||
If Go is installed, it's simple to try it out:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
You can see the types of every key in a TOML file with:
|
||||
|
||||
```bash
|
||||
tomlv -types some-toml-file.toml
|
||||
```
|
||||
|
||||
At the moment, only one error message is reported at a time. Error messages
|
||||
include line numbers. No output means that the files given are valid TOML, or
|
||||
there is a bug in `tomlv`.
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md)
|
||||
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// Command tomlv validates TOML documents and prints each key's type.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
var (
|
||||
flagTypes = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.BoolVar(&flagTypes, "types", flagTypes,
|
||||
"When set, the types of every defined key will be shown.")
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
|
||||
path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() < 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
for _, f := range flag.Args() {
|
||||
var tmp interface{}
|
||||
md, err := toml.DecodeFile(f, &tmp)
|
||||
if err != nil {
|
||||
log.Fatalf("Error in '%s': %s", f, err)
|
||||
}
|
||||
if flagTypes {
|
||||
printTypes(md)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTypes(md toml.MetaData) {
|
||||
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
for _, key := range md.Keys() {
|
||||
fmt.Fprintf(tabw, "%s%s\t%s\n",
|
||||
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
|
||||
}
|
||||
tabw.Flush()
|
||||
}
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func e(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("toml: "+format, args...)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||
func Unmarshal(p []byte, v interface{}) error {
|
||||
_, err := Decode(string(p), v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
// When using the various `Decode*` functions, the type `Primitive` may
|
||||
// be given to any value, and its decoding will be delayed.
|
||||
//
|
||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
||||
//
|
||||
// The underlying representation of a `Primitive` value is subject to change.
|
||||
// Do not rely on it.
|
||||
//
|
||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
||||
// the overhead of reflection. They can be useful when you don't know the
|
||||
// exact type of TOML data until run time.
|
||||
type Primitive struct {
|
||||
undecoded interface{}
|
||||
context Key
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use MetaData.PrimitiveDecode instead.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]bool)}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||
// can *only* be obtained from values filled by the decoder functions,
|
||||
// including this method. (i.e., `v` may contain more `Primitive`
|
||||
// values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by
|
||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||
// behind a Primitive will be considered undecoded. Executing this method will
|
||||
// update the undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
||||
// `v`.
|
||||
//
|
||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
||||
// used interchangeably.)
|
||||
//
|
||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
||||
// of maps.
|
||||
//
|
||||
// TOML datetimes correspond to Go `time.Time` values.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond
|
||||
// to the obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the
|
||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
||||
// a byte string and given to the value's UnmarshalText method. See the
|
||||
// Unmarshaler example for a demonstration with time duration strings.
|
||||
//
|
||||
// Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
||||
// struct fields that don't match the key name exactly. (See the example.)
|
||||
// A case insensitive match to struct names will be tried if an exact match
|
||||
// can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there
|
||||
// may exist TOML values that cannot be placed into your representation, and
|
||||
// there may be parts of your representation that do not correspond to
|
||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
||||
// and/or Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
||||
// `Decode` will not terminate.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||
}
|
||||
p, err := parse(data)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
md := MetaData{
|
||||
p.mapping, p.types, p.ordered,
|
||||
make(map[string]bool, len(p.ordered)), nil,
|
||||
}
|
||||
return md, md.unify(p.mapping, indirect(rv))
|
||||
}
|
||||
|
||||
// DecodeFile is just like Decode, except it will automatically read the
|
||||
// contents of the file at `fpath` and decode it for you.
|
||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// DecodeReader is just like Decode, except it will consume all bytes
|
||||
// from the reader and decode it for you.
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
|
||||
// Special case. Look for a `Primitive` value.
|
||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
copy(context, md.context)
|
||||
rv.Set(reflect.ValueOf(Primitive{
|
||||
undecoded: data,
|
||||
context: context,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special case. Unmarshaler Interface support.
|
||||
if rv.CanAddr() {
|
||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Special case. Handle time.Time values specifically.
|
||||
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
||||
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
||||
// interfaces.
|
||||
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
||||
return md.unifyDatetime(data, rv)
|
||||
}
|
||||
|
||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
// BUG(burntsushi)
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
||||
// hash or array. In particular, the unmarshaler should only be applied
|
||||
// to primitive TOML values. But at this point, it will be applied to
|
||||
// all kinds of values and produce an incorrect error whenever those values
|
||||
// are hashes or arrays (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
// laziness
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Ptr:
|
||||
elem := reflect.New(rv.Type().Elem())
|
||||
err := md.unify(data, reflect.Indirect(elem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv.Set(elem)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
return md.unifyMap(data, rv)
|
||||
case reflect.Array:
|
||||
return md.unifyArray(data, rv)
|
||||
case reflect.Slice:
|
||||
return md.unifySlice(data, rv)
|
||||
case reflect.String:
|
||||
return md.unifyString(data, rv)
|
||||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
// we only support empty interfaces.
|
||||
if rv.NumMethod() > 0 {
|
||||
return e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
var f *field
|
||||
fields := cachedTypeFields(rv.Type())
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if ff.name == key {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && strings.EqualFold(ff.name, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
subv := rv
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = true
|
||||
md.context = append(md.context, key)
|
||||
if err := md.unify(datum, subv); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
// Bad user! No soup for you!
|
||||
return e("cannot write unexported field %s.%s",
|
||||
rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = true
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
if err := md.unify(v, rvval); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey.SetString(k)
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
sliceLen := datav.Len()
|
||||
if sliceLen != rv.Len() {
|
||||
return e("expected array length %d; got TOML array of length %d",
|
||||
rv.Len(), sliceLen)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||
}
|
||||
rv.SetLen(n)
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
sliceLen := data.Len()
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
v := data.Index(i).Interface()
|
||||
sliceval := indirect(rv.Index(i))
|
||||
if err := md.unify(v, sliceval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||
if _, ok := data.(time.Time); ok {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
return badtype("time.Time", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rv.Kind() {
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(int64); ok {
|
||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Int8:
|
||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||
return e("value %d is out of range for int8", num)
|
||||
}
|
||||
case reflect.Int16:
|
||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||
return e("value %d is out of range for int16", num)
|
||||
}
|
||||
case reflect.Int32:
|
||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||
return e("value %d is out of range for int32", num)
|
||||
}
|
||||
}
|
||||
rv.SetInt(num)
|
||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||
unum := uint64(num)
|
||||
switch rv.Kind() {
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Uint8:
|
||||
if num < 0 || unum > math.MaxUint8 {
|
||||
return e("value %d is out of range for uint8", num)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
if num < 0 || unum > math.MaxUint16 {
|
||||
return e("value %d is out of range for uint16", num)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
if num < 0 || unum > math.MaxUint32 {
|
||||
return e("value %d is out of range for uint32", num)
|
||||
}
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case fmt.Stringer:
|
||||
s = sdata.String()
|
||||
case string:
|
||||
s = sdata
|
||||
case bool:
|
||||
s = fmt.Sprintf("%v", sdata)
|
||||
case int64:
|
||||
s = fmt.Sprintf("%d", sdata)
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
// Pointers are followed until the value is not a pointer.
|
||||
// New values are allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of
|
||||
// interest to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return indirect(reflect.Indirect(v))
|
||||
}
|
||||
|
||||
func isUnifiable(rv reflect.Value) bool {
|
||||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func badtype(expected string, data interface{}) error {
|
||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package toml
|
||||
|
||||
import "strings"
|
||||
|
||||
// MetaData allows access to meta information about TOML data that may not
|
||||
// be inferrable via reflection. In particular, whether a key has been defined
|
||||
// and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
}
|
||||
|
||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||
// should be specified hierarchially. e.g.,
|
||||
//
|
||||
// // access the TOML key 'a.b.c'
|
||||
// IsDefined("a", "b", "c")
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hash map[string]interface{}
|
||||
var ok bool
|
||||
var hashOrVal interface{} = md.mapping
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that
|
||||
// does not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
return typ.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
||||
// to get values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
return strings.Join(k, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuotedAll() string {
|
||||
var ss []string
|
||||
for i := range k {
|
||||
ss = append(ss, k.maybeQuoted(i))
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
quote := false
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
quote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if quote {
|
||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific.
|
||||
//
|
||||
// The list will have the same order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a Primitive value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Package toml provides facilities for decoding and encoding TOML configuration
|
||||
files via reflection. There is also support for delaying decoding with
|
||||
the Primitive type, and querying the set of keys in a TOML document with the
|
||||
MetaData type.
|
||||
|
||||
The specification implemented: https://github.com/mojombo/toml
|
||||
|
||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
||||
whether a file is a valid TOML document. It can also be used to print the
|
||||
type of each key in a TOML document.
|
||||
|
||||
Testing
|
||||
|
||||
There are two important types of tests used for this package. The first is
|
||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
||||
framework. These tests are primarily devoted to holistically testing the
|
||||
decoder and encoder.
|
||||
|
||||
The second type of testing is used to verify the implementation's adherence
|
||||
to the TOML specification. These tests have been factored into their own
|
||||
project: https://github.com/BurntSushi/toml-test
|
||||
|
||||
The reason the tests are in a separate project is so that they can be used by
|
||||
any implementation of TOML. Namely, it is language agnostic.
|
||||
*/
|
||||
package toml
|
||||
|
|
@ -0,0 +1,568 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayMixedElementTypes = errors.New(
|
||||
"toml: cannot encode array with mixed element types")
|
||||
errArrayNilElement = errors.New(
|
||||
"toml: cannot encode array with nil element")
|
||||
errNonString = errors.New(
|
||||
"toml: cannot encode a map with non-string key type")
|
||||
errAnonNonStruct = errors.New(
|
||||
"toml: cannot encode an anonymous field that is not a struct")
|
||||
errArrayNoTable = errors.New(
|
||||
"toml: TOML array element cannot contain a table")
|
||||
errNoKey = errors.New(
|
||||
"toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var quotedReplacer = strings.NewReplacer(
|
||||
"\t", "\\t",
|
||||
"\n", "\\n",
|
||||
"\r", "\\r",
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
)
|
||||
|
||||
// Encoder controls the encoding of Go values to a TOML document to some
|
||||
// io.Writer.
|
||||
//
|
||||
// The indentation level can be controlled with the Indent field.
|
||||
type Encoder struct {
|
||||
// A single indentation level. By default it is two spaces.
|
||||
Indent string
|
||||
|
||||
// hasWritten is whether we have written any output to w yet.
|
||||
hasWritten bool
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||
// given. By default, a single indentation level is 2 spaces.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
Indent: " ",
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the underlying
|
||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
||||
// then an error is returned.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same
|
||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
||||
// arbitrary binary data then you will need to use something like base64 since
|
||||
// TOML does not have any binary types.)
|
||||
//
|
||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
||||
// sub-hashes are encoded first.
|
||||
//
|
||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
||||
// deterministic output. More control over this behavior may be provided if
|
||||
// there is demand for it.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation---like map
|
||||
// types with non-string keys---will cause an error to be returned. Similarly
|
||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
||||
// non-struct types and nested slices containing maps or structs.
|
||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
||||
// and so is []map[string][]string.)
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if terr, ok := r.(tomlEncodeError); ok {
|
||||
err = terr.error
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
enc.encode(key, rv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// Special case. Time needs to be in ISO8601 format.
|
||||
// Special case. If we can marshal the type to text, then we used that.
|
||||
// Basically, this prevents the encoder for handling these types as
|
||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time, TextMarshaler:
|
||||
enc.keyEqElement(key, rv)
|
||||
return
|
||||
}
|
||||
|
||||
k := rv.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.keyEqElement(key, rv)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.keyEqElement(key, rv)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Map:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.eTable(key, rv)
|
||||
case reflect.Ptr:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
panic(e("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element (primitives and
|
||||
// arrays).
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time:
|
||||
// Special case time.Time as a primitive. Has to come before
|
||||
// TextMarshaler below because time.Time implements
|
||||
// encoding.TextMarshaler, but we need to always use UTC.
|
||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
return
|
||||
case TextMarshaler:
|
||||
// Special case. Use text marshaler if it's available for this value.
|
||||
if s, err := v.MarshalText(); err != nil {
|
||||
encPanic(err)
|
||||
} else {
|
||||
enc.writeQuoted(string(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
||||
case reflect.Float64:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
default:
|
||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one
|
||||
// number on either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := rv.Index(i)
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := rv.Index(i)
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
panicIfInvalidKey(key)
|
||||
if len(key) == 1 {
|
||||
// Output an extra new line between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
||||
switch rv := eindirect(rv); rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv)
|
||||
default:
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
}
|
||||
|
||||
// Sort keys so that we have deterministic output. And write keys directly
|
||||
// underneath this key first, before writing sub-structs or sub-maps.
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string) {
|
||||
sort.Strings(mapKeys)
|
||||
for _, mapKey := range mapKeys {
|
||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||
if isNil(mrv) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
enc.encode(key.add(mapKey), mrv)
|
||||
}
|
||||
}
|
||||
writeMapKeys(mapKeysDirect)
|
||||
writeMapKeys(mapKeysSub)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table, then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
rt := rv.Type()
|
||||
var fieldsDirect, fieldsSub [][]int
|
||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
// skip unexported fields
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
frv := rv.Field(i)
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
// Treat anonymous struct fields with
|
||||
// tag names as though they are not
|
||||
// anonymous, like encoding/json does.
|
||||
if getOptions(f.Tag).name == "" {
|
||||
addFields(t, frv, f.Index)
|
||||
continue
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t.Elem().Kind() == reflect.Struct &&
|
||||
getOptions(f.Tag).name == "" {
|
||||
if !frv.IsNil() {
|
||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Fall through to the normal field encoding logic below
|
||||
// for non-struct anonymous fields.
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
var writeFields = func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
sft := rt.FieldByIndex(fieldIndex)
|
||||
sf := rv.FieldByIndex(fieldIndex)
|
||||
if isNil(sf) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
opts := getOptions(sft.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
keyName := sft.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
if opts.omitempty && isEmpty(sf) {
|
||||
continue
|
||||
}
|
||||
if opts.omitzero && isZero(sf) {
|
||||
continue
|
||||
}
|
||||
|
||||
enc.encode(key.add(keyName), sf)
|
||||
}
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
}
|
||||
|
||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||
// used to determine whether the types of array elements are mixed (which is
|
||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||
// element, and valueIsNil is returned as true.
|
||||
|
||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||
// no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
return tomlInteger
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return tomlTypeOfGo(rv.Elem())
|
||||
case reflect.String:
|
||||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
case reflect.Struct:
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time:
|
||||
return tomlDatetime
|
||||
case TextMarshaler:
|
||||
return tomlString
|
||||
default:
|
||||
return tomlHash
|
||||
}
|
||||
default:
|
||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||
// slize). This function may also panic if it finds a type that cannot be
|
||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||
// nested arrays of tables).
|
||||
func tomlArrayType(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
firstType := tomlTypeOfGo(rv.Index(0))
|
||||
if firstType == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
}
|
||||
|
||||
rvlen := rv.Len()
|
||||
for i := 1; i < rvlen; i++ {
|
||||
elem := rv.Index(i)
|
||||
switch elemType := tomlTypeOfGo(elem); {
|
||||
case elemType == nil:
|
||||
encPanic(errArrayNilElement)
|
||||
case !typeEqual(firstType, elemType):
|
||||
encPanic(errArrayMixedElementTypes)
|
||||
}
|
||||
}
|
||||
// If we have a nested array, then we must make sure that the nested
|
||||
// array contains ONLY primitives.
|
||||
// This checks arbitrarily nested arrays.
|
||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
||||
encPanic(errArrayNoTable)
|
||||
}
|
||||
}
|
||||
return firstType
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
skip bool // "-"
|
||||
name string
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
}
|
||||
|
||||
func getOptions(tag reflect.StructTag) tagOptions {
|
||||
t := tag.Get("toml")
|
||||
if t == "-" {
|
||||
return tagOptions{skip: true}
|
||||
}
|
||||
var opts tagOptions
|
||||
parts := strings.Split(t, ",")
|
||||
opts.name = parts[0]
|
||||
for _, s := range parts[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func isZero(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0.0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
enc.newline()
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) indentStr(key Key) string {
|
||||
return strings.Repeat(enc.Indent, len(key)-1)
|
||||
}
|
||||
|
||||
func encPanic(err error) {
|
||||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return eindirect(v.Elem())
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func panicIfInvalidKey(key Key) {
|
||||
for _, k := range key {
|
||||
if len(k) == 0 {
|
||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
||||
"cannot be empty.", key.maybeQuotedAll()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidKeyName(s string) bool {
|
||||
return len(s) != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,615 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEncodeRoundTrip(t *testing.T) {
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time
|
||||
Ipaddress net.IP
|
||||
}
|
||||
|
||||
var inputs = Config{
|
||||
13,
|
||||
[]string{"one", "two", "three"},
|
||||
3.145,
|
||||
[]int{11, 2, 3, 4},
|
||||
time.Now(),
|
||||
net.ParseIP("192.168.59.254"),
|
||||
}
|
||||
|
||||
var firstBuffer bytes.Buffer
|
||||
e := NewEncoder(&firstBuffer)
|
||||
err := e.Encode(inputs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var outputs Config
|
||||
if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
|
||||
t.Logf("Could not decode:\n-----\n%s\n-----\n",
|
||||
firstBuffer.String())
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// could test each value individually, but I'm lazy
|
||||
var secondBuffer bytes.Buffer
|
||||
e2 := NewEncoder(&secondBuffer)
|
||||
err = e2.Encode(outputs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if firstBuffer.String() != secondBuffer.String() {
|
||||
t.Error(
|
||||
firstBuffer.String(),
|
||||
"\n\n is not identical to\n\n",
|
||||
secondBuffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
// XXX(burntsushi)
|
||||
// I think these tests probably should be removed. They are good, but they
|
||||
// ought to be obsolete by toml-test.
|
||||
func TestEncode(t *testing.T) {
|
||||
type Embedded struct {
|
||||
Int int `toml:"_int"`
|
||||
}
|
||||
type NonStruct int
|
||||
|
||||
date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
|
||||
dateStr := "2014-05-11T19:30:40Z"
|
||||
|
||||
tests := map[string]struct {
|
||||
input interface{}
|
||||
wantOutput string
|
||||
wantError error
|
||||
}{
|
||||
"bool field": {
|
||||
input: struct {
|
||||
BoolTrue bool
|
||||
BoolFalse bool
|
||||
}{true, false},
|
||||
wantOutput: "BoolTrue = true\nBoolFalse = false\n",
|
||||
},
|
||||
"int fields": {
|
||||
input: struct {
|
||||
Int int
|
||||
Int8 int8
|
||||
Int16 int16
|
||||
Int32 int32
|
||||
Int64 int64
|
||||
}{1, 2, 3, 4, 5},
|
||||
wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
|
||||
},
|
||||
"uint fields": {
|
||||
input: struct {
|
||||
Uint uint
|
||||
Uint8 uint8
|
||||
Uint16 uint16
|
||||
Uint32 uint32
|
||||
Uint64 uint64
|
||||
}{1, 2, 3, 4, 5},
|
||||
wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
|
||||
"\nUint64 = 5\n",
|
||||
},
|
||||
"float fields": {
|
||||
input: struct {
|
||||
Float32 float32
|
||||
Float64 float64
|
||||
}{1.5, 2.5},
|
||||
wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
|
||||
},
|
||||
"string field": {
|
||||
input: struct{ String string }{"foo"},
|
||||
wantOutput: "String = \"foo\"\n",
|
||||
},
|
||||
"string field and unexported field": {
|
||||
input: struct {
|
||||
String string
|
||||
unexported int
|
||||
}{"foo", 0},
|
||||
wantOutput: "String = \"foo\"\n",
|
||||
},
|
||||
"datetime field in UTC": {
|
||||
input: struct{ Date time.Time }{date},
|
||||
wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
|
||||
},
|
||||
"datetime field as primitive": {
|
||||
// Using a map here to fail if isStructOrMap() returns true for
|
||||
// time.Time.
|
||||
input: map[string]interface{}{
|
||||
"Date": date,
|
||||
"Int": 1,
|
||||
},
|
||||
wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
|
||||
},
|
||||
"array fields": {
|
||||
input: struct {
|
||||
IntArray0 [0]int
|
||||
IntArray3 [3]int
|
||||
}{[0]int{}, [3]int{1, 2, 3}},
|
||||
wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
|
||||
},
|
||||
"slice fields": {
|
||||
input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
|
||||
nil, []int{}, []int{1, 2, 3},
|
||||
},
|
||||
wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
|
||||
},
|
||||
"datetime slices": {
|
||||
input: struct{ DatetimeSlice []time.Time }{
|
||||
[]time.Time{date, date},
|
||||
},
|
||||
wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
|
||||
dateStr, dateStr),
|
||||
},
|
||||
"nested arrays and slices": {
|
||||
input: struct {
|
||||
SliceOfArrays [][2]int
|
||||
ArrayOfSlices [2][]int
|
||||
SliceOfArraysOfSlices [][2][]int
|
||||
ArrayOfSlicesOfArrays [2][][2]int
|
||||
SliceOfMixedArrays [][2]interface{}
|
||||
ArrayOfMixedSlices [2][]interface{}
|
||||
}{
|
||||
[][2]int{{1, 2}, {3, 4}},
|
||||
[2][]int{{1, 2}, {3, 4}},
|
||||
[][2][]int{
|
||||
{
|
||||
{1, 2}, {3, 4},
|
||||
},
|
||||
{
|
||||
{5, 6}, {7, 8},
|
||||
},
|
||||
},
|
||||
[2][][2]int{
|
||||
{
|
||||
{1, 2}, {3, 4},
|
||||
},
|
||||
{
|
||||
{5, 6}, {7, 8},
|
||||
},
|
||||
},
|
||||
[][2]interface{}{
|
||||
{1, 2}, {"a", "b"},
|
||||
},
|
||||
[2][]interface{}{
|
||||
{1, 2}, {"a", "b"},
|
||||
},
|
||||
},
|
||||
wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
|
||||
ArrayOfSlices = [[1, 2], [3, 4]]
|
||||
SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
SliceOfMixedArrays = [[1, 2], ["a", "b"]]
|
||||
ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
|
||||
`,
|
||||
},
|
||||
"empty slice": {
|
||||
input: struct{ Empty []interface{} }{[]interface{}{}},
|
||||
wantOutput: "Empty = []\n",
|
||||
},
|
||||
"(error) slice with element type mismatch (string and integer)": {
|
||||
input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"(error) slice with element type mismatch (integer and float)": {
|
||||
input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"slice with elems of differing Go types, same TOML types": {
|
||||
input: struct {
|
||||
MixedInts []interface{}
|
||||
MixedFloats []interface{}
|
||||
}{
|
||||
[]interface{}{
|
||||
int(1), int8(2), int16(3), int32(4), int64(5),
|
||||
uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
|
||||
},
|
||||
[]interface{}{float32(1.5), float64(2.5)},
|
||||
},
|
||||
wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
|
||||
"MixedFloats = [1.5, 2.5]\n",
|
||||
},
|
||||
"(error) slice w/ element type mismatch (one is nested array)": {
|
||||
input: struct{ Mixed []interface{} }{
|
||||
[]interface{}{1, []interface{}{2}},
|
||||
},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"(error) slice with 1 nil element": {
|
||||
input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
|
||||
wantError: errArrayNilElement,
|
||||
},
|
||||
"(error) slice with 1 nil element (and other non-nil elements)": {
|
||||
input: struct{ NilElement []interface{} }{
|
||||
[]interface{}{1, nil},
|
||||
},
|
||||
wantError: errArrayNilElement,
|
||||
},
|
||||
"simple map": {
|
||||
input: map[string]int{"a": 1, "b": 2},
|
||||
wantOutput: "a = 1\nb = 2\n",
|
||||
},
|
||||
"map with interface{} value type": {
|
||||
input: map[string]interface{}{"a": 1, "b": "c"},
|
||||
wantOutput: "a = 1\nb = \"c\"\n",
|
||||
},
|
||||
"map with interface{} value type, some of which are structs": {
|
||||
input: map[string]interface{}{
|
||||
"a": struct{ Int int }{2},
|
||||
"b": 1,
|
||||
},
|
||||
wantOutput: "b = 1\n\n[a]\n Int = 2\n",
|
||||
},
|
||||
"nested map": {
|
||||
input: map[string]map[string]int{
|
||||
"a": {"b": 1},
|
||||
"c": {"d": 2},
|
||||
},
|
||||
wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
|
||||
},
|
||||
"nested struct": {
|
||||
input: struct{ Struct struct{ Int int } }{
|
||||
struct{ Int int }{1},
|
||||
},
|
||||
wantOutput: "[Struct]\n Int = 1\n",
|
||||
},
|
||||
"nested struct and non-struct field": {
|
||||
input: struct {
|
||||
Struct struct{ Int int }
|
||||
Bool bool
|
||||
}{struct{ Int int }{1}, true},
|
||||
wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
|
||||
},
|
||||
"2 nested structs": {
|
||||
input: struct{ Struct1, Struct2 struct{ Int int } }{
|
||||
struct{ Int int }{1}, struct{ Int int }{2},
|
||||
},
|
||||
wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
|
||||
},
|
||||
"deeply nested structs": {
|
||||
input: struct {
|
||||
Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
|
||||
}{
|
||||
struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
|
||||
struct{ Struct3 *struct{ Int int } }{nil},
|
||||
},
|
||||
wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
|
||||
"\n\n[Struct2]\n",
|
||||
},
|
||||
"nested struct with nil struct elem": {
|
||||
input: struct {
|
||||
Struct struct{ Inner *struct{ Int int } }
|
||||
}{
|
||||
struct{ Inner *struct{ Int int } }{nil},
|
||||
},
|
||||
wantOutput: "[Struct]\n",
|
||||
},
|
||||
"nested struct with no fields": {
|
||||
input: struct {
|
||||
Struct struct{ Inner struct{} }
|
||||
}{
|
||||
struct{ Inner struct{} }{struct{}{}},
|
||||
},
|
||||
wantOutput: "[Struct]\n [Struct.Inner]\n",
|
||||
},
|
||||
"struct with tags": {
|
||||
input: struct {
|
||||
Struct struct {
|
||||
Int int `toml:"_int"`
|
||||
} `toml:"_struct"`
|
||||
Bool bool `toml:"_bool"`
|
||||
}{
|
||||
struct {
|
||||
Int int `toml:"_int"`
|
||||
}{1}, true,
|
||||
},
|
||||
wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
|
||||
},
|
||||
"embedded struct": {
|
||||
input: struct{ Embedded }{Embedded{1}},
|
||||
wantOutput: "_int = 1\n",
|
||||
},
|
||||
"embedded *struct": {
|
||||
input: struct{ *Embedded }{&Embedded{1}},
|
||||
wantOutput: "_int = 1\n",
|
||||
},
|
||||
"nested embedded struct": {
|
||||
input: struct {
|
||||
Struct struct{ Embedded } `toml:"_struct"`
|
||||
}{struct{ Embedded }{Embedded{1}}},
|
||||
wantOutput: "[_struct]\n _int = 1\n",
|
||||
},
|
||||
"nested embedded *struct": {
|
||||
input: struct {
|
||||
Struct struct{ *Embedded } `toml:"_struct"`
|
||||
}{struct{ *Embedded }{&Embedded{1}}},
|
||||
wantOutput: "[_struct]\n _int = 1\n",
|
||||
},
|
||||
"embedded non-struct": {
|
||||
input: struct{ NonStruct }{5},
|
||||
wantOutput: "NonStruct = 5\n",
|
||||
},
|
||||
"array of tables": {
|
||||
input: struct {
|
||||
Structs []*struct{ Int int } `toml:"struct"`
|
||||
}{
|
||||
[]*struct{ Int int }{{1}, {3}},
|
||||
},
|
||||
wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
|
||||
},
|
||||
"array of tables order": {
|
||||
input: map[string]interface{}{
|
||||
"map": map[string]interface{}{
|
||||
"zero": 5,
|
||||
"arr": []map[string]int{
|
||||
{
|
||||
"friend": 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
|
||||
},
|
||||
"(error) top-level slice": {
|
||||
input: []struct{ Int int }{{1}, {2}, {3}},
|
||||
wantError: errNoKey,
|
||||
},
|
||||
"(error) slice of slice": {
|
||||
input: struct {
|
||||
Slices [][]struct{ Int int }
|
||||
}{
|
||||
[][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
|
||||
},
|
||||
wantError: errArrayNoTable,
|
||||
},
|
||||
"(error) map no string key": {
|
||||
input: map[int]string{1: ""},
|
||||
wantError: errNonString,
|
||||
},
|
||||
"(error) empty key name": {
|
||||
input: map[string]int{"": 1},
|
||||
wantError: errAnything,
|
||||
},
|
||||
"(error) empty map name": {
|
||||
input: map[string]interface{}{
|
||||
"": map[string]int{"v": 1},
|
||||
},
|
||||
wantError: errAnything,
|
||||
},
|
||||
}
|
||||
for label, test := range tests {
|
||||
encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeNestedTableArrays(t *testing.T) {
|
||||
type song struct {
|
||||
Name string `toml:"name"`
|
||||
}
|
||||
type album struct {
|
||||
Name string `toml:"name"`
|
||||
Songs []song `toml:"songs"`
|
||||
}
|
||||
type springsteen struct {
|
||||
Albums []album `toml:"albums"`
|
||||
}
|
||||
value := springsteen{
|
||||
[]album{
|
||||
{"Born to Run",
|
||||
[]song{{"Jungleland"}, {"Meeting Across the River"}}},
|
||||
{"Born in the USA",
|
||||
[]song{{"Glory Days"}, {"Dancing in the Dark"}}},
|
||||
},
|
||||
}
|
||||
expected := `[[albums]]
|
||||
name = "Born to Run"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Jungleland"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Meeting Across the River"
|
||||
|
||||
[[albums]]
|
||||
name = "Born in the USA"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Glory Days"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Dancing in the Dark"
|
||||
`
|
||||
encodeExpected(t, "nested table arrays", value, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
|
||||
type Alpha struct {
|
||||
V int
|
||||
}
|
||||
type Beta struct {
|
||||
V int
|
||||
}
|
||||
type Conf struct {
|
||||
V int
|
||||
A Alpha
|
||||
B []Beta
|
||||
}
|
||||
|
||||
val := Conf{
|
||||
V: 1,
|
||||
A: Alpha{2},
|
||||
B: []Beta{{3}},
|
||||
}
|
||||
expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
|
||||
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeWithOmitEmpty(t *testing.T) {
|
||||
type simple struct {
|
||||
Bool bool `toml:"bool,omitempty"`
|
||||
String string `toml:"string,omitempty"`
|
||||
Array [0]byte `toml:"array,omitempty"`
|
||||
Slice []int `toml:"slice,omitempty"`
|
||||
Map map[string]string `toml:"map,omitempty"`
|
||||
}
|
||||
|
||||
var v simple
|
||||
encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
|
||||
v = simple{
|
||||
Bool: true,
|
||||
String: " ",
|
||||
Slice: []int{2, 3, 4},
|
||||
Map: map[string]string{"foo": "bar"},
|
||||
}
|
||||
expected := `bool = true
|
||||
string = " "
|
||||
slice = [2, 3, 4]
|
||||
|
||||
[map]
|
||||
foo = "bar"
|
||||
`
|
||||
encodeExpected(t, "fields with omitempty are not omitted when non-empty",
|
||||
v, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeWithOmitZero(t *testing.T) {
|
||||
type simple struct {
|
||||
Number int `toml:"number,omitzero"`
|
||||
Real float64 `toml:"real,omitzero"`
|
||||
Unsigned uint `toml:"unsigned,omitzero"`
|
||||
}
|
||||
|
||||
value := simple{0, 0.0, uint(0)}
|
||||
expected := ""
|
||||
|
||||
encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
|
||||
|
||||
value.Number = 10
|
||||
value.Real = 20
|
||||
value.Unsigned = 5
|
||||
expected = `number = 10
|
||||
real = 20.0
|
||||
unsigned = 5
|
||||
`
|
||||
encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeOmitemptyWithEmptyName(t *testing.T) {
|
||||
type simple struct {
|
||||
S []int `toml:",omitempty"`
|
||||
}
|
||||
v := simple{[]int{1, 2, 3}}
|
||||
expected := "S = [1, 2, 3]\n"
|
||||
encodeExpected(t, "simple with omitempty, no name, non-empty field",
|
||||
v, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStruct(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ Inner }
|
||||
type Outer1 struct {
|
||||
Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{Inner{3}}
|
||||
expected := "N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStructPointerField(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ *Inner }
|
||||
type Outer1 struct {
|
||||
*Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{}
|
||||
expected := ""
|
||||
encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v0 = Outer0{&Inner{3}}
|
||||
expected = "N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{}
|
||||
expected = ""
|
||||
encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
|
||||
v1 = Outer1{&Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeIgnoredFields(t *testing.T) {
|
||||
type simple struct {
|
||||
Number int `toml:"-"`
|
||||
}
|
||||
value := simple{}
|
||||
expected := ""
|
||||
encodeExpected(t, "ignored field", value, expected, nil)
|
||||
}
|
||||
|
||||
func encodeExpected(
|
||||
t *testing.T, label string, val interface{}, wantStr string, wantErr error,
|
||||
) {
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
err := enc.Encode(val)
|
||||
if err != wantErr {
|
||||
if wantErr != nil {
|
||||
if wantErr == errAnything && err != nil {
|
||||
return
|
||||
}
|
||||
t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
|
||||
} else {
|
||||
t.Errorf("%s: Encode failed: %s", label, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if got := buf.String(); wantStr != got {
|
||||
t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
|
||||
label, wantStr, got)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleEncoder_Encode() {
|
||||
date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
|
||||
var config = map[string]interface{}{
|
||||
"date": date,
|
||||
"counts": []int{1, 1, 2, 3, 5, 8},
|
||||
"hash": map[string]string{
|
||||
"key1": "val1",
|
||||
"key2": "val2",
|
||||
},
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := NewEncoder(buf).Encode(config); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(buf.String())
|
||||
|
||||
// Output:
|
||||
// counts = [1, 1, 2, 3, 5, 8]
|
||||
// date = 2010-03-14T18:00:00Z
|
||||
//
|
||||
// [hash]
|
||||
// key1 = "val1"
|
||||
// key2 = "val2"
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// +build go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// In order to support Go 1.1, we define our own TextMarshaler and
|
||||
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
||||
// standard library interfaces.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// +build !go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// These interfaces were introduced in Go 1.2, so we add them manually when
|
||||
// compiling for Go 1.1.
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler interface {
|
||||
MarshalText() (text []byte, err error)
|
||||
}
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler interface {
|
||||
UnmarshalText(text []byte) error
|
||||
}
|
||||
|
|
@ -0,0 +1,858 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota
|
||||
itemNIL // used in the parser to indicate no type
|
||||
itemEOF
|
||||
itemText
|
||||
itemString
|
||||
itemRawString
|
||||
itemMultilineString
|
||||
itemRawMultilineString
|
||||
itemBool
|
||||
itemInteger
|
||||
itemFloat
|
||||
itemDatetime
|
||||
itemArray // the start of an array
|
||||
itemArrayEnd
|
||||
itemTableStart
|
||||
itemTableEnd
|
||||
itemArrayTableStart
|
||||
itemArrayTableEnd
|
||||
itemKeyStart
|
||||
itemCommentStart
|
||||
)
|
||||
|
||||
const (
|
||||
eof = 0
|
||||
tableStart = '['
|
||||
tableEnd = ']'
|
||||
arrayTableStart = '['
|
||||
arrayTableEnd = ']'
|
||||
tableSep = '.'
|
||||
keySep = '='
|
||||
arrayStart = '['
|
||||
arrayEnd = ']'
|
||||
arrayValTerm = ','
|
||||
commentStart = '#'
|
||||
stringStart = '"'
|
||||
stringEnd = '"'
|
||||
rawStringStart = '\''
|
||||
rawStringEnd = '\''
|
||||
)
|
||||
|
||||
type stateFn func(lx *lexer) stateFn
|
||||
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
width int
|
||||
line int
|
||||
state stateFn
|
||||
items chan item
|
||||
|
||||
// A stack of state functions used to maintain context.
|
||||
// The idea is to reuse parts of the state machine in various places.
|
||||
// For example, values can appear at the top level or within arbitrarily
|
||||
// nested arrays. The last state on the stack is used after a value has
|
||||
// been lexed. Similarly for comments.
|
||||
stack []stateFn
|
||||
}
|
||||
|
||||
type item struct {
|
||||
typ itemType
|
||||
val string
|
||||
line int
|
||||
}
|
||||
|
||||
func (lx *lexer) nextItem() item {
|
||||
for {
|
||||
select {
|
||||
case item := <-lx.items:
|
||||
return item
|
||||
default:
|
||||
lx.state = lx.state(lx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lex(input string) *lexer {
|
||||
lx := &lexer{
|
||||
input: input + "\n",
|
||||
state: lexTop,
|
||||
line: 1,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
}
|
||||
return lx
|
||||
}
|
||||
|
||||
func (lx *lexer) push(state stateFn) {
|
||||
lx.stack = append(lx.stack, state)
|
||||
}
|
||||
|
||||
func (lx *lexer) pop() stateFn {
|
||||
if len(lx.stack) == 0 {
|
||||
return lx.errorf("BUG in lexer: no states to pop.")
|
||||
}
|
||||
last := lx.stack[len(lx.stack)-1]
|
||||
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
func (lx *lexer) current() string {
|
||||
return lx.input[lx.start:lx.pos]
|
||||
}
|
||||
|
||||
func (lx *lexer) emit(typ itemType) {
|
||||
lx.items <- item{typ, lx.current(), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) emitTrim(typ itemType) {
|
||||
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) next() (r rune) {
|
||||
if lx.pos >= len(lx.input) {
|
||||
lx.width = 0
|
||||
return eof
|
||||
}
|
||||
|
||||
if lx.input[lx.pos] == '\n' {
|
||||
lx.line++
|
||||
}
|
||||
r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:])
|
||||
lx.pos += lx.width
|
||||
return r
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (lx *lexer) ignore() {
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can be called only once per call of next.
|
||||
func (lx *lexer) backup() {
|
||||
lx.pos -= lx.width
|
||||
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
||||
lx.line--
|
||||
}
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's equal to `valid`.
|
||||
func (lx *lexer) accept(valid rune) bool {
|
||||
if lx.next() == valid {
|
||||
return true
|
||||
}
|
||||
lx.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (lx *lexer) peek() rune {
|
||||
r := lx.next()
|
||||
lx.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// skip ignores all input that matches the given predicate.
|
||||
func (lx *lexer) skip(pred func(rune) bool) {
|
||||
for {
|
||||
r := lx.next()
|
||||
if pred(r) {
|
||||
continue
|
||||
}
|
||||
lx.backup()
|
||||
lx.ignore()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||
// Note that any value that is a character is escaped if it's a special
|
||||
// character (new lines, tabs, etc.).
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
lx.items <- item{
|
||||
itemError,
|
||||
fmt.Sprintf(format, values...),
|
||||
lx.line,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexTop consumes elements at the top level of TOML data.
|
||||
func lexTop(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isWhitespace(r) || isNL(r) {
|
||||
return lexSkip(lx, lexTop)
|
||||
}
|
||||
|
||||
switch r {
|
||||
case commentStart:
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case tableStart:
|
||||
return lexTableStart
|
||||
case eof:
|
||||
if lx.pos > lx.start {
|
||||
return lx.errorf("Unexpected EOF.")
|
||||
}
|
||||
lx.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// At this point, the only valid item can be a key, so we back up
|
||||
// and let the key lexer do the rest.
|
||||
lx.backup()
|
||||
lx.push(lexTopEnd)
|
||||
return lexKeyStart
|
||||
}
|
||||
|
||||
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
||||
// or a table.) It must see only whitespace, and will turn back to lexTop
|
||||
// upon a new line. If it sees EOF, it will quit the lexer successfully.
|
||||
func lexTopEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == commentStart:
|
||||
// a comment will read to a new line for us.
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case isWhitespace(r):
|
||||
return lexTopEnd
|
||||
case isNL(r):
|
||||
lx.ignore()
|
||||
return lexTop
|
||||
case r == eof:
|
||||
lx.ignore()
|
||||
return lexTop
|
||||
}
|
||||
return lx.errorf("Expected a top-level item to end with a new line, "+
|
||||
"comment or EOF, but got %q instead.", r)
|
||||
}
|
||||
|
||||
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
||||
// it starts with a character other than '.' and ']'.
|
||||
// It assumes that '[' has already been consumed.
|
||||
// It also handles the case that this is an item in an array of tables.
|
||||
// e.g., '[[name]]'.
|
||||
func lexTableStart(lx *lexer) stateFn {
|
||||
if lx.peek() == arrayTableStart {
|
||||
lx.next()
|
||||
lx.emit(itemArrayTableStart)
|
||||
lx.push(lexArrayTableEnd)
|
||||
} else {
|
||||
lx.emit(itemTableStart)
|
||||
lx.push(lexTableEnd)
|
||||
}
|
||||
return lexTableNameStart
|
||||
}
|
||||
|
||||
func lexTableEnd(lx *lexer) stateFn {
|
||||
lx.emit(itemTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexArrayTableEnd(lx *lexer) stateFn {
|
||||
if r := lx.next(); r != arrayTableEnd {
|
||||
return lx.errorf("Expected end of table array name delimiter %q, "+
|
||||
"but got %q instead.", arrayTableEnd, r)
|
||||
}
|
||||
lx.emit(itemArrayTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexTableNameStart(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.peek(); {
|
||||
case r == tableEnd || r == eof:
|
||||
return lx.errorf("Unexpected end of table name. (Table names cannot " +
|
||||
"be empty.)")
|
||||
case r == tableSep:
|
||||
return lx.errorf("Unexpected table separator. (Table names cannot " +
|
||||
"be empty.)")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.push(lexTableNameEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
return lexBareTableName
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareTableName lexes the name of a table. It assumes that at least one
|
||||
// valid character for the table has already been read.
|
||||
func lexBareTableName(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isBareKeyChar(r) {
|
||||
return lexBareTableName
|
||||
}
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexTableNameEnd
|
||||
}
|
||||
|
||||
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
||||
// consuming whitespace.
|
||||
func lexTableNameEnd(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexTableNameEnd
|
||||
case r == tableSep:
|
||||
lx.ignore()
|
||||
return lexTableNameStart
|
||||
case r == tableEnd:
|
||||
return lx.pop()
|
||||
default:
|
||||
return lx.errorf("Expected '.' or ']' to end table name, but got %q "+
|
||||
"instead.", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
||||
// lexKeyStart will ignore whitespace.
|
||||
func lexKeyStart(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
switch {
|
||||
case r == keySep:
|
||||
return lx.errorf("Unexpected key separator %q.", keySep)
|
||||
case isWhitespace(r) || isNL(r):
|
||||
lx.next()
|
||||
return lexSkip(lx, lexKeyStart)
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
lx.push(lexKeyEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
return lexBareKey
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
||||
// (which is not whitespace) has not yet been consumed.
|
||||
func lexBareKey(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case isBareKeyChar(r):
|
||||
return lexBareKey
|
||||
case isWhitespace(r):
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
case r == keySep:
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
default:
|
||||
return lx.errorf("Bare keys cannot contain %q.", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
||||
// separator).
|
||||
func lexKeyEnd(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case r == keySep:
|
||||
return lexSkip(lx, lexValue)
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexKeyEnd)
|
||||
default:
|
||||
return lx.errorf("Expected key separator %q, but got %q instead.",
|
||||
keySep, r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexValue starts the consumption of a value anywhere a value is expected.
|
||||
// lexValue will ignore whitespace.
|
||||
// After a value is lexed, the last state on the next is popped and returned.
|
||||
func lexValue(lx *lexer) stateFn {
|
||||
// We allow whitespace to precede a value, but NOT new lines.
|
||||
// In array syntax, the array states are responsible for ignoring new
|
||||
// lines.
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case isDigit(r):
|
||||
lx.backup() // avoid an extra state and use the same as above
|
||||
return lexNumberOrDateStart
|
||||
}
|
||||
switch r {
|
||||
case arrayStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemArray)
|
||||
return lexArrayValue
|
||||
case stringStart:
|
||||
if lx.accept(stringStart) {
|
||||
if lx.accept(stringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case rawStringStart:
|
||||
if lx.accept(rawStringStart) {
|
||||
if lx.accept(rawStringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineRawString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the "'"
|
||||
return lexRawString
|
||||
case '+', '-':
|
||||
return lexNumberStart
|
||||
case '.': // special error case, be kind to users
|
||||
return lx.errorf("Floats must start with a digit, not '.'.")
|
||||
}
|
||||
if unicode.IsLetter(r) {
|
||||
// Be permissive here; lexBool will give a nice error if the
|
||||
// user wrote something like
|
||||
// x = foo
|
||||
// (i.e. not 'true' or 'false' but is something else word-like.)
|
||||
lx.backup()
|
||||
return lexBool
|
||||
}
|
||||
return lx.errorf("Expected value but found %q instead.", r)
|
||||
}
|
||||
|
||||
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
||||
// have already been consumed. All whitespace and new lines are ignored.
|
||||
func lexArrayValue(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValue)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValue)
|
||||
return lexCommentStart
|
||||
case r == arrayValTerm:
|
||||
return lx.errorf("Unexpected array value terminator %q.",
|
||||
arrayValTerm)
|
||||
case r == arrayEnd:
|
||||
return lexArrayEnd
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexValue
|
||||
}
|
||||
|
||||
// lexArrayValueEnd consumes the cruft between values of an array. Namely,
|
||||
// it ignores whitespace and expects either a ',' or a ']'.
|
||||
func lexArrayValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValueEnd)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexCommentStart
|
||||
case r == arrayValTerm:
|
||||
lx.ignore()
|
||||
return lexArrayValue // move on to the next value
|
||||
case r == arrayEnd:
|
||||
return lexArrayEnd
|
||||
}
|
||||
return lx.errorf("Expected an array value terminator %q or an array "+
|
||||
"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r)
|
||||
}
|
||||
|
||||
// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has
|
||||
// just been consumed.
|
||||
func lexArrayEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemArrayEnd)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexString consumes the inner contents of a string. It assumes that the
|
||||
// beginning '"' has already been consumed and ignored.
|
||||
func lexString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isNL(r):
|
||||
return lx.errorf("Strings cannot contain new lines.")
|
||||
case r == '\\':
|
||||
lx.push(lexString)
|
||||
return lexStringEscape
|
||||
case r == stringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexString
|
||||
}
|
||||
|
||||
// lexMultilineString consumes the inner contents of a string. It assumes that
|
||||
// the beginning '"""' has already been consumed and ignored.
|
||||
func lexMultilineString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == '\\':
|
||||
return lexMultilineStringEscape
|
||||
case r == stringEnd:
|
||||
if lx.accept(stringEnd) {
|
||||
if lx.accept(stringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineString
|
||||
}
|
||||
|
||||
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
||||
// It assumes that the beginning "'" has already been consumed and ignored.
|
||||
func lexRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isNL(r):
|
||||
return lx.errorf("Strings cannot contain new lines.")
|
||||
case r == rawStringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemRawString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexRawString
|
||||
}
|
||||
|
||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||
// a string. It assumes that the beginning "'" has already been consumed and
|
||||
// ignored.
|
||||
func lexMultilineRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == rawStringEnd:
|
||||
if lx.accept(rawStringEnd) {
|
||||
if lx.accept(rawStringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemRawMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineRawString
|
||||
}
|
||||
|
||||
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
||||
// preceding '\\' has already been consumed.
|
||||
func lexMultilineStringEscape(lx *lexer) stateFn {
|
||||
// Handle the special case first:
|
||||
if isNL(lx.next()) {
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
lx.push(lexMultilineString)
|
||||
return lexStringEscape(lx)
|
||||
}
|
||||
|
||||
func lexStringEscape(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
case 'b':
|
||||
fallthrough
|
||||
case 't':
|
||||
fallthrough
|
||||
case 'n':
|
||||
fallthrough
|
||||
case 'f':
|
||||
fallthrough
|
||||
case 'r':
|
||||
fallthrough
|
||||
case '"':
|
||||
fallthrough
|
||||
case '\\':
|
||||
return lx.pop()
|
||||
case 'u':
|
||||
return lexShortUnicodeEscape
|
||||
case 'U':
|
||||
return lexLongUnicodeEscape
|
||||
}
|
||||
return lx.errorf("Invalid escape character %q. Only the following "+
|
||||
"escape characters are allowed: "+
|
||||
"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+
|
||||
"\\uXXXX and \\UXXXXXXXX.", r)
|
||||
}
|
||||
|
||||
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 4; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf("Expected four hexadecimal digits after '\\u', "+
|
||||
"but got '%s' instead.", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 8; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf("Expected eight hexadecimal digits after '\\U', "+
|
||||
"but got '%s' instead.", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
||||
func lexNumberOrDateStart(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case 'e', 'E':
|
||||
return lexFloat
|
||||
case '.':
|
||||
return lx.errorf("Floats must start with a digit, not '.'.")
|
||||
}
|
||||
return lx.errorf("Expected a digit but got %q.", r)
|
||||
}
|
||||
|
||||
// lexNumberOrDate consumes either an integer, float or datetime.
|
||||
func lexNumberOrDate(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '-':
|
||||
return lexDatetime
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexDatetime consumes a Datetime, to a first approximation.
|
||||
// The parser validates that it matches one of the accepted formats.
|
||||
func lexDatetime(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexDatetime
|
||||
}
|
||||
switch r {
|
||||
case '-', 'T', ':', '.', 'Z':
|
||||
return lexDatetime
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemDatetime)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
||||
// has already been read, but that *no* digits have been consumed.
|
||||
// lexNumberStart will move to the appropriate integer or float states.
|
||||
func lexNumberStart(lx *lexer) stateFn {
|
||||
// We MUST see a digit. Even floats have to start with a digit.
|
||||
r := lx.next()
|
||||
if !isDigit(r) {
|
||||
if r == '.' {
|
||||
return lx.errorf("Floats must start with a digit, not '.'.")
|
||||
}
|
||||
return lx.errorf("Expected a digit but got %q.", r)
|
||||
}
|
||||
return lexNumber
|
||||
}
|
||||
|
||||
// lexNumber consumes an integer or a float after seeing the first digit.
|
||||
func lexNumber(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumber
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexFloat consumes the elements of a float. It allows any sequence of
|
||||
// float-like characters, so floats emitted by the lexer are only a first
|
||||
// approximation and must be validated by the parser.
|
||||
func lexFloat(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexFloat
|
||||
}
|
||||
switch r {
|
||||
case '_', '.', '-', '+', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemFloat)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexBool consumes a bool string: 'true' or 'false.
|
||||
func lexBool(lx *lexer) stateFn {
|
||||
var rs []rune
|
||||
for {
|
||||
r := lx.next()
|
||||
if r == eof || isWhitespace(r) || isNL(r) {
|
||||
lx.backup()
|
||||
break
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
s := string(rs)
|
||||
switch s {
|
||||
case "true", "false":
|
||||
lx.emit(itemBool)
|
||||
return lx.pop()
|
||||
}
|
||||
return lx.errorf("Expected value but found %q instead.", s)
|
||||
}
|
||||
|
||||
// lexCommentStart begins the lexing of a comment. It will emit
|
||||
// itemCommentStart and consume no characters, passing control to lexComment.
|
||||
func lexCommentStart(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemCommentStart)
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
||||
// It will consume *up to* the first new line character, and pass control
|
||||
// back to the last state on the stack.
|
||||
func lexComment(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
if isNL(r) || r == eof {
|
||||
lx.emit(itemText)
|
||||
return lx.pop()
|
||||
}
|
||||
lx.next()
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexSkip ignores all slurped input and moves on to the next state.
|
||||
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||
return func(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
return nextState
|
||||
}
|
||||
}
|
||||
|
||||
// isWhitespace returns true if `r` is a whitespace character according
|
||||
// to the spec.
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == '\t' || r == ' '
|
||||
}
|
||||
|
||||
func isNL(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func isHexadecimal(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
|
||||
func isBareKeyChar(r rune) bool {
|
||||
return (r >= 'A' && r <= 'Z') ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '_' ||
|
||||
r == '-'
|
||||
}
|
||||
|
||||
func (itype itemType) String() string {
|
||||
switch itype {
|
||||
case itemError:
|
||||
return "Error"
|
||||
case itemNIL:
|
||||
return "NIL"
|
||||
case itemEOF:
|
||||
return "EOF"
|
||||
case itemText:
|
||||
return "Text"
|
||||
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
||||
return "String"
|
||||
case itemBool:
|
||||
return "Bool"
|
||||
case itemInteger:
|
||||
return "Integer"
|
||||
case itemFloat:
|
||||
return "Float"
|
||||
case itemDatetime:
|
||||
return "DateTime"
|
||||
case itemTableStart:
|
||||
return "TableStart"
|
||||
case itemTableEnd:
|
||||
return "TableEnd"
|
||||
case itemKeyStart:
|
||||
return "KeyStart"
|
||||
case itemArray:
|
||||
return "Array"
|
||||
case itemArrayEnd:
|
||||
return "ArrayEnd"
|
||||
case itemCommentStart:
|
||||
return "CommentStart"
|
||||
}
|
||||
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
||||
}
|
||||
|
||||
func (item item) String() string {
|
||||
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
||||
}
|
||||
|
|
@ -0,0 +1,557 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
lx *lexer
|
||||
|
||||
// A list of keys in the order that they appear in the TOML data.
|
||||
ordered []Key
|
||||
|
||||
// the full key for the current hash in scope
|
||||
context Key
|
||||
|
||||
// the base key name for everything except hashes
|
||||
currentKey string
|
||||
|
||||
// rough approximation of line number
|
||||
approxLine int
|
||||
|
||||
// A map of 'key.group.names' to whether they were created implicitly.
|
||||
implicits map[string]bool
|
||||
}
|
||||
|
||||
type parseError string
|
||||
|
||||
func (pe parseError) Error() string {
|
||||
return string(pe)
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
if err, ok = r.(parseError); ok {
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
p = &parser{
|
||||
mapping: make(map[string]interface{}),
|
||||
types: make(map[string]tomlType),
|
||||
lx: lex(data),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]bool),
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
if item.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.topLevel(item)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
||||
panic(parseError(msg))
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
if it.typ == itemError {
|
||||
p.panicf("%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(typ itemType) item {
|
||||
it := p.next()
|
||||
p.assertEqual(typ, it.typ)
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) assertEqual(expected, got itemType) {
|
||||
if expected != got {
|
||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart:
|
||||
p.approxLine = item.line
|
||||
p.expect(itemText)
|
||||
case itemTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, false)
|
||||
p.setType("", tomlHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, true)
|
||||
p.setType("", tomlArrayHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart:
|
||||
kname := p.next()
|
||||
p.approxLine = kname.line
|
||||
p.currentKey = p.keyString(kname)
|
||||
|
||||
val, typ := p.value(p.next())
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a string for a key (or part of a key in a table name).
|
||||
func (p *parser) keyString(it item) string {
|
||||
switch it.typ {
|
||||
case itemText:
|
||||
return it.val
|
||||
case itemString, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
||||
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
}
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
case itemInteger:
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
||||
it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
||||
"signed integers.", it.val)
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemFloat:
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicf("Invalid float %q: underscores must be "+
|
||||
"surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicf("Invalid float %q: '.' must be followed "+
|
||||
"by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Float '%s' is out of the range of 64-bit "+
|
||||
"IEEE-754 floating-point numbers.", it.val)
|
||||
} else {
|
||||
p.panicf("Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemDatetime:
|
||||
var t time.Time
|
||||
var ok bool
|
||||
var err error
|
||||
for _, format := range []string{
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02",
|
||||
} {
|
||||
t, err = time.ParseInLocation(format, it.val, time.Local)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
case itemArray:
|
||||
array := make([]interface{}, 0)
|
||||
types := make([]tomlType, 0)
|
||||
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
}
|
||||
return array, p.typeOfArray(types)
|
||||
}
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
accept = false
|
||||
continue
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||
func numPeriodsOK(s string) bool {
|
||||
period := false
|
||||
for _, r := range s {
|
||||
if period && !isDigit(r) {
|
||||
return false
|
||||
}
|
||||
period = r == '.'
|
||||
}
|
||||
return !period
|
||||
}
|
||||
|
||||
// establishContext sets the current context of the parser,
|
||||
// where the context is either a hash or an array of hashes. Which one is
|
||||
// set depends on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) establishContext(key Key, array bool) {
|
||||
var ok bool
|
||||
|
||||
// Always start at the top level and drill down for our context.
|
||||
hashContext := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
|
||||
// We only need implicit hashes for key[0:-1]
|
||||
for _, k := range key[0 : len(key)-1] {
|
||||
_, ok = hashContext[k]
|
||||
keyContext = append(keyContext, k)
|
||||
|
||||
// No key? Make an implicit hash and move on.
|
||||
if !ok {
|
||||
p.addImplicit(keyContext)
|
||||
hashContext[k] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// If the hash context is actually an array of tables, then set
|
||||
// the hash context to the last element in that array.
|
||||
//
|
||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||
// virtue of it not being the last element in a key).
|
||||
switch t := hashContext[k].(type) {
|
||||
case []map[string]interface{}:
|
||||
hashContext = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hashContext = t
|
||||
default:
|
||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||
}
|
||||
}
|
||||
|
||||
p.context = keyContext
|
||||
if array {
|
||||
// If this is the first element for this array, then allocate a new
|
||||
// list of tables for it.
|
||||
k := key[len(key)-1]
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
// for something else.
|
||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as "+
|
||||
"an array.", keyContext)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
}
|
||||
p.context = append(p.context, key[len(key)-1])
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value interface{}) {
|
||||
var tmpHash interface{}
|
||||
var ok bool
|
||||
|
||||
hash := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||
}
|
||||
switch t := tmpHash.(type) {
|
||||
case []map[string]interface{}:
|
||||
// The context is a table of hashes. Pick the most recent table
|
||||
// defined as the current hash.
|
||||
hash = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hash = t
|
||||
default:
|
||||
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
||||
"it has '%T' instead.", tmpHash)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Typically, if the given key has already been set, then we have
|
||||
// to raise an error since duplicate keys are disallowed. However,
|
||||
// it's possible that a key was previously defined implicitly. In this
|
||||
// case, it is allowed to be redefined concretely. (See the
|
||||
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we have a concrete key trying to override a previous
|
||||
// key, which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key.
|
||||
// It should be called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
}
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
p.types[keyContext.String()] = typ
|
||||
}
|
||||
|
||||
// addImplicit sets the given Key as having been created implicitly.
|
||||
func (p *parser) addImplicit(key Key) {
|
||||
p.implicits[key.String()] = true
|
||||
}
|
||||
|
||||
// removeImplicit stops tagging the given key as having been implicitly
|
||||
// created.
|
||||
func (p *parser) removeImplicit(key Key) {
|
||||
p.implicits[key.String()] = false
|
||||
}
|
||||
|
||||
// isImplicit returns true if the key group pointed to by the key was created
|
||||
// implicitly.
|
||||
func (p *parser) isImplicit(key Key) bool {
|
||||
return p.implicits[key.String()]
|
||||
}
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
if len(p.currentKey) == 0 {
|
||||
return p.context.String()
|
||||
}
|
||||
if len(p.context) == 0 {
|
||||
return p.currentKey
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) == 0 || s[0] != '\n' {
|
||||
return s
|
||||
}
|
||||
return s[1:]
|
||||
}
|
||||
|
||||
func stripEscapedWhitespace(s string) string {
|
||||
esc := strings.Split(s, "\\\n")
|
||||
if len(esc) > 1 {
|
||||
for i := 1; i < len(esc); i++ {
|
||||
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
return strings.Join(esc, "")
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(str string) string {
|
||||
var replaced []rune
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
if s[r] != '\\' {
|
||||
c, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
replaced = append(replaced, c)
|
||||
continue
|
||||
}
|
||||
r += 1
|
||||
if r >= len(s) {
|
||||
p.bug("Escape sequence at end of string.")
|
||||
return ""
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
return ""
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
r += 1
|
||||
case 't':
|
||||
replaced = append(replaced, rune(0x0009))
|
||||
r += 1
|
||||
case 'n':
|
||||
replaced = append(replaced, rune(0x000A))
|
||||
r += 1
|
||||
case 'f':
|
||||
replaced = append(replaced, rune(0x000C))
|
||||
r += 1
|
||||
case 'r':
|
||||
replaced = append(replaced, rune(0x000D))
|
||||
r += 1
|
||||
case '"':
|
||||
replaced = append(replaced, rune(0x0022))
|
||||
r += 1
|
||||
case '\\':
|
||||
replaced = append(replaced, rune(0x005C))
|
||||
r += 1
|
||||
case 'u':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
}
|
||||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||
"lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
|
||||
func isStringType(ty itemType) bool {
|
||||
return ty == itemString || ty == itemMultilineString ||
|
||||
ty == itemRawString || ty == itemRawMultilineString
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package toml
|
||||
|
||||
// tomlType represents any Go type that corresponds to a TOML type.
|
||||
// While the first draft of the TOML spec has a simplistic type system that
|
||||
// probably doesn't need this level of sophistication, we seem to be militating
|
||||
// toward adding real composite types.
|
||||
type tomlType interface {
|
||||
typeString() string
|
||||
}
|
||||
|
||||
// typeEqual accepts any two types and returns true if they are equal.
|
||||
func typeEqual(t1, t2 tomlType) bool {
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string {
|
||||
return string(btype)
|
||||
}
|
||||
|
||||
func (btype tomlBaseType) String() string {
|
||||
return btype.typeString()
|
||||
}
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
tomlFloat tomlBaseType = "Float"
|
||||
tomlDatetime tomlBaseType = "Datetime"
|
||||
tomlString tomlBaseType = "String"
|
||||
tomlBool tomlBaseType = "Bool"
|
||||
tomlArray tomlBaseType = "Array"
|
||||
tomlHash tomlBaseType = "Hash"
|
||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||
)
|
||||
|
||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||
//
|
||||
// Passing a lexer item other than the following will cause a BUG message
|
||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||
switch lexItem.typ {
|
||||
case itemInteger:
|
||||
return tomlInteger
|
||||
case itemFloat:
|
||||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
case itemRawString:
|
||||
return tomlString
|
||||
case itemRawMultilineString:
|
||||
return tomlString
|
||||
case itemBool:
|
||||
return tomlBool
|
||||
}
|
||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||
// values.
|
||||
//
|
||||
// In the current spec, if an array is homogeneous, then its type is always
|
||||
// "Array". If the array is not homogeneous, an error is generated.
|
||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||
// Empty arrays are cool.
|
||||
if len(types) == 0 {
|
||||
return tomlArray
|
||||
}
|
||||
|
||||
theType := types[0]
|
||||
for _, t := range types[1:] {
|
||||
if !typeEqual(theType, t) {
|
||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||
"arrays must be homogeneous.", theType, t)
|
||||
}
|
||||
}
|
||||
return tomlArray
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
package toml
|
||||
|
||||
// Struct field handling is adapted from code in encoding/json:
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the Go distribution.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A field represents a single field found in a struct.
|
||||
type field struct {
|
||||
name string // the name of the field (`toml` tag included)
|
||||
tag bool // whether field has a `toml` tag
|
||||
index []int // represents the depth of an anonymous field
|
||||
typ reflect.Type // the type of the field
|
||||
}
|
||||
|
||||
// byName sorts field by name, breaking ties with depth,
|
||||
// then breaking ties with "name came from toml tag", then
|
||||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
}
|
||||
if len(x[i].index) != len(x[j].index) {
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
if x[i].tag != x[j].tag {
|
||||
return x[i].tag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
return false
|
||||
}
|
||||
if xik != x[j].index[k] {
|
||||
return xik < x[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that TOML should recognize for the given
|
||||
// type. The algorithm is breadth-first search over the set of structs to
|
||||
// include - the top struct and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []field
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
opts := getOptions(sf.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := opts.name != ""
|
||||
name := opts.name
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
fields = append(fields, field{name, tagged, index, ft})
|
||||
if count[f.typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
f := field{name: ft.Name(), index: index, typ: ft}
|
||||
next = append(next, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byName(fields))
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with TOML tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
sort.Sort(byIndex(fields))
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// TOML tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []field) (field, bool) {
|
||||
// The fields are sorted in increasing index-length order. The winner
|
||||
// must therefore be one with the shortest index length. Drop all
|
||||
// longer entries, which is easy: just truncate the slice.
|
||||
length := len(fields[0].index)
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if len(f.index) > length {
|
||||
fields = fields[:i]
|
||||
break
|
||||
}
|
||||
if f.tag {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return field{}, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return field{}, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache struct {
|
||||
sync.RWMutex
|
||||
m map[reflect.Type][]field
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
fieldCache.RLock()
|
||||
f := fieldCache.m[t]
|
||||
fieldCache.RUnlock()
|
||||
if f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
// Compute fields without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
f = typeFields(t)
|
||||
if f == nil {
|
||||
f = []field{}
|
||||
}
|
||||
|
||||
fieldCache.Lock()
|
||||
if fieldCache.m == nil {
|
||||
fieldCache.m = map[reflect.Type][]field{}
|
||||
}
|
||||
fieldCache.m[t] = f
|
||||
fieldCache.Unlock()
|
||||
return f
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Filesystem Package
|
||||
|
||||
http://godoc.org/github.com/kr/fs
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package fs_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
func ExampleWalker() {
|
||||
walker := fs.Walk("/usr/lib")
|
||||
for walker.Step() {
|
||||
if err := walker.Err(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
fmt.Println(walker.Path())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileSystem defines the methods of an abstract filesystem.
|
||||
type FileSystem interface {
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns a
|
||||
// list of directory entries.
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
|
||||
// Lstat returns a FileInfo describing the named file. If the file is a
|
||||
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
|
||||
// makes no attempt to follow the link.
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
|
||||
// Join joins any number of path elements into a single path, adding a
|
||||
// separator if necessary. The result is Cleaned; in particular, all
|
||||
// empty strings are ignored.
|
||||
//
|
||||
// The separator is FileSystem specific.
|
||||
Join(elem ...string) string
|
||||
}
|
||||
|
||||
// fs represents a FileSystem provided by the os package.
|
||||
type fs struct{}
|
||||
|
||||
func (f *fs) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) }
|
||||
|
||||
func (f *fs) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
|
||||
|
||||
func (f *fs) Join(elem ...string) string { return filepath.Join(elem...) }
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// Package fs provides filesystem-related functions.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Walker provides a convenient interface for iterating over the
|
||||
// descendants of a filesystem path.
|
||||
// Successive calls to the Step method will step through each
|
||||
// file or directory in the tree, including the root. The files
|
||||
// are walked in lexical order, which makes the output deterministic
|
||||
// but means that for very large directories Walker can be inefficient.
|
||||
// Walker does not follow symbolic links.
|
||||
type Walker struct {
|
||||
fs FileSystem
|
||||
cur item
|
||||
stack []item
|
||||
descend bool
|
||||
}
|
||||
|
||||
type item struct {
|
||||
path string
|
||||
info os.FileInfo
|
||||
err error
|
||||
}
|
||||
|
||||
// Walk returns a new Walker rooted at root.
|
||||
func Walk(root string) *Walker {
|
||||
return WalkFS(root, new(fs))
|
||||
}
|
||||
|
||||
// WalkFS returns a new Walker rooted at root on the FileSystem fs.
|
||||
func WalkFS(root string, fs FileSystem) *Walker {
|
||||
info, err := fs.Lstat(root)
|
||||
return &Walker{
|
||||
fs: fs,
|
||||
stack: []item{{root, info, err}},
|
||||
}
|
||||
}
|
||||
|
||||
// Step advances the Walker to the next file or directory,
|
||||
// which will then be available through the Path, Stat,
|
||||
// and Err methods.
|
||||
// It returns false when the walk stops at the end of the tree.
|
||||
func (w *Walker) Step() bool {
|
||||
if w.descend && w.cur.err == nil && w.cur.info.IsDir() {
|
||||
list, err := w.fs.ReadDir(w.cur.path)
|
||||
if err != nil {
|
||||
w.cur.err = err
|
||||
w.stack = append(w.stack, w.cur)
|
||||
} else {
|
||||
for i := len(list) - 1; i >= 0; i-- {
|
||||
path := w.fs.Join(w.cur.path, list[i].Name())
|
||||
w.stack = append(w.stack, item{path, list[i], nil})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.stack) == 0 {
|
||||
return false
|
||||
}
|
||||
i := len(w.stack) - 1
|
||||
w.cur = w.stack[i]
|
||||
w.stack = w.stack[:i]
|
||||
w.descend = true
|
||||
return true
|
||||
}
|
||||
|
||||
// Path returns the path to the most recent file or directory
|
||||
// visited by a call to Step. It contains the argument to Walk
|
||||
// as a prefix; that is, if Walk is called with "dir", which is
|
||||
// a directory containing the file "a", Path will return "dir/a".
|
||||
func (w *Walker) Path() string {
|
||||
return w.cur.path
|
||||
}
|
||||
|
||||
// Stat returns info for the most recent file or directory
|
||||
// visited by a call to Step.
|
||||
func (w *Walker) Stat() os.FileInfo {
|
||||
return w.cur.info
|
||||
}
|
||||
|
||||
// Err returns the error, if any, for the most recent attempt
|
||||
// by Step to visit a file or directory. If a directory has
|
||||
// an error, w will not descend into that directory.
|
||||
func (w *Walker) Err() error {
|
||||
return w.cur.err
|
||||
}
|
||||
|
||||
// SkipDir causes the currently visited directory to be skipped.
|
||||
// If w is not on a directory, SkipDir has no effect.
|
||||
func (w *Walker) SkipDir() {
|
||||
w.descend = false
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
type PathTest struct {
|
||||
path, result string
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
name string
|
||||
entries []*Node // nil if the entry is a file
|
||||
mark int
|
||||
}
|
||||
|
||||
var tree = &Node{
|
||||
"testdata",
|
||||
[]*Node{
|
||||
{"a", nil, 0},
|
||||
{"b", []*Node{}, 0},
|
||||
{"c", nil, 0},
|
||||
{
|
||||
"d",
|
||||
[]*Node{
|
||||
{"x", nil, 0},
|
||||
{"y", []*Node{}, 0},
|
||||
{
|
||||
"z",
|
||||
[]*Node{
|
||||
{"u", nil, 0},
|
||||
{"v", nil, 0},
|
||||
},
|
||||
0,
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
},
|
||||
0,
|
||||
}
|
||||
|
||||
func walkTree(n *Node, path string, f func(path string, n *Node)) {
|
||||
f(path, n)
|
||||
for _, e := range n.entries {
|
||||
walkTree(e, filepath.Join(path, e.name), f)
|
||||
}
|
||||
}
|
||||
|
||||
func makeTree(t *testing.T) {
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.entries == nil {
|
||||
fd, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Errorf("makeTree: %v", err)
|
||||
return
|
||||
}
|
||||
fd.Close()
|
||||
} else {
|
||||
os.Mkdir(path, 0770)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
||||
|
||||
func checkMarks(t *testing.T, report bool) {
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.mark != 1 && report {
|
||||
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
||||
}
|
||||
n.mark = 0
|
||||
})
|
||||
}
|
||||
|
||||
// Assumes that each node name is unique. Good enough for a test.
|
||||
// If clear is true, any incoming error is cleared before return. The errors
|
||||
// are always accumulated, though.
|
||||
func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error {
|
||||
if err != nil {
|
||||
*errors = append(*errors, err)
|
||||
if clear {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
name := info.Name()
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.name == name {
|
||||
n.mark++
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
makeTree(t)
|
||||
errors := make([]error, 0, 10)
|
||||
clear := true
|
||||
markFn := func(walker *fs.Walker) (err error) {
|
||||
for walker.Step() {
|
||||
err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Expect no errors.
|
||||
err := markFn(fs.Walk(tree.name))
|
||||
if err != nil {
|
||||
t.Fatalf("no error expected, found: %s", err)
|
||||
}
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("unexpected errors: %s", errors)
|
||||
}
|
||||
checkMarks(t, true)
|
||||
errors = errors[0:0]
|
||||
|
||||
// Test permission errors. Only possible if we're not root
|
||||
// and only on some file systems (AFS, FAT). To avoid errors during
|
||||
// all.bash on those file systems, skip during go test -short.
|
||||
if os.Getuid() > 0 && !testing.Short() {
|
||||
// introduce 2 errors: chmod top-level directories to 0
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
|
||||
|
||||
// 3) capture errors, expect two.
|
||||
// mark respective subtrees manually
|
||||
markTree(tree.entries[1])
|
||||
markTree(tree.entries[3])
|
||||
// correct double-marking of directory itself
|
||||
tree.entries[1].mark--
|
||||
tree.entries[3].mark--
|
||||
err := markFn(fs.Walk(tree.name))
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error return from Walk, got %s", err)
|
||||
}
|
||||
if len(errors) != 2 {
|
||||
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
|
||||
}
|
||||
// the inaccessible subtrees were marked manually
|
||||
checkMarks(t, true)
|
||||
errors = errors[0:0]
|
||||
|
||||
// 4) capture errors, stop after first error.
|
||||
// mark respective subtrees manually
|
||||
markTree(tree.entries[1])
|
||||
markTree(tree.entries[3])
|
||||
// correct double-marking of directory itself
|
||||
tree.entries[1].mark--
|
||||
tree.entries[3].mark--
|
||||
clear = false // error will stop processing
|
||||
err = markFn(fs.Walk(tree.name))
|
||||
if err == nil {
|
||||
t.Fatalf("expected error return from Walk")
|
||||
}
|
||||
if len(errors) != 1 {
|
||||
t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
|
||||
}
|
||||
// the inaccessible subtrees were marked manually
|
||||
checkMarks(t, false)
|
||||
errors = errors[0:0]
|
||||
|
||||
// restore permissions
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
if err := os.RemoveAll(tree.name); err != nil {
|
||||
t.Errorf("removeTree: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBug3486(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=3486
|
||||
root, err := filepath.EvalSymlinks(runtime.GOROOT())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lib := filepath.Join(root, "lib")
|
||||
src := filepath.Join(root, "src")
|
||||
seenSrc := false
|
||||
walker := fs.Walk(root)
|
||||
for walker.Step() {
|
||||
if walker.Err() != nil {
|
||||
t.Fatal(walker.Err())
|
||||
}
|
||||
|
||||
switch walker.Path() {
|
||||
case lib:
|
||||
walker.SkipDir()
|
||||
case src:
|
||||
seenSrc = true
|
||||
}
|
||||
}
|
||||
if !seenSrc {
|
||||
t.Fatalf("%q not seen", src)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.un~
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.3
|
||||
- 1.6
|
||||
- tip
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
## Changelog
|
||||
|
||||
### [1.7.0](https://github.com/magiconair/properties/tags/v1.7.0) - 20 Mar 2016
|
||||
|
||||
* [Issue #10](https://github.com/magiconair/properties/issues/10): Add [LoadURL,LoadURLs,MustLoadURL,MustLoadURLs](http://godoc.org/github.com/magiconair/properties#Properties.LoadURL) method to load properties from a URL.
|
||||
* [Issue #11](https://github.com/magiconair/properties/issues/11): Add [LoadString,MustLoadString](http://godoc.org/github.com/magiconair/properties#Properties.LoadString) method to load properties from an UTF8 string.
|
||||
* [PR #8](https://github.com/magiconair/properties/pull/8): Add [MustFlag](http://godoc.org/github.com/magiconair/properties#Properties.MustFlag) method to provide overrides via command line flags. (@pascaldekloe)
|
||||
|
||||
### [1.6.0](https://github.com/magiconair/properties/tags/v1.6.0) - 11 Dec 2015
|
||||
|
||||
* Add [Decode](http://godoc.org/github.com/magiconair/properties#Properties.Decode) method to populate struct from properties via tags.
|
||||
|
||||
### [1.5.6](https://github.com/magiconair/properties/tags/v1.5.6) - 18 Oct 2015
|
||||
|
||||
* Vendored in gopkg.in/check.v1
|
||||
|
||||
### [1.5.5](https://github.com/magiconair/properties/tags/v1.5.5) - 31 Jul 2015
|
||||
|
||||
* [PR #6](https://github.com/magiconair/properties/pull/6): Add [Delete](http://godoc.org/github.com/magiconair/properties#Properties.Delete) method to remove keys including comments. (@gerbenjacobs)
|
||||
|
||||
### [1.5.4](https://github.com/magiconair/properties/tags/v1.5.4) - 23 Jun 2015
|
||||
|
||||
* [Issue #5](https://github.com/magiconair/properties/issues/5): Allow disabling of property expansion [DisableExpansion](http://godoc.org/github.com/magiconair/properties#Properties.DisableExpansion). When property expansion is disabled Properties become a simple key/value store and don't check for circular references.
|
||||
|
||||
### [1.5.3](https://github.com/magiconair/properties/tags/v1.5.3) - 02 Jun 2015
|
||||
|
||||
* [Issue #4](https://github.com/magiconair/properties/issues/4): Maintain key order in [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) and [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp)
|
||||
|
||||
### [1.5.2](https://github.com/magiconair/properties/tags/v1.5.2) - 10 Apr 2015
|
||||
|
||||
* [Issue #3](https://github.com/magiconair/properties/issues/3): Don't print comments in [WriteComment()](http://godoc.org/github.com/magiconair/properties#Properties.WriteComment) if they are all empty
|
||||
* Add clickable links to README
|
||||
|
||||
### [1.5.1](https://github.com/magiconair/properties/tags/v1.5.1) - 08 Dec 2014
|
||||
|
||||
* Added [GetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.GetParsedDuration) and [MustGetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.MustGetParsedDuration) for values specified compatible with
|
||||
[time.ParseDuration()](http://golang.org/pkg/time/#ParseDuration).
|
||||
|
||||
### [1.5.0](https://github.com/magiconair/properties/tags/v1.5.0) - 18 Nov 2014
|
||||
|
||||
* Added support for single and multi-line comments (reading, writing and updating)
|
||||
* The order of keys is now preserved
|
||||
* Calling [Set()](http://godoc.org/github.com/magiconair/properties#Properties.Set) with an empty key now silently ignores the call and does not create a new entry
|
||||
* Added a [MustSet()](http://godoc.org/github.com/magiconair/properties#Properties.MustSet) method
|
||||
* Migrated test library from launchpad.net/gocheck to [gopkg.in/check.v1](http://gopkg.in/check.v1)
|
||||
|
||||
### [1.4.2](https://github.com/magiconair/properties/tags/v1.4.2) - 15 Nov 2014
|
||||
|
||||
* [Issue #2](https://github.com/magiconair/properties/issues/2): Fixed goroutine leak in parser which created two lexers but cleaned up only one
|
||||
|
||||
### [1.4.1](https://github.com/magiconair/properties/tags/v1.4.1) - 13 Nov 2014
|
||||
|
||||
* [Issue #1](https://github.com/magiconair/properties/issues/1): Fixed bug in Keys() method which returned an empty string
|
||||
|
||||
### [1.4.0](https://github.com/magiconair/properties/tags/v1.4.0) - 23 Sep 2014
|
||||
|
||||
* Added [Keys()](http://godoc.org/github.com/magiconair/properties#Properties.Keys) to get the keys
|
||||
* Added [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp) and [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) to get a subset of the properties
|
||||
|
||||
### [1.3.0](https://github.com/magiconair/properties/tags/v1.3.0) - 18 Mar 2014
|
||||
|
||||
* Added support for time.Duration
|
||||
* Made MustXXX() failure beha[ior configurable (log.Fatal, panic](https://github.com/magiconair/properties/tags/vior configurable (log.Fatal, panic) - custom)
|
||||
* Changed default of MustXXX() failure from panic to log.Fatal
|
||||
|
||||
### [1.2.0](https://github.com/magiconair/properties/tags/v1.2.0) - 05 Mar 2014
|
||||
|
||||
* Added MustGet... functions
|
||||
* Added support for int and uint with range checks on 32 bit platforms
|
||||
|
||||
### [1.1.0](https://github.com/magiconair/properties/tags/v1.1.0) - 20 Jan 2014
|
||||
|
||||
* Renamed from goproperties to properties
|
||||
* Added support for expansion of environment vars in
|
||||
filenames and value expressions
|
||||
* Fixed bug where value expressions were not at the
|
||||
start of the string
|
||||
|
||||
### [1.0.0](https://github.com/magiconair/properties/tags/v1.0.0) - 7 Jan 2014
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
goproperties - properties file decoder for Go
|
||||
|
||||
Copyright (c) 2013-2014 - Frank Schroeder
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
Overview [](https://travis-ci.org/magiconair/properties)
|
||||
========
|
||||
|
||||
#### Current version: 1.7.0
|
||||
|
||||
properties is a Go library for reading and writing properties files.
|
||||
|
||||
It supports reading from multiple files or URLs and Spring style recursive
|
||||
property expansion of expressions like `${key}` to their corresponding value.
|
||||
Value expressions can refer to other keys like in `${key}` or to environment
|
||||
variables like in `${USER}`. Filenames can also contain environment variables
|
||||
like in `/home/${USER}/myapp.properties`.
|
||||
|
||||
Properties can be decoded into structs, maps, arrays and values through
|
||||
struct tags.
|
||||
|
||||
Comments and the order of keys are preserved. Comments can be modified
|
||||
and can be written to the output.
|
||||
|
||||
The properties library supports both ISO-8859-1 and UTF-8 encoded data.
|
||||
|
||||
Starting from version 1.3.0 the behavior of the MustXXX() functions is
|
||||
configurable by providing a custom `ErrorHandler` function. The default has
|
||||
changed from `panic` to `log.Fatal` but this is configurable and custom
|
||||
error handling functions can be provided. See the package documentation for
|
||||
details.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```go
|
||||
import (
|
||||
"flag"
|
||||
"github.com/magiconair/properties"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := properties.MustLoadFile("${HOME}/config.properties", properties.UTF8)
|
||||
|
||||
// via getters
|
||||
host := p.MustGetString("host")
|
||||
port := p.GetInt("port", 8080)
|
||||
|
||||
// or via decode
|
||||
type Config struct {
|
||||
Host string `properties:"host"`
|
||||
Port int `properties:"port,default=9000"`
|
||||
Accept []string `properties:"accept,default=image/png;image;gif"`
|
||||
Timeout time.Duration `properties:"timeout,default=5s"`
|
||||
}
|
||||
var cfg Config
|
||||
if err := p.Decode(&cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// or via flags
|
||||
p.MustFlag(flag.CommandLine)
|
||||
|
||||
// or via url
|
||||
p = properties.MustLoadURL("http://host/path")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Read the full documentation on [GoDoc](https://godoc.org/github.com/magiconair/properties) [](https://godoc.org/github.com/magiconair/properties)
|
||||
|
||||
Installation and Upgrade
|
||||
------------------------
|
||||
|
||||
```
|
||||
$ go get -u github.com/magiconair/properties
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
2 clause BSD license. See [LICENSE](https://github.com/magiconair/properties/blob/master/LICENSE) file for details.
|
||||
|
||||
ToDo
|
||||
----
|
||||
* Dump contents with passwords and secrets obscured
|
||||
25
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/LICENSE
generated
vendored
Normal file
25
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Gocheck - A rich testing framework for Go
|
||||
|
||||
Copyright (c) 2010-2013 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
20
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/README.md
generated
vendored
Normal file
20
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Instructions
|
||||
============
|
||||
|
||||
Install the package with:
|
||||
|
||||
go get gopkg.in/check.v1
|
||||
|
||||
Import it with:
|
||||
|
||||
import "gopkg.in/check.v1"
|
||||
|
||||
and use _check_ as the package name inside the code.
|
||||
|
||||
For more details, visit the project page:
|
||||
|
||||
* http://labix.org/gocheck
|
||||
|
||||
and the API documentation:
|
||||
|
||||
* https://gopkg.in/check.v1
|
||||
2
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/TODO
generated
vendored
Normal file
2
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/TODO
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- Assert(slice, Contains, item)
|
||||
- Parallel test support
|
||||
163
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/benchmark.go
generated
vendored
Normal file
163
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/benchmark.go
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package check
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var memStats runtime.MemStats
|
||||
|
||||
// testingB is a type passed to Benchmark functions to manage benchmark
|
||||
// timing and to specify the number of iterations to run.
|
||||
type timer struct {
|
||||
start time.Time // Time test or benchmark started
|
||||
duration time.Duration
|
||||
N int
|
||||
bytes int64
|
||||
timerOn bool
|
||||
benchTime time.Duration
|
||||
// The initial states of memStats.Mallocs and memStats.TotalAlloc.
|
||||
startAllocs uint64
|
||||
startBytes uint64
|
||||
// The net total of this test after being run.
|
||||
netAllocs uint64
|
||||
netBytes uint64
|
||||
}
|
||||
|
||||
// StartTimer starts timing a test. This function is called automatically
|
||||
// before a benchmark starts, but it can also used to resume timing after
|
||||
// a call to StopTimer.
|
||||
func (c *C) StartTimer() {
|
||||
if !c.timerOn {
|
||||
c.start = time.Now()
|
||||
c.timerOn = true
|
||||
|
||||
runtime.ReadMemStats(&memStats)
|
||||
c.startAllocs = memStats.Mallocs
|
||||
c.startBytes = memStats.TotalAlloc
|
||||
}
|
||||
}
|
||||
|
||||
// StopTimer stops timing a test. This can be used to pause the timer
|
||||
// while performing complex initialization that you don't
|
||||
// want to measure.
|
||||
func (c *C) StopTimer() {
|
||||
if c.timerOn {
|
||||
c.duration += time.Now().Sub(c.start)
|
||||
c.timerOn = false
|
||||
runtime.ReadMemStats(&memStats)
|
||||
c.netAllocs += memStats.Mallocs - c.startAllocs
|
||||
c.netBytes += memStats.TotalAlloc - c.startBytes
|
||||
}
|
||||
}
|
||||
|
||||
// ResetTimer sets the elapsed benchmark time to zero.
|
||||
// It does not affect whether the timer is running.
|
||||
func (c *C) ResetTimer() {
|
||||
if c.timerOn {
|
||||
c.start = time.Now()
|
||||
runtime.ReadMemStats(&memStats)
|
||||
c.startAllocs = memStats.Mallocs
|
||||
c.startBytes = memStats.TotalAlloc
|
||||
}
|
||||
c.duration = 0
|
||||
c.netAllocs = 0
|
||||
c.netBytes = 0
|
||||
}
|
||||
|
||||
// SetBytes informs the number of bytes that the benchmark processes
|
||||
// on each iteration. If this is called in a benchmark it will also
|
||||
// report MB/s.
|
||||
func (c *C) SetBytes(n int64) {
|
||||
c.bytes = n
|
||||
}
|
||||
|
||||
func (c *C) nsPerOp() int64 {
|
||||
if c.N <= 0 {
|
||||
return 0
|
||||
}
|
||||
return c.duration.Nanoseconds() / int64(c.N)
|
||||
}
|
||||
|
||||
func (c *C) mbPerSec() float64 {
|
||||
if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds()
|
||||
}
|
||||
|
||||
func (c *C) timerString() string {
|
||||
if c.N <= 0 {
|
||||
return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9)
|
||||
}
|
||||
mbs := c.mbPerSec()
|
||||
mb := ""
|
||||
if mbs != 0 {
|
||||
mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
|
||||
}
|
||||
nsop := c.nsPerOp()
|
||||
ns := fmt.Sprintf("%10d ns/op", nsop)
|
||||
if c.N > 0 && nsop < 100 {
|
||||
// The format specifiers here make sure that
|
||||
// the ones digits line up for all three possible formats.
|
||||
if nsop < 10 {
|
||||
ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
|
||||
} else {
|
||||
ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
|
||||
}
|
||||
}
|
||||
memStats := ""
|
||||
if c.benchMem {
|
||||
allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N))
|
||||
allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N))
|
||||
memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs)
|
||||
}
|
||||
return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats)
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x > y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func max(x, y int) int {
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// roundDown10 rounds a number down to the nearest power of 10.
|
||||
func roundDown10(n int) int {
|
||||
var tens = 0
|
||||
// tens = floor(log_10(n))
|
||||
for n > 10 {
|
||||
n = n / 10
|
||||
tens++
|
||||
}
|
||||
// result = 10^tens
|
||||
result := 1
|
||||
for i := 0; i < tens; i++ {
|
||||
result *= 10
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
|
||||
func roundUp(n int) int {
|
||||
base := roundDown10(n)
|
||||
if n < (2 * base) {
|
||||
return 2 * base
|
||||
}
|
||||
if n < (5 * base) {
|
||||
return 5 * base
|
||||
}
|
||||
return 10 * base
|
||||
}
|
||||
91
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/benchmark_test.go
generated
vendored
Normal file
91
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/benchmark_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// These tests verify the test running logic.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
var benchmarkS = Suite(&BenchmarkS{})
|
||||
|
||||
type BenchmarkS struct{}
|
||||
|
||||
func (s *BenchmarkS) TestCountSuite(c *C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
func (s *BenchmarkS) TestBasicTestTiming(c *C) {
|
||||
helper := FixtureHelper{sleepOn: "Test1", sleep: 1000000 * time.Nanosecond}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Verbose: true}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t0\\.001s\n" +
|
||||
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t0\\.000s\n"
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *BenchmarkS) TestStreamTestTiming(c *C) {
|
||||
helper := FixtureHelper{sleepOn: "SetUpSuite", sleep: 1000000 * time.Nanosecond}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Stream: true}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "(?s).*\nPASS: check_test\\.go:[0-9]+: FixtureHelper\\.SetUpSuite\t *0\\.001s\n.*"
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *BenchmarkS) TestBenchmark(c *C) {
|
||||
helper := FixtureHelper{sleep: 100000}
|
||||
output := String{}
|
||||
runConf := RunConf{
|
||||
Output: &output,
|
||||
Benchmark: true,
|
||||
BenchmarkTime: 10000000,
|
||||
Filter: "Benchmark1",
|
||||
}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Benchmark1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Benchmark1")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
// ... and more.
|
||||
|
||||
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark1\t *100\t *[12][0-9]{5} ns/op\n"
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *BenchmarkS) TestBenchmarkBytes(c *C) {
|
||||
helper := FixtureHelper{sleep: 100000}
|
||||
output := String{}
|
||||
runConf := RunConf{
|
||||
Output: &output,
|
||||
Benchmark: true,
|
||||
BenchmarkTime: 10000000,
|
||||
Filter: "Benchmark2",
|
||||
}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark2\t *100\t *[12][0-9]{5} ns/op\t *[4-9]\\.[0-9]{2} MB/s\n"
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *BenchmarkS) TestBenchmarkMem(c *C) {
|
||||
helper := FixtureHelper{sleep: 100000}
|
||||
output := String{}
|
||||
runConf := RunConf{
|
||||
Output: &output,
|
||||
Benchmark: true,
|
||||
BenchmarkMem: true,
|
||||
BenchmarkTime: 10000000,
|
||||
Filter: "Benchmark3",
|
||||
}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark3\t *100\t *[12][0-9]{5} ns/op\t *[0-9]+ B/op\t *[1-9] allocs/op\n"
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
82
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/bootstrap_test.go
generated
vendored
Normal file
82
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/bootstrap_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// These initial tests are for bootstrapping. They verify that we can
|
||||
// basically use the testing infrastructure itself to check if the test
|
||||
// system is working.
|
||||
//
|
||||
// These tests use will break down the test runner badly in case of
|
||||
// errors because if they simply fail, we can't be sure the developer
|
||||
// will ever see anything (because failing means the failing system
|
||||
// somehow isn't working! :-)
|
||||
//
|
||||
// Do not assume *any* internal functionality works as expected besides
|
||||
// what's actually tested here.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BootstrapS struct{}
|
||||
|
||||
var boostrapS = check.Suite(&BootstrapS{})
|
||||
|
||||
func (s *BootstrapS) TestCountSuite(c *check.C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestFailedAndFail(c *check.C) {
|
||||
if c.Failed() {
|
||||
critical("c.Failed() must be false first!")
|
||||
}
|
||||
c.Fail()
|
||||
if !c.Failed() {
|
||||
critical("c.Fail() didn't put the test in a failed state!")
|
||||
}
|
||||
c.Succeed()
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestFailedAndSucceed(c *check.C) {
|
||||
c.Fail()
|
||||
c.Succeed()
|
||||
if c.Failed() {
|
||||
critical("c.Succeed() didn't put the test back in a non-failed state")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestLogAndGetTestLog(c *check.C) {
|
||||
c.Log("Hello there!")
|
||||
log := c.GetTestLog()
|
||||
if log != "Hello there!\n" {
|
||||
critical(fmt.Sprintf("Log() or GetTestLog() is not working! Got: %#v", log))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestLogfAndGetTestLog(c *check.C) {
|
||||
c.Logf("Hello %v", "there!")
|
||||
log := c.GetTestLog()
|
||||
if log != "Hello there!\n" {
|
||||
critical(fmt.Sprintf("Logf() or GetTestLog() is not working! Got: %#v", log))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestRunShowsErrors(c *check.C) {
|
||||
output := String{}
|
||||
check.Run(&FailHelper{}, &check.RunConf{Output: &output})
|
||||
if strings.Index(output.value, "Expected failure!") == -1 {
|
||||
critical(fmt.Sprintf("RunWithWriter() output did not contain the "+
|
||||
"expected failure! Got: %#v",
|
||||
output.value))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestRunDoesntShowSuccesses(c *check.C) {
|
||||
output := String{}
|
||||
check.Run(&SuccessHelper{}, &check.RunConf{Output: &output})
|
||||
if strings.Index(output.value, "Expected success!") != -1 {
|
||||
critical(fmt.Sprintf("RunWithWriter() output contained a successful "+
|
||||
"test! Got: %#v",
|
||||
output.value))
|
||||
}
|
||||
}
|
||||
945
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/check.go
generated
vendored
Normal file
945
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/check.go
generated
vendored
Normal file
|
|
@ -0,0 +1,945 @@
|
|||
// Package check is a rich testing extension for Go's testing package.
|
||||
//
|
||||
// For details about the project, see:
|
||||
//
|
||||
// http://labix.org/gocheck
|
||||
//
|
||||
package check
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Internal type which deals with suite method calling.
|
||||
|
||||
const (
|
||||
fixtureKd = iota
|
||||
testKd
|
||||
)
|
||||
|
||||
type funcKind int
|
||||
|
||||
const (
|
||||
succeededSt = iota
|
||||
failedSt
|
||||
skippedSt
|
||||
panickedSt
|
||||
fixturePanickedSt
|
||||
missedSt
|
||||
)
|
||||
|
||||
type funcStatus int
|
||||
|
||||
// A method value can't reach its own Method structure.
|
||||
type methodType struct {
|
||||
reflect.Value
|
||||
Info reflect.Method
|
||||
}
|
||||
|
||||
func newMethod(receiver reflect.Value, i int) *methodType {
|
||||
return &methodType{receiver.Method(i), receiver.Type().Method(i)}
|
||||
}
|
||||
|
||||
func (method *methodType) PC() uintptr {
|
||||
return method.Info.Func.Pointer()
|
||||
}
|
||||
|
||||
func (method *methodType) suiteName() string {
|
||||
t := method.Info.Type.In(0)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t.Name()
|
||||
}
|
||||
|
||||
func (method *methodType) String() string {
|
||||
return method.suiteName() + "." + method.Info.Name
|
||||
}
|
||||
|
||||
func (method *methodType) matches(re *regexp.Regexp) bool {
|
||||
return (re.MatchString(method.Info.Name) ||
|
||||
re.MatchString(method.suiteName()) ||
|
||||
re.MatchString(method.String()))
|
||||
}
|
||||
|
||||
type C struct {
|
||||
method *methodType
|
||||
kind funcKind
|
||||
testName string
|
||||
status funcStatus
|
||||
logb *logger
|
||||
logw io.Writer
|
||||
done chan *C
|
||||
reason string
|
||||
mustFail bool
|
||||
tempDir *tempDir
|
||||
benchMem bool
|
||||
startTime time.Time
|
||||
timer
|
||||
}
|
||||
|
||||
func (c *C) stopNow() {
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
// logger is a concurrency safe byte.Buffer
|
||||
type logger struct {
|
||||
sync.Mutex
|
||||
writer bytes.Buffer
|
||||
}
|
||||
|
||||
func (l *logger) Write(buf []byte) (int, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.writer.Write(buf)
|
||||
}
|
||||
|
||||
func (l *logger) WriteTo(w io.Writer) (int64, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.writer.WriteTo(w)
|
||||
}
|
||||
|
||||
func (l *logger) String() string {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.writer.String()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Handling of temporary files and directories.
|
||||
|
||||
type tempDir struct {
|
||||
sync.Mutex
|
||||
path string
|
||||
counter int
|
||||
}
|
||||
|
||||
func (td *tempDir) newPath() string {
|
||||
td.Lock()
|
||||
defer td.Unlock()
|
||||
if td.path == "" {
|
||||
var err error
|
||||
for i := 0; i != 100; i++ {
|
||||
path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int())
|
||||
if err = os.Mkdir(path, 0700); err == nil {
|
||||
td.path = path
|
||||
break
|
||||
}
|
||||
}
|
||||
if td.path == "" {
|
||||
panic("Couldn't create temporary directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
result := filepath.Join(td.path, strconv.Itoa(td.counter))
|
||||
td.counter += 1
|
||||
return result
|
||||
}
|
||||
|
||||
func (td *tempDir) removeAll() {
|
||||
td.Lock()
|
||||
defer td.Unlock()
|
||||
if td.path != "" {
|
||||
err := os.RemoveAll(td.path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new temporary directory which is automatically removed after
|
||||
// the suite finishes running.
|
||||
func (c *C) MkDir() string {
|
||||
path := c.tempDir.newPath()
|
||||
if err := os.Mkdir(path, 0700); err != nil {
|
||||
panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error()))
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Low-level logging functions.
|
||||
|
||||
func (c *C) log(args ...interface{}) {
|
||||
c.writeLog([]byte(fmt.Sprint(args...) + "\n"))
|
||||
}
|
||||
|
||||
func (c *C) logf(format string, args ...interface{}) {
|
||||
c.writeLog([]byte(fmt.Sprintf(format+"\n", args...)))
|
||||
}
|
||||
|
||||
func (c *C) logNewLine() {
|
||||
c.writeLog([]byte{'\n'})
|
||||
}
|
||||
|
||||
func (c *C) writeLog(buf []byte) {
|
||||
c.logb.Write(buf)
|
||||
if c.logw != nil {
|
||||
c.logw.Write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
func hasStringOrError(x interface{}) (ok bool) {
|
||||
_, ok = x.(fmt.Stringer)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
_, ok = x.(error)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *C) logValue(label string, value interface{}) {
|
||||
if label == "" {
|
||||
if hasStringOrError(value) {
|
||||
c.logf("... %#v (%q)", value, value)
|
||||
} else {
|
||||
c.logf("... %#v", value)
|
||||
}
|
||||
} else if value == nil {
|
||||
c.logf("... %s = nil", label)
|
||||
} else {
|
||||
if hasStringOrError(value) {
|
||||
fv := fmt.Sprintf("%#v", value)
|
||||
qv := fmt.Sprintf("%q", value)
|
||||
if fv != qv {
|
||||
c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv)
|
||||
return
|
||||
}
|
||||
}
|
||||
if s, ok := value.(string); ok && isMultiLine(s) {
|
||||
c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value))
|
||||
c.logMultiLine(s)
|
||||
} else {
|
||||
c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *C) logMultiLine(s string) {
|
||||
b := make([]byte, 0, len(s)*2)
|
||||
i := 0
|
||||
n := len(s)
|
||||
for i < n {
|
||||
j := i + 1
|
||||
for j < n && s[j-1] != '\n' {
|
||||
j++
|
||||
}
|
||||
b = append(b, "... "...)
|
||||
b = strconv.AppendQuote(b, s[i:j])
|
||||
if j < n {
|
||||
b = append(b, " +"...)
|
||||
}
|
||||
b = append(b, '\n')
|
||||
i = j
|
||||
}
|
||||
c.writeLog(b)
|
||||
}
|
||||
|
||||
func isMultiLine(s string) bool {
|
||||
for i := 0; i+1 < len(s); i++ {
|
||||
if s[i] == '\n' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *C) logString(issue string) {
|
||||
c.log("... ", issue)
|
||||
}
|
||||
|
||||
func (c *C) logCaller(skip int) {
|
||||
// This is a bit heavier than it ought to be.
|
||||
skip += 1 // Our own frame.
|
||||
pc, callerFile, callerLine, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var testFile string
|
||||
var testLine int
|
||||
testFunc := runtime.FuncForPC(c.method.PC())
|
||||
if runtime.FuncForPC(pc) != testFunc {
|
||||
for {
|
||||
skip += 1
|
||||
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||
// Note that the test line may be different on
|
||||
// distinct calls for the same test. Showing
|
||||
// the "internal" line is helpful when debugging.
|
||||
if runtime.FuncForPC(pc) == testFunc {
|
||||
testFile, testLine = file, line
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if testFile != "" && (testFile != callerFile || testLine != callerLine) {
|
||||
c.logCode(testFile, testLine)
|
||||
}
|
||||
c.logCode(callerFile, callerLine)
|
||||
}
|
||||
|
||||
func (c *C) logCode(path string, line int) {
|
||||
c.logf("%s:%d:", nicePath(path), line)
|
||||
code, err := printLine(path, line)
|
||||
if code == "" {
|
||||
code = "..." // XXX Open the file and take the raw line.
|
||||
if err != nil {
|
||||
code += err.Error()
|
||||
}
|
||||
}
|
||||
c.log(indent(code, " "))
|
||||
}
|
||||
|
||||
var valueGo = filepath.Join("reflect", "value.go")
|
||||
var asmGo = filepath.Join("runtime", "asm_")
|
||||
|
||||
func (c *C) logPanic(skip int, value interface{}) {
|
||||
skip++ // Our own frame.
|
||||
initialSkip := skip
|
||||
for ; ; skip++ {
|
||||
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||
if skip == initialSkip {
|
||||
c.logf("... Panic: %s (PC=0x%X)\n", value, pc)
|
||||
}
|
||||
name := niceFuncName(pc)
|
||||
path := nicePath(file)
|
||||
if strings.Contains(path, "/gopkg.in/check.v") {
|
||||
continue
|
||||
}
|
||||
if name == "Value.call" && strings.HasSuffix(path, valueGo) {
|
||||
continue
|
||||
}
|
||||
if name == "call16" && strings.Contains(path, asmGo) {
|
||||
continue
|
||||
}
|
||||
c.logf("%s:%d\n in %s", nicePath(file), line, name)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *C) logSoftPanic(issue string) {
|
||||
c.log("... Panic: ", issue)
|
||||
}
|
||||
|
||||
func (c *C) logArgPanic(method *methodType, expectedType string) {
|
||||
c.logf("... Panic: %s argument should be %s",
|
||||
niceFuncName(method.PC()), expectedType)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Some simple formatting helpers.
|
||||
|
||||
var initWD, initWDErr = os.Getwd()
|
||||
|
||||
func init() {
|
||||
if initWDErr == nil {
|
||||
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
|
||||
}
|
||||
}
|
||||
|
||||
func nicePath(path string) string {
|
||||
if initWDErr == nil {
|
||||
if strings.HasPrefix(path, initWD) {
|
||||
return path[len(initWD):]
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func niceFuncPath(pc uintptr) string {
|
||||
function := runtime.FuncForPC(pc)
|
||||
if function != nil {
|
||||
filename, line := function.FileLine(pc)
|
||||
return fmt.Sprintf("%s:%d", nicePath(filename), line)
|
||||
}
|
||||
return "<unknown path>"
|
||||
}
|
||||
|
||||
func niceFuncName(pc uintptr) string {
|
||||
function := runtime.FuncForPC(pc)
|
||||
if function != nil {
|
||||
name := path.Base(function.Name())
|
||||
if i := strings.Index(name, "."); i > 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
if strings.HasPrefix(name, "(*") {
|
||||
if i := strings.Index(name, ")"); i > 0 {
|
||||
name = name[2:i] + name[i+1:]
|
||||
}
|
||||
}
|
||||
if i := strings.LastIndex(name, ".*"); i != -1 {
|
||||
name = name[:i] + "." + name[i+2:]
|
||||
}
|
||||
if i := strings.LastIndex(name, "·"); i != -1 {
|
||||
name = name[:i] + "." + name[i+2:]
|
||||
}
|
||||
return name
|
||||
}
|
||||
return "<unknown function>"
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Result tracker to aggregate call results.
|
||||
|
||||
type Result struct {
|
||||
Succeeded int
|
||||
Failed int
|
||||
Skipped int
|
||||
Panicked int
|
||||
FixturePanicked int
|
||||
ExpectedFailures int
|
||||
Missed int // Not even tried to run, related to a panic in the fixture.
|
||||
RunError error // Houston, we've got a problem.
|
||||
WorkDir string // If KeepWorkDir is true
|
||||
}
|
||||
|
||||
type resultTracker struct {
|
||||
result Result
|
||||
_lastWasProblem bool
|
||||
_waiting int
|
||||
_missed int
|
||||
_expectChan chan *C
|
||||
_doneChan chan *C
|
||||
_stopChan chan bool
|
||||
}
|
||||
|
||||
func newResultTracker() *resultTracker {
|
||||
return &resultTracker{_expectChan: make(chan *C), // Synchronous
|
||||
_doneChan: make(chan *C, 32), // Asynchronous
|
||||
_stopChan: make(chan bool)} // Synchronous
|
||||
}
|
||||
|
||||
func (tracker *resultTracker) start() {
|
||||
go tracker._loopRoutine()
|
||||
}
|
||||
|
||||
func (tracker *resultTracker) waitAndStop() {
|
||||
<-tracker._stopChan
|
||||
}
|
||||
|
||||
func (tracker *resultTracker) expectCall(c *C) {
|
||||
tracker._expectChan <- c
|
||||
}
|
||||
|
||||
func (tracker *resultTracker) callDone(c *C) {
|
||||
tracker._doneChan <- c
|
||||
}
|
||||
|
||||
func (tracker *resultTracker) _loopRoutine() {
|
||||
for {
|
||||
var c *C
|
||||
if tracker._waiting > 0 {
|
||||
// Calls still running. Can't stop.
|
||||
select {
|
||||
// XXX Reindent this (not now to make diff clear)
|
||||
case c = <-tracker._expectChan:
|
||||
tracker._waiting += 1
|
||||
case c = <-tracker._doneChan:
|
||||
tracker._waiting -= 1
|
||||
switch c.status {
|
||||
case succeededSt:
|
||||
if c.kind == testKd {
|
||||
if c.mustFail {
|
||||
tracker.result.ExpectedFailures++
|
||||
} else {
|
||||
tracker.result.Succeeded++
|
||||
}
|
||||
}
|
||||
case failedSt:
|
||||
tracker.result.Failed++
|
||||
case panickedSt:
|
||||
if c.kind == fixtureKd {
|
||||
tracker.result.FixturePanicked++
|
||||
} else {
|
||||
tracker.result.Panicked++
|
||||
}
|
||||
case fixturePanickedSt:
|
||||
// Track it as missed, since the panic
|
||||
// was on the fixture, not on the test.
|
||||
tracker.result.Missed++
|
||||
case missedSt:
|
||||
tracker.result.Missed++
|
||||
case skippedSt:
|
||||
if c.kind == testKd {
|
||||
tracker.result.Skipped++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No calls. Can stop, but no done calls here.
|
||||
select {
|
||||
case tracker._stopChan <- true:
|
||||
return
|
||||
case c = <-tracker._expectChan:
|
||||
tracker._waiting += 1
|
||||
case c = <-tracker._doneChan:
|
||||
panic("Tracker got an unexpected done call.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// The underlying suite runner.
|
||||
|
||||
type suiteRunner struct {
|
||||
suite interface{}
|
||||
setUpSuite, tearDownSuite *methodType
|
||||
setUpTest, tearDownTest *methodType
|
||||
tests []*methodType
|
||||
tracker *resultTracker
|
||||
tempDir *tempDir
|
||||
keepDir bool
|
||||
output *outputWriter
|
||||
reportedProblemLast bool
|
||||
benchTime time.Duration
|
||||
benchMem bool
|
||||
}
|
||||
|
||||
type RunConf struct {
|
||||
Output io.Writer
|
||||
Stream bool
|
||||
Verbose bool
|
||||
Filter string
|
||||
Benchmark bool
|
||||
BenchmarkTime time.Duration // Defaults to 1 second
|
||||
BenchmarkMem bool
|
||||
KeepWorkDir bool
|
||||
}
|
||||
|
||||
// Create a new suiteRunner able to run all methods in the given suite.
|
||||
func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
|
||||
var conf RunConf
|
||||
if runConf != nil {
|
||||
conf = *runConf
|
||||
}
|
||||
if conf.Output == nil {
|
||||
conf.Output = os.Stdout
|
||||
}
|
||||
if conf.Benchmark {
|
||||
conf.Verbose = true
|
||||
}
|
||||
|
||||
suiteType := reflect.TypeOf(suite)
|
||||
suiteNumMethods := suiteType.NumMethod()
|
||||
suiteValue := reflect.ValueOf(suite)
|
||||
|
||||
runner := &suiteRunner{
|
||||
suite: suite,
|
||||
output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose),
|
||||
tracker: newResultTracker(),
|
||||
benchTime: conf.BenchmarkTime,
|
||||
benchMem: conf.BenchmarkMem,
|
||||
tempDir: &tempDir{},
|
||||
keepDir: conf.KeepWorkDir,
|
||||
tests: make([]*methodType, 0, suiteNumMethods),
|
||||
}
|
||||
if runner.benchTime == 0 {
|
||||
runner.benchTime = 1 * time.Second
|
||||
}
|
||||
|
||||
var filterRegexp *regexp.Regexp
|
||||
if conf.Filter != "" {
|
||||
if regexp, err := regexp.Compile(conf.Filter); err != nil {
|
||||
msg := "Bad filter expression: " + err.Error()
|
||||
runner.tracker.result.RunError = errors.New(msg)
|
||||
return runner
|
||||
} else {
|
||||
filterRegexp = regexp
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i != suiteNumMethods; i++ {
|
||||
method := newMethod(suiteValue, i)
|
||||
switch method.Info.Name {
|
||||
case "SetUpSuite":
|
||||
runner.setUpSuite = method
|
||||
case "TearDownSuite":
|
||||
runner.tearDownSuite = method
|
||||
case "SetUpTest":
|
||||
runner.setUpTest = method
|
||||
case "TearDownTest":
|
||||
runner.tearDownTest = method
|
||||
default:
|
||||
prefix := "Test"
|
||||
if conf.Benchmark {
|
||||
prefix = "Benchmark"
|
||||
}
|
||||
if !strings.HasPrefix(method.Info.Name, prefix) {
|
||||
continue
|
||||
}
|
||||
if filterRegexp == nil || method.matches(filterRegexp) {
|
||||
runner.tests = append(runner.tests, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
return runner
|
||||
}
|
||||
|
||||
// Run all methods in the given suite.
|
||||
func (runner *suiteRunner) run() *Result {
|
||||
if runner.tracker.result.RunError == nil && len(runner.tests) > 0 {
|
||||
runner.tracker.start()
|
||||
if runner.checkFixtureArgs() {
|
||||
c := runner.runFixture(runner.setUpSuite, "", nil)
|
||||
if c == nil || c.status == succeededSt {
|
||||
for i := 0; i != len(runner.tests); i++ {
|
||||
c := runner.runTest(runner.tests[i])
|
||||
if c.status == fixturePanickedSt {
|
||||
runner.skipTests(missedSt, runner.tests[i+1:])
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if c != nil && c.status == skippedSt {
|
||||
runner.skipTests(skippedSt, runner.tests)
|
||||
} else {
|
||||
runner.skipTests(missedSt, runner.tests)
|
||||
}
|
||||
runner.runFixture(runner.tearDownSuite, "", nil)
|
||||
} else {
|
||||
runner.skipTests(missedSt, runner.tests)
|
||||
}
|
||||
runner.tracker.waitAndStop()
|
||||
if runner.keepDir {
|
||||
runner.tracker.result.WorkDir = runner.tempDir.path
|
||||
} else {
|
||||
runner.tempDir.removeAll()
|
||||
}
|
||||
}
|
||||
return &runner.tracker.result
|
||||
}
|
||||
|
||||
// Create a call object with the given suite method, and fork a
|
||||
// goroutine with the provided dispatcher for running it.
|
||||
func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
|
||||
var logw io.Writer
|
||||
if runner.output.Stream {
|
||||
logw = runner.output
|
||||
}
|
||||
if logb == nil {
|
||||
logb = new(logger)
|
||||
}
|
||||
c := &C{
|
||||
method: method,
|
||||
kind: kind,
|
||||
testName: testName,
|
||||
logb: logb,
|
||||
logw: logw,
|
||||
tempDir: runner.tempDir,
|
||||
done: make(chan *C, 1),
|
||||
timer: timer{benchTime: runner.benchTime},
|
||||
startTime: time.Now(),
|
||||
benchMem: runner.benchMem,
|
||||
}
|
||||
runner.tracker.expectCall(c)
|
||||
go (func() {
|
||||
runner.reportCallStarted(c)
|
||||
defer runner.callDone(c)
|
||||
dispatcher(c)
|
||||
})()
|
||||
return c
|
||||
}
|
||||
|
||||
// Same as forkCall(), but wait for call to finish before returning.
|
||||
func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
|
||||
c := runner.forkCall(method, kind, testName, logb, dispatcher)
|
||||
<-c.done
|
||||
return c
|
||||
}
|
||||
|
||||
// Handle a finished call. If there were any panics, update the call status
|
||||
// accordingly. Then, mark the call as done and report to the tracker.
|
||||
func (runner *suiteRunner) callDone(c *C) {
|
||||
value := recover()
|
||||
if value != nil {
|
||||
switch v := value.(type) {
|
||||
case *fixturePanic:
|
||||
if v.status == skippedSt {
|
||||
c.status = skippedSt
|
||||
} else {
|
||||
c.logSoftPanic("Fixture has panicked (see related PANIC)")
|
||||
c.status = fixturePanickedSt
|
||||
}
|
||||
default:
|
||||
c.logPanic(1, value)
|
||||
c.status = panickedSt
|
||||
}
|
||||
}
|
||||
if c.mustFail {
|
||||
switch c.status {
|
||||
case failedSt:
|
||||
c.status = succeededSt
|
||||
case succeededSt:
|
||||
c.status = failedSt
|
||||
c.logString("Error: Test succeeded, but was expected to fail")
|
||||
c.logString("Reason: " + c.reason)
|
||||
}
|
||||
}
|
||||
|
||||
runner.reportCallDone(c)
|
||||
c.done <- c
|
||||
}
|
||||
|
||||
// Runs a fixture call synchronously. The fixture will still be run in a
|
||||
// goroutine like all suite methods, but this method will not return
|
||||
// while the fixture goroutine is not done, because the fixture must be
|
||||
// run in a desired order.
|
||||
func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C {
|
||||
if method != nil {
|
||||
c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) {
|
||||
c.ResetTimer()
|
||||
c.StartTimer()
|
||||
defer c.StopTimer()
|
||||
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||
})
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
|
||||
// in case the fixture method panics. This makes it easier to track the
|
||||
// fixture panic together with other call panics within forkTest().
|
||||
func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C {
|
||||
if skipped != nil && *skipped {
|
||||
return nil
|
||||
}
|
||||
c := runner.runFixture(method, testName, logb)
|
||||
if c != nil && c.status != succeededSt {
|
||||
if skipped != nil {
|
||||
*skipped = c.status == skippedSt
|
||||
}
|
||||
panic(&fixturePanic{c.status, method})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type fixturePanic struct {
|
||||
status funcStatus
|
||||
method *methodType
|
||||
}
|
||||
|
||||
// Run the suite test method, together with the test-specific fixture,
|
||||
// asynchronously.
|
||||
func (runner *suiteRunner) forkTest(method *methodType) *C {
|
||||
testName := method.String()
|
||||
return runner.forkCall(method, testKd, testName, nil, func(c *C) {
|
||||
var skipped bool
|
||||
defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped)
|
||||
defer c.StopTimer()
|
||||
benchN := 1
|
||||
for {
|
||||
runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped)
|
||||
mt := c.method.Type()
|
||||
if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) {
|
||||
// Rather than a plain panic, provide a more helpful message when
|
||||
// the argument type is incorrect.
|
||||
c.status = panickedSt
|
||||
c.logArgPanic(c.method, "*check.C")
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(c.method.Info.Name, "Test") {
|
||||
c.ResetTimer()
|
||||
c.StartTimer()
|
||||
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(c.method.Info.Name, "Benchmark") {
|
||||
panic("unexpected method prefix: " + c.method.Info.Name)
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
c.N = benchN
|
||||
c.ResetTimer()
|
||||
c.StartTimer()
|
||||
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||
c.StopTimer()
|
||||
if c.status != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 {
|
||||
return
|
||||
}
|
||||
perOpN := int(1e9)
|
||||
if c.nsPerOp() != 0 {
|
||||
perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp())
|
||||
}
|
||||
|
||||
// Logic taken from the stock testing package:
|
||||
// - Run more iterations than we think we'll need for a second (1.5x).
|
||||
// - Don't grow too fast in case we had timing errors previously.
|
||||
// - Be sure to run at least one more than last time.
|
||||
benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1)
|
||||
benchN = roundUp(benchN)
|
||||
|
||||
skipped = true // Don't run the deferred one if this panics.
|
||||
runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil)
|
||||
skipped = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Same as forkTest(), but wait for the test to finish before returning.
|
||||
func (runner *suiteRunner) runTest(method *methodType) *C {
|
||||
c := runner.forkTest(method)
|
||||
<-c.done
|
||||
return c
|
||||
}
|
||||
|
||||
// Helper to mark tests as skipped or missed. A bit heavy for what
|
||||
// it does, but it enables homogeneous handling of tracking, including
|
||||
// nice verbose output.
|
||||
func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) {
|
||||
for _, method := range methods {
|
||||
runner.runFunc(method, testKd, "", nil, func(c *C) {
|
||||
c.status = status
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if the fixture arguments are *check.C. In case of errors,
|
||||
// log the error as a panic in the fixture method call, and return false.
|
||||
func (runner *suiteRunner) checkFixtureArgs() bool {
|
||||
succeeded := true
|
||||
argType := reflect.TypeOf(&C{})
|
||||
for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} {
|
||||
if method != nil {
|
||||
mt := method.Type()
|
||||
if mt.NumIn() != 1 || mt.In(0) != argType {
|
||||
succeeded = false
|
||||
runner.runFunc(method, fixtureKd, "", nil, func(c *C) {
|
||||
c.logArgPanic(method, "*check.C")
|
||||
c.status = panickedSt
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return succeeded
|
||||
}
|
||||
|
||||
func (runner *suiteRunner) reportCallStarted(c *C) {
|
||||
runner.output.WriteCallStarted("START", c)
|
||||
}
|
||||
|
||||
func (runner *suiteRunner) reportCallDone(c *C) {
|
||||
runner.tracker.callDone(c)
|
||||
switch c.status {
|
||||
case succeededSt:
|
||||
if c.mustFail {
|
||||
runner.output.WriteCallSuccess("FAIL EXPECTED", c)
|
||||
} else {
|
||||
runner.output.WriteCallSuccess("PASS", c)
|
||||
}
|
||||
case skippedSt:
|
||||
runner.output.WriteCallSuccess("SKIP", c)
|
||||
case failedSt:
|
||||
runner.output.WriteCallProblem("FAIL", c)
|
||||
case panickedSt:
|
||||
runner.output.WriteCallProblem("PANIC", c)
|
||||
case fixturePanickedSt:
|
||||
// That's a testKd call reporting that its fixture
|
||||
// has panicked. The fixture call which caused the
|
||||
// panic itself was tracked above. We'll report to
|
||||
// aid debugging.
|
||||
runner.output.WriteCallProblem("PANIC", c)
|
||||
case missedSt:
|
||||
runner.output.WriteCallSuccess("MISS", c)
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Output writer manages atomic output writing according to settings.
|
||||
|
||||
type outputWriter struct {
|
||||
m sync.Mutex
|
||||
writer io.Writer
|
||||
wroteCallProblemLast bool
|
||||
Stream bool
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter {
|
||||
return &outputWriter{writer: writer, Stream: stream, Verbose: verbose}
|
||||
}
|
||||
|
||||
func (ow *outputWriter) Write(content []byte) (n int, err error) {
|
||||
ow.m.Lock()
|
||||
n, err = ow.writer.Write(content)
|
||||
ow.m.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (ow *outputWriter) WriteCallStarted(label string, c *C) {
|
||||
if ow.Stream {
|
||||
header := renderCallHeader(label, c, "", "\n")
|
||||
ow.m.Lock()
|
||||
ow.writer.Write([]byte(header))
|
||||
ow.m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (ow *outputWriter) WriteCallProblem(label string, c *C) {
|
||||
var prefix string
|
||||
if !ow.Stream {
|
||||
prefix = "\n-----------------------------------" +
|
||||
"-----------------------------------\n"
|
||||
}
|
||||
header := renderCallHeader(label, c, prefix, "\n\n")
|
||||
ow.m.Lock()
|
||||
ow.wroteCallProblemLast = true
|
||||
ow.writer.Write([]byte(header))
|
||||
if !ow.Stream {
|
||||
c.logb.WriteTo(ow.writer)
|
||||
}
|
||||
ow.m.Unlock()
|
||||
}
|
||||
|
||||
func (ow *outputWriter) WriteCallSuccess(label string, c *C) {
|
||||
if ow.Stream || (ow.Verbose && c.kind == testKd) {
|
||||
// TODO Use a buffer here.
|
||||
var suffix string
|
||||
if c.reason != "" {
|
||||
suffix = " (" + c.reason + ")"
|
||||
}
|
||||
if c.status == succeededSt {
|
||||
suffix += "\t" + c.timerString()
|
||||
}
|
||||
suffix += "\n"
|
||||
if ow.Stream {
|
||||
suffix += "\n"
|
||||
}
|
||||
header := renderCallHeader(label, c, "", suffix)
|
||||
ow.m.Lock()
|
||||
// Resist temptation of using line as prefix above due to race.
|
||||
if !ow.Stream && ow.wroteCallProblemLast {
|
||||
header = "\n-----------------------------------" +
|
||||
"-----------------------------------\n" +
|
||||
header
|
||||
}
|
||||
ow.wroteCallProblemLast = false
|
||||
ow.writer.Write([]byte(header))
|
||||
ow.m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func renderCallHeader(label string, c *C, prefix, suffix string) string {
|
||||
pc := c.method.PC()
|
||||
return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc),
|
||||
niceFuncName(pc), suffix)
|
||||
}
|
||||
207
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/check_test.go
generated
vendored
Normal file
207
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/check_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// This file contains just a few generic helpers which are used by the
|
||||
// other test files.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// We count the number of suites run at least to get a vague hint that the
|
||||
// test suite is behaving as it should. Otherwise a bug introduced at the
|
||||
// very core of the system could go unperceived.
|
||||
const suitesRunExpected = 8
|
||||
|
||||
var suitesRun int = 0
|
||||
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
if suitesRun != suitesRunExpected && flag.Lookup("check.f").Value.String() == "" {
|
||||
critical(fmt.Sprintf("Expected %d suites to run rather than %d",
|
||||
suitesRunExpected, suitesRun))
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper functions.
|
||||
|
||||
// Break down badly. This is used in test cases which can't yet assume
|
||||
// that the fundamental bits are working.
|
||||
func critical(error string) {
|
||||
fmt.Fprintln(os.Stderr, "CRITICAL: "+error)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Return the file line where it's called.
|
||||
func getMyLine() int {
|
||||
if _, _, line, ok := runtime.Caller(1); ok {
|
||||
return line
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper type implementing a basic io.Writer for testing output.
|
||||
|
||||
// Type implementing the io.Writer interface for analyzing output.
|
||||
type String struct {
|
||||
value string
|
||||
}
|
||||
|
||||
// The only function required by the io.Writer interface. Will append
|
||||
// written data to the String.value string.
|
||||
func (s *String) Write(p []byte) (n int, err error) {
|
||||
s.value += string(p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Trivial wrapper to test errors happening on a different file
|
||||
// than the test itself.
|
||||
func checkEqualWrapper(c *check.C, obtained, expected interface{}) (result bool, line int) {
|
||||
return c.Check(obtained, check.Equals, expected), getMyLine()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper suite for testing basic fail behavior.
|
||||
|
||||
type FailHelper struct {
|
||||
testLine int
|
||||
}
|
||||
|
||||
func (s *FailHelper) TestLogAndFail(c *check.C) {
|
||||
s.testLine = getMyLine() - 1
|
||||
c.Log("Expected failure!")
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper suite for testing basic success behavior.
|
||||
|
||||
type SuccessHelper struct{}
|
||||
|
||||
func (s *SuccessHelper) TestLogAndSucceed(c *check.C) {
|
||||
c.Log("Expected success!")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper suite for testing ordering and behavior of fixture.
|
||||
|
||||
type FixtureHelper struct {
|
||||
calls []string
|
||||
panicOn string
|
||||
skip bool
|
||||
skipOnN int
|
||||
sleepOn string
|
||||
sleep time.Duration
|
||||
bytes int64
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) trace(name string, c *check.C) {
|
||||
s.calls = append(s.calls, name)
|
||||
if name == s.panicOn {
|
||||
panic(name)
|
||||
}
|
||||
if s.sleep > 0 && s.sleepOn == name {
|
||||
time.Sleep(s.sleep)
|
||||
}
|
||||
if s.skip && s.skipOnN == len(s.calls)-1 {
|
||||
c.Skip("skipOnN == n")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) SetUpSuite(c *check.C) {
|
||||
s.trace("SetUpSuite", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) TearDownSuite(c *check.C) {
|
||||
s.trace("TearDownSuite", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) SetUpTest(c *check.C) {
|
||||
s.trace("SetUpTest", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) TearDownTest(c *check.C) {
|
||||
s.trace("TearDownTest", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) Test1(c *check.C) {
|
||||
s.trace("Test1", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) Test2(c *check.C) {
|
||||
s.trace("Test2", c)
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) Benchmark1(c *check.C) {
|
||||
s.trace("Benchmark1", c)
|
||||
for i := 0; i < c.N; i++ {
|
||||
time.Sleep(s.sleep)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) Benchmark2(c *check.C) {
|
||||
s.trace("Benchmark2", c)
|
||||
c.SetBytes(1024)
|
||||
for i := 0; i < c.N; i++ {
|
||||
time.Sleep(s.sleep)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FixtureHelper) Benchmark3(c *check.C) {
|
||||
var x []int64
|
||||
s.trace("Benchmark3", c)
|
||||
for i := 0; i < c.N; i++ {
|
||||
time.Sleep(s.sleep)
|
||||
x = make([]int64, 5)
|
||||
_ = x
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper which checks the state of the test and ensures that it matches
|
||||
// the given expectations. Depends on c.Errorf() working, so shouldn't
|
||||
// be used to test this one function.
|
||||
|
||||
type expectedState struct {
|
||||
name string
|
||||
result interface{}
|
||||
failed bool
|
||||
log string
|
||||
}
|
||||
|
||||
// Verify the state of the test. Note that since this also verifies if
|
||||
// the test is supposed to be in a failed state, no other checks should
|
||||
// be done in addition to what is being tested.
|
||||
func checkState(c *check.C, result interface{}, expected *expectedState) {
|
||||
failed := c.Failed()
|
||||
c.Succeed()
|
||||
log := c.GetTestLog()
|
||||
matched, matchError := regexp.MatchString("^"+expected.log+"$", log)
|
||||
if matchError != nil {
|
||||
c.Errorf("Error in matching expression used in testing %s",
|
||||
expected.name)
|
||||
} else if !matched {
|
||||
c.Errorf("%s logged:\n----------\n%s----------\n\nExpected:\n----------\n%s\n----------",
|
||||
expected.name, log, expected.log)
|
||||
}
|
||||
if result != expected.result {
|
||||
c.Errorf("%s returned %#v rather than %#v",
|
||||
expected.name, result, expected.result)
|
||||
}
|
||||
if failed != expected.failed {
|
||||
if failed {
|
||||
c.Errorf("%s has failed when it shouldn't", expected.name)
|
||||
} else {
|
||||
c.Errorf("%s has not failed when it should", expected.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
458
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/checkers.go
generated
vendored
Normal file
458
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/checkers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
package check
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CommentInterface and Commentf helper, to attach extra information to checks.
|
||||
|
||||
type comment struct {
|
||||
format string
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Commentf returns an infomational value to use with Assert or Check calls.
|
||||
// If the checker test fails, the provided arguments will be passed to
|
||||
// fmt.Sprintf, and will be presented next to the logged failure.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i))
|
||||
//
|
||||
// Note that if the comment is constant, a better option is to
|
||||
// simply use a normal comment right above or next to the line, as
|
||||
// it will also get printed with any errors:
|
||||
//
|
||||
// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123)
|
||||
//
|
||||
func Commentf(format string, args ...interface{}) CommentInterface {
|
||||
return &comment{format, args}
|
||||
}
|
||||
|
||||
// CommentInterface must be implemented by types that attach extra
|
||||
// information to failed checks. See the Commentf function for details.
|
||||
type CommentInterface interface {
|
||||
CheckCommentString() string
|
||||
}
|
||||
|
||||
func (c *comment) CheckCommentString() string {
|
||||
return fmt.Sprintf(c.format, c.args...)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// The Checker interface.
|
||||
|
||||
// The Checker interface must be provided by checkers used with
|
||||
// the Assert and Check verification methods.
|
||||
type Checker interface {
|
||||
Info() *CheckerInfo
|
||||
Check(params []interface{}, names []string) (result bool, error string)
|
||||
}
|
||||
|
||||
// See the Checker interface.
|
||||
type CheckerInfo struct {
|
||||
Name string
|
||||
Params []string
|
||||
}
|
||||
|
||||
func (info *CheckerInfo) Info() *CheckerInfo {
|
||||
return info
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Not checker logic inverter.
|
||||
|
||||
// The Not checker inverts the logic of the provided checker. The
|
||||
// resulting checker will succeed where the original one failed, and
|
||||
// vice-versa.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(a, Not(Equals), b)
|
||||
//
|
||||
func Not(checker Checker) Checker {
|
||||
return ¬Checker{checker}
|
||||
}
|
||||
|
||||
type notChecker struct {
|
||||
sub Checker
|
||||
}
|
||||
|
||||
func (checker *notChecker) Info() *CheckerInfo {
|
||||
info := *checker.sub.Info()
|
||||
info.Name = "Not(" + info.Name + ")"
|
||||
return &info
|
||||
}
|
||||
|
||||
func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
result, error = checker.sub.Check(params, names)
|
||||
result = !result
|
||||
return
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// IsNil checker.
|
||||
|
||||
type isNilChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The IsNil checker tests whether the obtained value is nil.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(err, IsNil)
|
||||
//
|
||||
var IsNil Checker = &isNilChecker{
|
||||
&CheckerInfo{Name: "IsNil", Params: []string{"value"}},
|
||||
}
|
||||
|
||||
func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
return isNil(params[0]), ""
|
||||
}
|
||||
|
||||
func isNil(obtained interface{}) (result bool) {
|
||||
if obtained == nil {
|
||||
result = true
|
||||
} else {
|
||||
switch v := reflect.ValueOf(obtained); v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return v.IsNil()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// NotNil checker. Alias for Not(IsNil), since it's so common.
|
||||
|
||||
type notNilChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The NotNil checker verifies that the obtained value is not nil.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(iface, NotNil)
|
||||
//
|
||||
// This is an alias for Not(IsNil), made available since it's a
|
||||
// fairly common check.
|
||||
//
|
||||
var NotNil Checker = ¬NilChecker{
|
||||
&CheckerInfo{Name: "NotNil", Params: []string{"value"}},
|
||||
}
|
||||
|
||||
func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
return !isNil(params[0]), ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Equals checker.
|
||||
|
||||
type equalsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The Equals checker verifies that the obtained value is equal to
|
||||
// the expected value, according to usual Go semantics for ==.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(value, Equals, 42)
|
||||
//
|
||||
var Equals Checker = &equalsChecker{
|
||||
&CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}},
|
||||
}
|
||||
|
||||
func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
result = false
|
||||
error = fmt.Sprint(v)
|
||||
}
|
||||
}()
|
||||
return params[0] == params[1], ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DeepEquals checker.
|
||||
|
||||
type deepEqualsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The DeepEquals checker verifies that the obtained value is deep-equal to
|
||||
// the expected value. The check will work correctly even when facing
|
||||
// slices, interfaces, and values of different types (which always fail
|
||||
// the test).
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(value, DeepEquals, 42)
|
||||
// c.Assert(array, DeepEquals, []string{"hi", "there"})
|
||||
//
|
||||
var DeepEquals Checker = &deepEqualsChecker{
|
||||
&CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}},
|
||||
}
|
||||
|
||||
func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
return reflect.DeepEqual(params[0], params[1]), ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// HasLen checker.
|
||||
|
||||
type hasLenChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The HasLen checker verifies that the obtained value has the
|
||||
// provided length. In many cases this is superior to using Equals
|
||||
// in conjuction with the len function because in case the check
|
||||
// fails the value itself will be printed, instead of its length,
|
||||
// providing more details for figuring the problem.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(list, HasLen, 5)
|
||||
//
|
||||
var HasLen Checker = &hasLenChecker{
|
||||
&CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}},
|
||||
}
|
||||
|
||||
func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
n, ok := params[1].(int)
|
||||
if !ok {
|
||||
return false, "n must be an int"
|
||||
}
|
||||
value := reflect.ValueOf(params[0])
|
||||
switch value.Kind() {
|
||||
case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String:
|
||||
default:
|
||||
return false, "obtained value type has no length"
|
||||
}
|
||||
return value.Len() == n, ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ErrorMatches checker.
|
||||
|
||||
type errorMatchesChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The ErrorMatches checker verifies that the error value
|
||||
// is non nil and matches the regular expression provided.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(err, ErrorMatches, "perm.*denied")
|
||||
//
|
||||
var ErrorMatches Checker = errorMatchesChecker{
|
||||
&CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}},
|
||||
}
|
||||
|
||||
func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) {
|
||||
if params[0] == nil {
|
||||
return false, "Error value is nil"
|
||||
}
|
||||
err, ok := params[0].(error)
|
||||
if !ok {
|
||||
return false, "Value is not an error"
|
||||
}
|
||||
params[0] = err.Error()
|
||||
names[0] = "error"
|
||||
return matches(params[0], params[1])
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Matches checker.
|
||||
|
||||
type matchesChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The Matches checker verifies that the string provided as the obtained
|
||||
// value (or the string resulting from obtained.String()) matches the
|
||||
// regular expression provided.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(err, Matches, "perm.*denied")
|
||||
//
|
||||
var Matches Checker = &matchesChecker{
|
||||
&CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}},
|
||||
}
|
||||
|
||||
func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
return matches(params[0], params[1])
|
||||
}
|
||||
|
||||
func matches(value, regex interface{}) (result bool, error string) {
|
||||
reStr, ok := regex.(string)
|
||||
if !ok {
|
||||
return false, "Regex must be a string"
|
||||
}
|
||||
valueStr, valueIsStr := value.(string)
|
||||
if !valueIsStr {
|
||||
if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr {
|
||||
valueStr, valueIsStr = valueWithStr.String(), true
|
||||
}
|
||||
}
|
||||
if valueIsStr {
|
||||
matches, err := regexp.MatchString("^"+reStr+"$", valueStr)
|
||||
if err != nil {
|
||||
return false, "Can't compile regex: " + err.Error()
|
||||
}
|
||||
return matches, ""
|
||||
}
|
||||
return false, "Obtained value is not a string and has no .String()"
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Panics checker.
|
||||
|
||||
type panicsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The Panics checker verifies that calling the provided zero-argument
|
||||
// function will cause a panic which is deep-equal to the provided value.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}).
|
||||
//
|
||||
//
|
||||
var Panics Checker = &panicsChecker{
|
||||
&CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}},
|
||||
}
|
||||
|
||||
func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
f := reflect.ValueOf(params[0])
|
||||
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
|
||||
return false, "Function must take zero arguments"
|
||||
}
|
||||
defer func() {
|
||||
// If the function has not panicked, then don't do the check.
|
||||
if error != "" {
|
||||
return
|
||||
}
|
||||
params[0] = recover()
|
||||
names[0] = "panic"
|
||||
result = reflect.DeepEqual(params[0], params[1])
|
||||
}()
|
||||
f.Call(nil)
|
||||
return false, "Function has not panicked"
|
||||
}
|
||||
|
||||
type panicMatchesChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The PanicMatches checker verifies that calling the provided zero-argument
|
||||
// function will cause a panic with an error value matching
|
||||
// the regular expression provided.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`).
|
||||
//
|
||||
//
|
||||
var PanicMatches Checker = &panicMatchesChecker{
|
||||
&CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}},
|
||||
}
|
||||
|
||||
func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) {
|
||||
f := reflect.ValueOf(params[0])
|
||||
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
|
||||
return false, "Function must take zero arguments"
|
||||
}
|
||||
defer func() {
|
||||
// If the function has not panicked, then don't do the check.
|
||||
if errmsg != "" {
|
||||
return
|
||||
}
|
||||
obtained := recover()
|
||||
names[0] = "panic"
|
||||
if e, ok := obtained.(error); ok {
|
||||
params[0] = e.Error()
|
||||
} else if _, ok := obtained.(string); ok {
|
||||
params[0] = obtained
|
||||
} else {
|
||||
errmsg = "Panic value is not a string or an error"
|
||||
return
|
||||
}
|
||||
result, errmsg = matches(params[0], params[1])
|
||||
}()
|
||||
f.Call(nil)
|
||||
return false, "Function has not panicked"
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// FitsTypeOf checker.
|
||||
|
||||
type fitsTypeChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The FitsTypeOf checker verifies that the obtained value is
|
||||
// assignable to a variable with the same type as the provided
|
||||
// sample value.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c.Assert(value, FitsTypeOf, int64(0))
|
||||
// c.Assert(value, FitsTypeOf, os.Error(nil))
|
||||
//
|
||||
var FitsTypeOf Checker = &fitsTypeChecker{
|
||||
&CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}},
|
||||
}
|
||||
|
||||
func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
obtained := reflect.ValueOf(params[0])
|
||||
sample := reflect.ValueOf(params[1])
|
||||
if !obtained.IsValid() {
|
||||
return false, ""
|
||||
}
|
||||
if !sample.IsValid() {
|
||||
return false, "Invalid sample value"
|
||||
}
|
||||
return obtained.Type().AssignableTo(sample.Type()), ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Implements checker.
|
||||
|
||||
type implementsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
// The Implements checker verifies that the obtained value
|
||||
// implements the interface specified via a pointer to an interface
|
||||
// variable.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var e os.Error
|
||||
// c.Assert(err, Implements, &e)
|
||||
//
|
||||
var Implements Checker = &implementsChecker{
|
||||
&CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}},
|
||||
}
|
||||
|
||||
func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
obtained := reflect.ValueOf(params[0])
|
||||
ifaceptr := reflect.ValueOf(params[1])
|
||||
if !obtained.IsValid() {
|
||||
return false, ""
|
||||
}
|
||||
if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface {
|
||||
return false, "ifaceptr should be a pointer to an interface variable"
|
||||
}
|
||||
return obtained.Type().Implements(ifaceptr.Elem().Type()), ""
|
||||
}
|
||||
272
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/checkers_test.go
generated
vendored
Normal file
272
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/checkers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
package check_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type CheckersS struct{}
|
||||
|
||||
var _ = check.Suite(&CheckersS{})
|
||||
|
||||
func testInfo(c *check.C, checker check.Checker, name string, paramNames []string) {
|
||||
info := checker.Info()
|
||||
if info.Name != name {
|
||||
c.Fatalf("Got name %s, expected %s", info.Name, name)
|
||||
}
|
||||
if !reflect.DeepEqual(info.Params, paramNames) {
|
||||
c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames)
|
||||
}
|
||||
}
|
||||
|
||||
func testCheck(c *check.C, checker check.Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) {
|
||||
info := checker.Info()
|
||||
if len(params) != len(info.Params) {
|
||||
c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params))
|
||||
}
|
||||
names := append([]string{}, info.Params...)
|
||||
result_, error_ := checker.Check(params, names)
|
||||
if result_ != result || error_ != error {
|
||||
c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)",
|
||||
info.Name, params, result_, error_, result, error)
|
||||
}
|
||||
return params, names
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestComment(c *check.C) {
|
||||
bug := check.Commentf("a %d bc", 42)
|
||||
comment := bug.CheckCommentString()
|
||||
if comment != "a 42 bc" {
|
||||
c.Fatalf("Commentf returned %#v", comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestIsNil(c *check.C) {
|
||||
testInfo(c, check.IsNil, "IsNil", []string{"value"})
|
||||
|
||||
testCheck(c, check.IsNil, true, "", nil)
|
||||
testCheck(c, check.IsNil, false, "", "a")
|
||||
|
||||
testCheck(c, check.IsNil, true, "", (chan int)(nil))
|
||||
testCheck(c, check.IsNil, false, "", make(chan int))
|
||||
testCheck(c, check.IsNil, true, "", (error)(nil))
|
||||
testCheck(c, check.IsNil, false, "", errors.New(""))
|
||||
testCheck(c, check.IsNil, true, "", ([]int)(nil))
|
||||
testCheck(c, check.IsNil, false, "", make([]int, 1))
|
||||
testCheck(c, check.IsNil, false, "", int(0))
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestNotNil(c *check.C) {
|
||||
testInfo(c, check.NotNil, "NotNil", []string{"value"})
|
||||
|
||||
testCheck(c, check.NotNil, false, "", nil)
|
||||
testCheck(c, check.NotNil, true, "", "a")
|
||||
|
||||
testCheck(c, check.NotNil, false, "", (chan int)(nil))
|
||||
testCheck(c, check.NotNil, true, "", make(chan int))
|
||||
testCheck(c, check.NotNil, false, "", (error)(nil))
|
||||
testCheck(c, check.NotNil, true, "", errors.New(""))
|
||||
testCheck(c, check.NotNil, false, "", ([]int)(nil))
|
||||
testCheck(c, check.NotNil, true, "", make([]int, 1))
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestNot(c *check.C) {
|
||||
testInfo(c, check.Not(check.IsNil), "Not(IsNil)", []string{"value"})
|
||||
|
||||
testCheck(c, check.Not(check.IsNil), false, "", nil)
|
||||
testCheck(c, check.Not(check.IsNil), true, "", "a")
|
||||
}
|
||||
|
||||
type simpleStruct struct {
|
||||
i int
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestEquals(c *check.C) {
|
||||
testInfo(c, check.Equals, "Equals", []string{"obtained", "expected"})
|
||||
|
||||
// The simplest.
|
||||
testCheck(c, check.Equals, true, "", 42, 42)
|
||||
testCheck(c, check.Equals, false, "", 42, 43)
|
||||
|
||||
// Different native types.
|
||||
testCheck(c, check.Equals, false, "", int32(42), int64(42))
|
||||
|
||||
// With nil.
|
||||
testCheck(c, check.Equals, false, "", 42, nil)
|
||||
|
||||
// Slices
|
||||
testCheck(c, check.Equals, false, "runtime error: comparing uncomparable type []uint8", []byte{1, 2}, []byte{1, 2})
|
||||
|
||||
// Struct values
|
||||
testCheck(c, check.Equals, true, "", simpleStruct{1}, simpleStruct{1})
|
||||
testCheck(c, check.Equals, false, "", simpleStruct{1}, simpleStruct{2})
|
||||
|
||||
// Struct pointers
|
||||
testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{1})
|
||||
testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{2})
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestDeepEquals(c *check.C) {
|
||||
testInfo(c, check.DeepEquals, "DeepEquals", []string{"obtained", "expected"})
|
||||
|
||||
// The simplest.
|
||||
testCheck(c, check.DeepEquals, true, "", 42, 42)
|
||||
testCheck(c, check.DeepEquals, false, "", 42, 43)
|
||||
|
||||
// Different native types.
|
||||
testCheck(c, check.DeepEquals, false, "", int32(42), int64(42))
|
||||
|
||||
// With nil.
|
||||
testCheck(c, check.DeepEquals, false, "", 42, nil)
|
||||
|
||||
// Slices
|
||||
testCheck(c, check.DeepEquals, true, "", []byte{1, 2}, []byte{1, 2})
|
||||
testCheck(c, check.DeepEquals, false, "", []byte{1, 2}, []byte{1, 3})
|
||||
|
||||
// Struct values
|
||||
testCheck(c, check.DeepEquals, true, "", simpleStruct{1}, simpleStruct{1})
|
||||
testCheck(c, check.DeepEquals, false, "", simpleStruct{1}, simpleStruct{2})
|
||||
|
||||
// Struct pointers
|
||||
testCheck(c, check.DeepEquals, true, "", &simpleStruct{1}, &simpleStruct{1})
|
||||
testCheck(c, check.DeepEquals, false, "", &simpleStruct{1}, &simpleStruct{2})
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestHasLen(c *check.C) {
|
||||
testInfo(c, check.HasLen, "HasLen", []string{"obtained", "n"})
|
||||
|
||||
testCheck(c, check.HasLen, true, "", "abcd", 4)
|
||||
testCheck(c, check.HasLen, true, "", []int{1, 2}, 2)
|
||||
testCheck(c, check.HasLen, false, "", []int{1, 2}, 3)
|
||||
|
||||
testCheck(c, check.HasLen, false, "n must be an int", []int{1, 2}, "2")
|
||||
testCheck(c, check.HasLen, false, "obtained value type has no length", nil, 2)
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestErrorMatches(c *check.C) {
|
||||
testInfo(c, check.ErrorMatches, "ErrorMatches", []string{"value", "regex"})
|
||||
|
||||
testCheck(c, check.ErrorMatches, false, "Error value is nil", nil, "some error")
|
||||
testCheck(c, check.ErrorMatches, false, "Value is not an error", 1, "some error")
|
||||
testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "some error")
|
||||
testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "so.*or")
|
||||
|
||||
// Verify params mutation
|
||||
params, names := testCheck(c, check.ErrorMatches, false, "", errors.New("some error"), "other error")
|
||||
c.Assert(params[0], check.Equals, "some error")
|
||||
c.Assert(names[0], check.Equals, "error")
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestMatches(c *check.C) {
|
||||
testInfo(c, check.Matches, "Matches", []string{"value", "regex"})
|
||||
|
||||
// Simple matching
|
||||
testCheck(c, check.Matches, true, "", "abc", "abc")
|
||||
testCheck(c, check.Matches, true, "", "abc", "a.c")
|
||||
|
||||
// Must match fully
|
||||
testCheck(c, check.Matches, false, "", "abc", "ab")
|
||||
testCheck(c, check.Matches, false, "", "abc", "bc")
|
||||
|
||||
// String()-enabled values accepted
|
||||
testCheck(c, check.Matches, true, "", reflect.ValueOf("abc"), "a.c")
|
||||
testCheck(c, check.Matches, false, "", reflect.ValueOf("abc"), "a.d")
|
||||
|
||||
// Some error conditions.
|
||||
testCheck(c, check.Matches, false, "Obtained value is not a string and has no .String()", 1, "a.c")
|
||||
testCheck(c, check.Matches, false, "Can't compile regex: error parsing regexp: missing closing ]: `[c$`", "abc", "a[c")
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestPanics(c *check.C) {
|
||||
testInfo(c, check.Panics, "Panics", []string{"function", "expected"})
|
||||
|
||||
// Some errors.
|
||||
testCheck(c, check.Panics, false, "Function has not panicked", func() bool { return false }, "BOOM")
|
||||
testCheck(c, check.Panics, false, "Function must take zero arguments", 1, "BOOM")
|
||||
|
||||
// Plain strings.
|
||||
testCheck(c, check.Panics, true, "", func() { panic("BOOM") }, "BOOM")
|
||||
testCheck(c, check.Panics, false, "", func() { panic("KABOOM") }, "BOOM")
|
||||
testCheck(c, check.Panics, true, "", func() bool { panic("BOOM") }, "BOOM")
|
||||
|
||||
// Error values.
|
||||
testCheck(c, check.Panics, true, "", func() { panic(errors.New("BOOM")) }, errors.New("BOOM"))
|
||||
testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
|
||||
|
||||
type deep struct{ i int }
|
||||
// Deep value
|
||||
testCheck(c, check.Panics, true, "", func() { panic(&deep{99}) }, &deep{99})
|
||||
|
||||
// Verify params/names mutation
|
||||
params, names := testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
|
||||
c.Assert(params[0], check.ErrorMatches, "KABOOM")
|
||||
c.Assert(names[0], check.Equals, "panic")
|
||||
|
||||
// Verify a nil panic
|
||||
testCheck(c, check.Panics, true, "", func() { panic(nil) }, nil)
|
||||
testCheck(c, check.Panics, false, "", func() { panic(nil) }, "NOPE")
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestPanicMatches(c *check.C) {
|
||||
testInfo(c, check.PanicMatches, "PanicMatches", []string{"function", "expected"})
|
||||
|
||||
// Error matching.
|
||||
testCheck(c, check.PanicMatches, true, "", func() { panic(errors.New("BOOM")) }, "BO.M")
|
||||
testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BO.M")
|
||||
|
||||
// Some errors.
|
||||
testCheck(c, check.PanicMatches, false, "Function has not panicked", func() bool { return false }, "BOOM")
|
||||
testCheck(c, check.PanicMatches, false, "Function must take zero arguments", 1, "BOOM")
|
||||
|
||||
// Plain strings.
|
||||
testCheck(c, check.PanicMatches, true, "", func() { panic("BOOM") }, "BO.M")
|
||||
testCheck(c, check.PanicMatches, false, "", func() { panic("KABOOM") }, "BOOM")
|
||||
testCheck(c, check.PanicMatches, true, "", func() bool { panic("BOOM") }, "BO.M")
|
||||
|
||||
// Verify params/names mutation
|
||||
params, names := testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BOOM")
|
||||
c.Assert(params[0], check.Equals, "KABOOM")
|
||||
c.Assert(names[0], check.Equals, "panic")
|
||||
|
||||
// Verify a nil panic
|
||||
testCheck(c, check.PanicMatches, false, "Panic value is not a string or an error", func() { panic(nil) }, "")
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestFitsTypeOf(c *check.C) {
|
||||
testInfo(c, check.FitsTypeOf, "FitsTypeOf", []string{"obtained", "sample"})
|
||||
|
||||
// Basic types
|
||||
testCheck(c, check.FitsTypeOf, true, "", 1, 0)
|
||||
testCheck(c, check.FitsTypeOf, false, "", 1, int64(0))
|
||||
|
||||
// Aliases
|
||||
testCheck(c, check.FitsTypeOf, false, "", 1, errors.New(""))
|
||||
testCheck(c, check.FitsTypeOf, false, "", "error", errors.New(""))
|
||||
testCheck(c, check.FitsTypeOf, true, "", errors.New("error"), errors.New(""))
|
||||
|
||||
// Structures
|
||||
testCheck(c, check.FitsTypeOf, false, "", 1, simpleStruct{})
|
||||
testCheck(c, check.FitsTypeOf, false, "", simpleStruct{42}, &simpleStruct{})
|
||||
testCheck(c, check.FitsTypeOf, true, "", simpleStruct{42}, simpleStruct{})
|
||||
testCheck(c, check.FitsTypeOf, true, "", &simpleStruct{42}, &simpleStruct{})
|
||||
|
||||
// Some bad values
|
||||
testCheck(c, check.FitsTypeOf, false, "Invalid sample value", 1, interface{}(nil))
|
||||
testCheck(c, check.FitsTypeOf, false, "", interface{}(nil), 0)
|
||||
}
|
||||
|
||||
func (s *CheckersS) TestImplements(c *check.C) {
|
||||
testInfo(c, check.Implements, "Implements", []string{"obtained", "ifaceptr"})
|
||||
|
||||
var e error
|
||||
var re runtime.Error
|
||||
testCheck(c, check.Implements, true, "", errors.New(""), &e)
|
||||
testCheck(c, check.Implements, false, "", errors.New(""), &re)
|
||||
|
||||
// Some bad values
|
||||
testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, errors.New(""))
|
||||
testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, interface{}(nil))
|
||||
testCheck(c, check.Implements, false, "", interface{}(nil), &e)
|
||||
}
|
||||
9
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/export_test.go
generated
vendored
Normal file
9
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/export_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package check
|
||||
|
||||
func PrintLine(filename string, line int) (string, error) {
|
||||
return printLine(filename, line)
|
||||
}
|
||||
|
||||
func Indent(s, with string) string {
|
||||
return indent(s, with)
|
||||
}
|
||||
484
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/fixture_test.go
generated
vendored
Normal file
484
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/fixture_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
// Tests for the behavior of the test fixture system.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Fixture test suite.
|
||||
|
||||
type FixtureS struct{}
|
||||
|
||||
var fixtureS = Suite(&FixtureS{})
|
||||
|
||||
func (s *FixtureS) TestCountSuite(c *C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Basic fixture ordering verification.
|
||||
|
||||
func (s *FixtureS) TestOrder(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
Run(&helper, nil)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Test2")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[7], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Check the behavior when panics occur within tests and fixtures.
|
||||
|
||||
func (s *FixtureS) TestPanicOnTest(c *C) {
|
||||
helper := FixtureHelper{panicOn: "Test1"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Test2")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[7], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: FixtureHelper.Test1\n\n" +
|
||||
"\\.\\.\\. Panic: Test1 \\(PC=[xA-F0-9]+\\)\n\n" +
|
||||
".+:[0-9]+\n" +
|
||||
" in (go)?panic\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.trace\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.Test1\n" +
|
||||
"(.|\n)*$"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnSetUpTest(c *C) {
|
||||
helper := FixtureHelper{panicOn: "SetUpTest"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[3], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 4)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper\\.SetUpTest\n\n" +
|
||||
"\\.\\.\\. Panic: SetUpTest \\(PC=[xA-F0-9]+\\)\n\n" +
|
||||
".+:[0-9]+\n" +
|
||||
" in (go)?panic\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.trace\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.SetUpTest\n" +
|
||||
"(.|\n)*" +
|
||||
"\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper\\.Test1\n\n" +
|
||||
"\\.\\.\\. Panic: Fixture has panicked " +
|
||||
"\\(see related PANIC\\)\n$"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnTearDownTest(c *C) {
|
||||
helper := FixtureHelper{panicOn: "TearDownTest"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 5)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper.TearDownTest\n\n" +
|
||||
"\\.\\.\\. Panic: TearDownTest \\(PC=[xA-F0-9]+\\)\n\n" +
|
||||
".+:[0-9]+\n" +
|
||||
" in (go)?panic\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.trace\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.TearDownTest\n" +
|
||||
"(.|\n)*" +
|
||||
"\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper\\.Test1\n\n" +
|
||||
"\\.\\.\\. Panic: Fixture has panicked " +
|
||||
"\\(see related PANIC\\)\n$"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnSetUpSuite(c *C) {
|
||||
helper := FixtureHelper{panicOn: "SetUpSuite"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 2)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper.SetUpSuite\n\n" +
|
||||
"\\.\\.\\. Panic: SetUpSuite \\(PC=[xA-F0-9]+\\)\n\n" +
|
||||
".+:[0-9]+\n" +
|
||||
" in (go)?panic\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.trace\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.SetUpSuite\n" +
|
||||
"(.|\n)*$"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnTearDownSuite(c *C) {
|
||||
helper := FixtureHelper{panicOn: "TearDownSuite"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Test2")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[7], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: check_test\\.go:[0-9]+: " +
|
||||
"FixtureHelper.TearDownSuite\n\n" +
|
||||
"\\.\\.\\. Panic: TearDownSuite \\(PC=[xA-F0-9]+\\)\n\n" +
|
||||
".+:[0-9]+\n" +
|
||||
" in (go)?panic\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.trace\n" +
|
||||
".*check_test.go:[0-9]+\n" +
|
||||
" in FixtureHelper.TearDownSuite\n" +
|
||||
"(.|\n)*$"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// A wrong argument on a test or fixture will produce a nice error.
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongTestArg(c *C) {
|
||||
helper := WrongTestArgHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[3], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[4], Equals, "Test2")
|
||||
c.Check(helper.calls[5], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[6], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 7)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongTestArgHelper\\.Test1\n\n" +
|
||||
"\\.\\.\\. Panic: WrongTestArgHelper\\.Test1 argument " +
|
||||
"should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongSetUpTestArg(c *C) {
|
||||
helper := WrongSetUpTestArgHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
|
||||
expected :=
|
||||
"^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongSetUpTestArgHelper\\.SetUpTest\n\n" +
|
||||
"\\.\\.\\. Panic: WrongSetUpTestArgHelper\\.SetUpTest argument " +
|
||||
"should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArg(c *C) {
|
||||
helper := WrongSetUpSuiteArgHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
|
||||
expected :=
|
||||
"^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongSetUpSuiteArgHelper\\.SetUpSuite\n\n" +
|
||||
"\\.\\.\\. Panic: WrongSetUpSuiteArgHelper\\.SetUpSuite argument " +
|
||||
"should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Nice errors also when tests or fixture have wrong arg count.
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongTestArgCount(c *C) {
|
||||
helper := WrongTestArgCountHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[3], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[4], Equals, "Test2")
|
||||
c.Check(helper.calls[5], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[6], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 7)
|
||||
|
||||
expected := "^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongTestArgCountHelper\\.Test1\n\n" +
|
||||
"\\.\\.\\. Panic: WrongTestArgCountHelper\\.Test1 argument " +
|
||||
"should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongSetUpTestArgCount(c *C) {
|
||||
helper := WrongSetUpTestArgCountHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
|
||||
expected :=
|
||||
"^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongSetUpTestArgCountHelper\\.SetUpTest\n\n" +
|
||||
"\\.\\.\\. Panic: WrongSetUpTestArgCountHelper\\.SetUpTest argument " +
|
||||
"should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArgCount(c *C) {
|
||||
helper := WrongSetUpSuiteArgCountHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
|
||||
expected :=
|
||||
"^\n-+\n" +
|
||||
"PANIC: fixture_test\\.go:[0-9]+: " +
|
||||
"WrongSetUpSuiteArgCountHelper\\.SetUpSuite\n\n" +
|
||||
"\\.\\.\\. Panic: WrongSetUpSuiteArgCountHelper" +
|
||||
"\\.SetUpSuite argument should be \\*check\\.C\n"
|
||||
|
||||
c.Check(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper test suites with wrong function arguments.
|
||||
|
||||
type WrongTestArgHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongTestArgHelper) Test1(t int) {
|
||||
}
|
||||
|
||||
type WrongSetUpTestArgHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongSetUpTestArgHelper) SetUpTest(t int) {
|
||||
}
|
||||
|
||||
type WrongSetUpSuiteArgHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongSetUpSuiteArgHelper) SetUpSuite(t int) {
|
||||
}
|
||||
|
||||
type WrongTestArgCountHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongTestArgCountHelper) Test1(c *C, i int) {
|
||||
}
|
||||
|
||||
type WrongSetUpTestArgCountHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongSetUpTestArgCountHelper) SetUpTest(c *C, i int) {
|
||||
}
|
||||
|
||||
type WrongSetUpSuiteArgCountHelper struct {
|
||||
FixtureHelper
|
||||
}
|
||||
|
||||
func (s *WrongSetUpSuiteArgCountHelper) SetUpSuite(c *C, i int) {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Ensure fixture doesn't run without tests.
|
||||
|
||||
type NoTestsHelper struct {
|
||||
hasRun bool
|
||||
}
|
||||
|
||||
func (s *NoTestsHelper) SetUpSuite(c *C) {
|
||||
s.hasRun = true
|
||||
}
|
||||
|
||||
func (s *NoTestsHelper) TearDownSuite(c *C) {
|
||||
s.hasRun = true
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestFixtureDoesntRunWithoutTests(c *C) {
|
||||
helper := NoTestsHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Check(helper.hasRun, Equals, false)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that checks and assertions work correctly inside the fixture.
|
||||
|
||||
type FixtureCheckHelper struct {
|
||||
fail string
|
||||
completed bool
|
||||
}
|
||||
|
||||
func (s *FixtureCheckHelper) SetUpSuite(c *C) {
|
||||
switch s.fail {
|
||||
case "SetUpSuiteAssert":
|
||||
c.Assert(false, Equals, true)
|
||||
case "SetUpSuiteCheck":
|
||||
c.Check(false, Equals, true)
|
||||
}
|
||||
s.completed = true
|
||||
}
|
||||
|
||||
func (s *FixtureCheckHelper) SetUpTest(c *C) {
|
||||
switch s.fail {
|
||||
case "SetUpTestAssert":
|
||||
c.Assert(false, Equals, true)
|
||||
case "SetUpTestCheck":
|
||||
c.Check(false, Equals, true)
|
||||
}
|
||||
s.completed = true
|
||||
}
|
||||
|
||||
func (s *FixtureCheckHelper) Test(c *C) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestSetUpSuiteCheck(c *C) {
|
||||
helper := FixtureCheckHelper{fail: "SetUpSuiteCheck"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Assert(output.value, Matches,
|
||||
"\n---+\n"+
|
||||
"FAIL: fixture_test\\.go:[0-9]+: "+
|
||||
"FixtureCheckHelper\\.SetUpSuite\n\n"+
|
||||
"fixture_test\\.go:[0-9]+:\n"+
|
||||
" c\\.Check\\(false, Equals, true\\)\n"+
|
||||
"\\.+ obtained bool = false\n"+
|
||||
"\\.+ expected bool = true\n\n")
|
||||
c.Assert(helper.completed, Equals, true)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestSetUpSuiteAssert(c *C) {
|
||||
helper := FixtureCheckHelper{fail: "SetUpSuiteAssert"}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Assert(output.value, Matches,
|
||||
"\n---+\n"+
|
||||
"FAIL: fixture_test\\.go:[0-9]+: "+
|
||||
"FixtureCheckHelper\\.SetUpSuite\n\n"+
|
||||
"fixture_test\\.go:[0-9]+:\n"+
|
||||
" c\\.Assert\\(false, Equals, true\\)\n"+
|
||||
"\\.+ obtained bool = false\n"+
|
||||
"\\.+ expected bool = true\n\n")
|
||||
c.Assert(helper.completed, Equals, false)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that logging within SetUpTest() persists within the test log itself.
|
||||
|
||||
type FixtureLogHelper struct {
|
||||
c *C
|
||||
}
|
||||
|
||||
func (s *FixtureLogHelper) SetUpTest(c *C) {
|
||||
s.c = c
|
||||
c.Log("1")
|
||||
}
|
||||
|
||||
func (s *FixtureLogHelper) Test(c *C) {
|
||||
c.Log("2")
|
||||
s.c.Log("3")
|
||||
c.Log("4")
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
func (s *FixtureLogHelper) TearDownTest(c *C) {
|
||||
s.c.Log("5")
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestFixtureLogging(c *C) {
|
||||
helper := FixtureLogHelper{}
|
||||
output := String{}
|
||||
Run(&helper, &RunConf{Output: &output})
|
||||
c.Assert(output.value, Matches,
|
||||
"\n---+\n"+
|
||||
"FAIL: fixture_test\\.go:[0-9]+: "+
|
||||
"FixtureLogHelper\\.Test\n\n"+
|
||||
"1\n2\n3\n4\n5\n")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Skip() within fixture methods.
|
||||
|
||||
func (s *FixtureS) TestSkipSuite(c *C) {
|
||||
helper := FixtureHelper{skip: true, skipOnN: 0}
|
||||
output := String{}
|
||||
result := Run(&helper, &RunConf{Output: &output})
|
||||
c.Assert(output.value, Equals, "")
|
||||
c.Assert(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Assert(helper.calls[1], Equals, "TearDownSuite")
|
||||
c.Assert(len(helper.calls), Equals, 2)
|
||||
c.Assert(result.Skipped, Equals, 2)
|
||||
}
|
||||
|
||||
func (s *FixtureS) TestSkipTest(c *C) {
|
||||
helper := FixtureHelper{skip: true, skipOnN: 1}
|
||||
output := String{}
|
||||
result := Run(&helper, &RunConf{Output: &output})
|
||||
c.Assert(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Assert(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Assert(helper.calls[2], Equals, "SetUpTest")
|
||||
c.Assert(helper.calls[3], Equals, "Test2")
|
||||
c.Assert(helper.calls[4], Equals, "TearDownTest")
|
||||
c.Assert(helper.calls[5], Equals, "TearDownSuite")
|
||||
c.Assert(len(helper.calls), Equals, 6)
|
||||
c.Assert(result.Skipped, Equals, 1)
|
||||
}
|
||||
335
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/foundation_test.go
generated
vendored
Normal file
335
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/foundation_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
// These tests check that the foundations of gocheck are working properly.
|
||||
// They already assume that fundamental failing is working already, though,
|
||||
// since this was tested in bootstrap_test.go. Even then, some care may
|
||||
// still have to be taken when using external functions, since they should
|
||||
// of course not rely on functionality tested here.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Foundation test suite.
|
||||
|
||||
type FoundationS struct{}
|
||||
|
||||
var foundationS = check.Suite(&FoundationS{})
|
||||
|
||||
func (s *FoundationS) TestCountSuite(c *check.C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestErrorf(c *check.C) {
|
||||
// Do not use checkState() here. It depends on Errorf() working.
|
||||
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
|
||||
" c.Errorf(\"Error %%v!\", \"message\")\n"+
|
||||
"... Error: Error message!\n\n",
|
||||
getMyLine()+1)
|
||||
c.Errorf("Error %v!", "message")
|
||||
failed := c.Failed()
|
||||
c.Succeed()
|
||||
if log := c.GetTestLog(); log != expectedLog {
|
||||
c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog)
|
||||
c.Fail()
|
||||
}
|
||||
if !failed {
|
||||
c.Logf("Errorf() didn't put the test in a failed state")
|
||||
c.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestError(c *check.C) {
|
||||
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
|
||||
" c\\.Error\\(\"Error \", \"message!\"\\)\n"+
|
||||
"\\.\\.\\. Error: Error message!\n\n",
|
||||
getMyLine()+1)
|
||||
c.Error("Error ", "message!")
|
||||
checkState(c, nil,
|
||||
&expectedState{
|
||||
name: "Error(`Error `, `message!`)",
|
||||
failed: true,
|
||||
log: expectedLog,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestFailNow(c *check.C) {
|
||||
defer (func() {
|
||||
if !c.Failed() {
|
||||
c.Error("FailNow() didn't fail the test")
|
||||
} else {
|
||||
c.Succeed()
|
||||
if c.GetTestLog() != "" {
|
||||
c.Error("Something got logged:\n" + c.GetTestLog())
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
c.FailNow()
|
||||
c.Log("FailNow() didn't stop the test")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestSucceedNow(c *check.C) {
|
||||
defer (func() {
|
||||
if c.Failed() {
|
||||
c.Error("SucceedNow() didn't succeed the test")
|
||||
}
|
||||
if c.GetTestLog() != "" {
|
||||
c.Error("Something got logged:\n" + c.GetTestLog())
|
||||
}
|
||||
})()
|
||||
|
||||
c.Fail()
|
||||
c.SucceedNow()
|
||||
c.Log("SucceedNow() didn't stop the test")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestFailureHeader(c *check.C) {
|
||||
output := String{}
|
||||
failHelper := FailHelper{}
|
||||
check.Run(&failHelper, &check.RunConf{Output: &output})
|
||||
header := fmt.Sprintf(""+
|
||||
"\n-----------------------------------"+
|
||||
"-----------------------------------\n"+
|
||||
"FAIL: check_test.go:%d: FailHelper.TestLogAndFail\n",
|
||||
failHelper.testLine)
|
||||
if strings.Index(output.value, header) == -1 {
|
||||
c.Errorf(""+
|
||||
"Failure didn't print a proper header.\n"+
|
||||
"... Got:\n%s... Expected something with:\n%s",
|
||||
output.value, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestFatal(c *check.C) {
|
||||
var line int
|
||||
defer (func() {
|
||||
if !c.Failed() {
|
||||
c.Error("Fatal() didn't fail the test")
|
||||
} else {
|
||||
c.Succeed()
|
||||
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
|
||||
" c.Fatal(\"Die \", \"now!\")\n"+
|
||||
"... Error: Die now!\n\n",
|
||||
line)
|
||||
if c.GetTestLog() != expected {
|
||||
c.Error("Incorrect log:", c.GetTestLog())
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
line = getMyLine() + 1
|
||||
c.Fatal("Die ", "now!")
|
||||
c.Log("Fatal() didn't stop the test")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestFatalf(c *check.C) {
|
||||
var line int
|
||||
defer (func() {
|
||||
if !c.Failed() {
|
||||
c.Error("Fatalf() didn't fail the test")
|
||||
} else {
|
||||
c.Succeed()
|
||||
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
|
||||
" c.Fatalf(\"Die %%s!\", \"now\")\n"+
|
||||
"... Error: Die now!\n\n",
|
||||
line)
|
||||
if c.GetTestLog() != expected {
|
||||
c.Error("Incorrect log:", c.GetTestLog())
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
line = getMyLine() + 1
|
||||
c.Fatalf("Die %s!", "now")
|
||||
c.Log("Fatalf() didn't stop the test")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestCallerLoggingInsideTest(c *check.C) {
|
||||
log := fmt.Sprintf(""+
|
||||
"foundation_test.go:%d:\n"+
|
||||
" result := c.Check\\(10, check.Equals, 20\\)\n"+
|
||||
"\\.\\.\\. obtained int = 10\n"+
|
||||
"\\.\\.\\. expected int = 20\n\n",
|
||||
getMyLine()+1)
|
||||
result := c.Check(10, check.Equals, 20)
|
||||
checkState(c, result,
|
||||
&expectedState{
|
||||
name: "Check(10, Equals, 20)",
|
||||
result: false,
|
||||
failed: true,
|
||||
log: log,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestCallerLoggingInDifferentFile(c *check.C) {
|
||||
result, line := checkEqualWrapper(c, 10, 20)
|
||||
testLine := getMyLine() - 1
|
||||
log := fmt.Sprintf(""+
|
||||
"foundation_test.go:%d:\n"+
|
||||
" result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+
|
||||
"check_test.go:%d:\n"+
|
||||
" return c.Check\\(obtained, check.Equals, expected\\), getMyLine\\(\\)\n"+
|
||||
"\\.\\.\\. obtained int = 10\n"+
|
||||
"\\.\\.\\. expected int = 20\n\n",
|
||||
testLine, line)
|
||||
checkState(c, result,
|
||||
&expectedState{
|
||||
name: "Check(10, Equals, 20)",
|
||||
result: false,
|
||||
failed: true,
|
||||
log: log,
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpectFailure() inverts the logic of failure.
|
||||
|
||||
type ExpectFailureSucceedHelper struct{}
|
||||
|
||||
func (s *ExpectFailureSucceedHelper) TestSucceed(c *check.C) {
|
||||
c.ExpectFailure("It booms!")
|
||||
c.Error("Boom!")
|
||||
}
|
||||
|
||||
type ExpectFailureFailHelper struct{}
|
||||
|
||||
func (s *ExpectFailureFailHelper) TestFail(c *check.C) {
|
||||
c.ExpectFailure("Bug #XYZ")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestExpectFailureFail(c *check.C) {
|
||||
helper := ExpectFailureFailHelper{}
|
||||
output := String{}
|
||||
result := check.Run(&helper, &check.RunConf{Output: &output})
|
||||
|
||||
expected := "" +
|
||||
"^\n-+\n" +
|
||||
"FAIL: foundation_test\\.go:[0-9]+:" +
|
||||
" ExpectFailureFailHelper\\.TestFail\n\n" +
|
||||
"\\.\\.\\. Error: Test succeeded, but was expected to fail\n" +
|
||||
"\\.\\.\\. Reason: Bug #XYZ\n$"
|
||||
|
||||
matched, err := regexp.MatchString(expected, output.value)
|
||||
if err != nil {
|
||||
c.Error("Bad expression: ", expected)
|
||||
} else if !matched {
|
||||
c.Error("ExpectFailure() didn't log properly:\n", output.value)
|
||||
}
|
||||
|
||||
c.Assert(result.ExpectedFailures, check.Equals, 0)
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestExpectFailureSucceed(c *check.C) {
|
||||
helper := ExpectFailureSucceedHelper{}
|
||||
output := String{}
|
||||
result := check.Run(&helper, &check.RunConf{Output: &output})
|
||||
|
||||
c.Assert(output.value, check.Equals, "")
|
||||
c.Assert(result.ExpectedFailures, check.Equals, 1)
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestExpectFailureSucceedVerbose(c *check.C) {
|
||||
helper := ExpectFailureSucceedHelper{}
|
||||
output := String{}
|
||||
result := check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})
|
||||
|
||||
expected := "" +
|
||||
"FAIL EXPECTED: foundation_test\\.go:[0-9]+:" +
|
||||
" ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n"
|
||||
|
||||
matched, err := regexp.MatchString(expected, output.value)
|
||||
if err != nil {
|
||||
c.Error("Bad expression: ", expected)
|
||||
} else if !matched {
|
||||
c.Error("ExpectFailure() didn't log properly:\n", output.value)
|
||||
}
|
||||
|
||||
c.Assert(result.ExpectedFailures, check.Equals, 1)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Skip() allows stopping a test without positive/negative results.
|
||||
|
||||
type SkipTestHelper struct{}
|
||||
|
||||
func (s *SkipTestHelper) TestFail(c *check.C) {
|
||||
c.Skip("Wrong platform or whatever")
|
||||
c.Error("Boom!")
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestSkip(c *check.C) {
|
||||
helper := SkipTestHelper{}
|
||||
output := String{}
|
||||
check.Run(&helper, &check.RunConf{Output: &output})
|
||||
|
||||
if output.value != "" {
|
||||
c.Error("Skip() logged something:\n", output.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FoundationS) TestSkipVerbose(c *check.C) {
|
||||
helper := SkipTestHelper{}
|
||||
output := String{}
|
||||
check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})
|
||||
|
||||
expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" +
|
||||
" \\(Wrong platform or whatever\\)"
|
||||
matched, err := regexp.MatchString(expected, output.value)
|
||||
if err != nil {
|
||||
c.Error("Bad expression: ", expected)
|
||||
} else if !matched {
|
||||
c.Error("Skip() didn't log properly:\n", output.value)
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Check minimum *log.Logger interface provided by *check.C.
|
||||
|
||||
type minLogger interface {
|
||||
Output(calldepth int, s string) error
|
||||
}
|
||||
|
||||
func (s *BootstrapS) TestMinLogger(c *check.C) {
|
||||
var logger minLogger
|
||||
logger = log.New(os.Stderr, "", 0)
|
||||
logger = c
|
||||
logger.Output(0, "Hello there")
|
||||
expected := `\[LOG\] [0-9]+:[0-9][0-9]\.[0-9][0-9][0-9] +Hello there\n`
|
||||
output := c.GetTestLog()
|
||||
c.Assert(output, check.Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Ensure that suites with embedded types are working fine, including the
|
||||
// the workaround for issue 906.
|
||||
|
||||
type EmbeddedInternalS struct {
|
||||
called bool
|
||||
}
|
||||
|
||||
type EmbeddedS struct {
|
||||
EmbeddedInternalS
|
||||
}
|
||||
|
||||
var embeddedS = check.Suite(&EmbeddedS{})
|
||||
|
||||
func (s *EmbeddedS) TestCountSuite(c *check.C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
func (s *EmbeddedInternalS) TestMethod(c *check.C) {
|
||||
c.Error("TestMethod() of the embedded type was called!?")
|
||||
}
|
||||
|
||||
func (s *EmbeddedS) TestMethod(c *check.C) {
|
||||
// http://code.google.com/p/go/issues/detail?id=906
|
||||
c.Check(s.called, check.Equals, false) // Go issue 906 is affecting the runner?
|
||||
s.called = true
|
||||
}
|
||||
231
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/helpers.go
generated
vendored
Normal file
231
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/helpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
package check
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestName returns the current test name in the form "SuiteName.TestName"
|
||||
func (c *C) TestName() string {
|
||||
return c.testName
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Basic succeeding/failing logic.
|
||||
|
||||
// Failed returns whether the currently running test has already failed.
|
||||
func (c *C) Failed() bool {
|
||||
return c.status == failedSt
|
||||
}
|
||||
|
||||
// Fail marks the currently running test as failed.
|
||||
//
|
||||
// Something ought to have been previously logged so the developer can tell
|
||||
// what went wrong. The higher level helper functions will fail the test
|
||||
// and do the logging properly.
|
||||
func (c *C) Fail() {
|
||||
c.status = failedSt
|
||||
}
|
||||
|
||||
// FailNow marks the currently running test as failed and stops running it.
|
||||
// Something ought to have been previously logged so the developer can tell
|
||||
// what went wrong. The higher level helper functions will fail the test
|
||||
// and do the logging properly.
|
||||
func (c *C) FailNow() {
|
||||
c.Fail()
|
||||
c.stopNow()
|
||||
}
|
||||
|
||||
// Succeed marks the currently running test as succeeded, undoing any
|
||||
// previous failures.
|
||||
func (c *C) Succeed() {
|
||||
c.status = succeededSt
|
||||
}
|
||||
|
||||
// SucceedNow marks the currently running test as succeeded, undoing any
|
||||
// previous failures, and stops running the test.
|
||||
func (c *C) SucceedNow() {
|
||||
c.Succeed()
|
||||
c.stopNow()
|
||||
}
|
||||
|
||||
// ExpectFailure informs that the running test is knowingly broken for
|
||||
// the provided reason. If the test does not fail, an error will be reported
|
||||
// to raise attention to this fact. This method is useful to temporarily
|
||||
// disable tests which cover well known problems until a better time to
|
||||
// fix the problem is found, without forgetting about the fact that a
|
||||
// failure still exists.
|
||||
func (c *C) ExpectFailure(reason string) {
|
||||
if reason == "" {
|
||||
panic("Missing reason why the test is expected to fail")
|
||||
}
|
||||
c.mustFail = true
|
||||
c.reason = reason
|
||||
}
|
||||
|
||||
// Skip skips the running test for the provided reason. If run from within
|
||||
// SetUpTest, the individual test being set up will be skipped, and if run
|
||||
// from within SetUpSuite, the whole suite is skipped.
|
||||
func (c *C) Skip(reason string) {
|
||||
if reason == "" {
|
||||
panic("Missing reason why the test is being skipped")
|
||||
}
|
||||
c.reason = reason
|
||||
c.status = skippedSt
|
||||
c.stopNow()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Basic logging.
|
||||
|
||||
// GetTestLog returns the current test error output.
|
||||
func (c *C) GetTestLog() string {
|
||||
return c.logb.String()
|
||||
}
|
||||
|
||||
// Log logs some information into the test error output.
|
||||
// The provided arguments are assembled together into a string with fmt.Sprint.
|
||||
func (c *C) Log(args ...interface{}) {
|
||||
c.log(args...)
|
||||
}
|
||||
|
||||
// Log logs some information into the test error output.
|
||||
// The provided arguments are assembled together into a string with fmt.Sprintf.
|
||||
func (c *C) Logf(format string, args ...interface{}) {
|
||||
c.logf(format, args...)
|
||||
}
|
||||
|
||||
// Output enables *C to be used as a logger in functions that require only
|
||||
// the minimum interface of *log.Logger.
|
||||
func (c *C) Output(calldepth int, s string) error {
|
||||
d := time.Now().Sub(c.startTime)
|
||||
msec := d / time.Millisecond
|
||||
sec := d / time.Second
|
||||
min := d / time.Minute
|
||||
|
||||
c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error logs an error into the test error output and marks the test as failed.
|
||||
// The provided arguments are assembled together into a string with fmt.Sprint.
|
||||
func (c *C) Error(args ...interface{}) {
|
||||
c.logCaller(1)
|
||||
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
|
||||
c.logNewLine()
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
// Errorf logs an error into the test error output and marks the test as failed.
|
||||
// The provided arguments are assembled together into a string with fmt.Sprintf.
|
||||
func (c *C) Errorf(format string, args ...interface{}) {
|
||||
c.logCaller(1)
|
||||
c.logString(fmt.Sprintf("Error: "+format, args...))
|
||||
c.logNewLine()
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
// Fatal logs an error into the test error output, marks the test as failed, and
|
||||
// stops the test execution. The provided arguments are assembled together into
|
||||
// a string with fmt.Sprint.
|
||||
func (c *C) Fatal(args ...interface{}) {
|
||||
c.logCaller(1)
|
||||
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
|
||||
c.logNewLine()
|
||||
c.FailNow()
|
||||
}
|
||||
|
||||
// Fatlaf logs an error into the test error output, marks the test as failed, and
|
||||
// stops the test execution. The provided arguments are assembled together into
|
||||
// a string with fmt.Sprintf.
|
||||
func (c *C) Fatalf(format string, args ...interface{}) {
|
||||
c.logCaller(1)
|
||||
c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...)))
|
||||
c.logNewLine()
|
||||
c.FailNow()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Generic checks and assertions based on checkers.
|
||||
|
||||
// Check verifies if the first value matches the expected value according
|
||||
// to the provided checker. If they do not match, an error is logged, the
|
||||
// test is marked as failed, and the test execution continues.
|
||||
//
|
||||
// Some checkers may not need the expected argument (e.g. IsNil).
|
||||
//
|
||||
// Extra arguments provided to the function are logged next to the reported
|
||||
// problem when the matching fails.
|
||||
func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool {
|
||||
return c.internalCheck("Check", obtained, checker, args...)
|
||||
}
|
||||
|
||||
// Assert ensures that the first value matches the expected value according
|
||||
// to the provided checker. If they do not match, an error is logged, the
|
||||
// test is marked as failed, and the test execution stops.
|
||||
//
|
||||
// Some checkers may not need the expected argument (e.g. IsNil).
|
||||
//
|
||||
// Extra arguments provided to the function are logged next to the reported
|
||||
// problem when the matching fails.
|
||||
func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) {
|
||||
if !c.internalCheck("Assert", obtained, checker, args...) {
|
||||
c.stopNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool {
|
||||
if checker == nil {
|
||||
c.logCaller(2)
|
||||
c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName))
|
||||
c.logString("Oops.. you've provided a nil checker!")
|
||||
c.logNewLine()
|
||||
c.Fail()
|
||||
return false
|
||||
}
|
||||
|
||||
// If the last argument is a bug info, extract it out.
|
||||
var comment CommentInterface
|
||||
if len(args) > 0 {
|
||||
if c, ok := args[len(args)-1].(CommentInterface); ok {
|
||||
comment = c
|
||||
args = args[:len(args)-1]
|
||||
}
|
||||
}
|
||||
|
||||
params := append([]interface{}{obtained}, args...)
|
||||
info := checker.Info()
|
||||
|
||||
if len(params) != len(info.Params) {
|
||||
names := append([]string{info.Params[0], info.Name}, info.Params[1:]...)
|
||||
c.logCaller(2)
|
||||
c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", ")))
|
||||
c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1))
|
||||
c.logNewLine()
|
||||
c.Fail()
|
||||
return false
|
||||
}
|
||||
|
||||
// Copy since it may be mutated by Check.
|
||||
names := append([]string{}, info.Params...)
|
||||
|
||||
// Do the actual check.
|
||||
result, error := checker.Check(params, names)
|
||||
if !result || error != "" {
|
||||
c.logCaller(2)
|
||||
for i := 0; i != len(params); i++ {
|
||||
c.logValue(names[i], params[i])
|
||||
}
|
||||
if comment != nil {
|
||||
c.logString(comment.CheckCommentString())
|
||||
}
|
||||
if error != "" {
|
||||
c.logString(error)
|
||||
}
|
||||
c.logNewLine()
|
||||
c.Fail()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
519
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/helpers_test.go
generated
vendored
Normal file
519
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/helpers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
// These tests verify the inner workings of the helper methods associated
|
||||
// with check.T.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var helpersS = check.Suite(&HelpersS{})
|
||||
|
||||
type HelpersS struct{}
|
||||
|
||||
func (s *HelpersS) TestCountSuite(c *check.C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Fake checker and bug info to verify the behavior of Assert() and Check().
|
||||
|
||||
type MyChecker struct {
|
||||
info *check.CheckerInfo
|
||||
params []interface{}
|
||||
names []string
|
||||
result bool
|
||||
error string
|
||||
}
|
||||
|
||||
func (checker *MyChecker) Info() *check.CheckerInfo {
|
||||
if checker.info == nil {
|
||||
return &check.CheckerInfo{Name: "MyChecker", Params: []string{"myobtained", "myexpected"}}
|
||||
}
|
||||
return checker.info
|
||||
}
|
||||
|
||||
func (checker *MyChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
rparams := checker.params
|
||||
rnames := checker.names
|
||||
checker.params = append([]interface{}{}, params...)
|
||||
checker.names = append([]string{}, names...)
|
||||
if rparams != nil {
|
||||
copy(params, rparams)
|
||||
}
|
||||
if rnames != nil {
|
||||
copy(names, rnames)
|
||||
}
|
||||
return checker.result, checker.error
|
||||
}
|
||||
|
||||
type myCommentType string
|
||||
|
||||
func (c myCommentType) CheckCommentString() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func myComment(s string) myCommentType {
|
||||
return myCommentType(s)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Ensure a real checker actually works fine.
|
||||
|
||||
func (s *HelpersS) TestCheckerInterface(c *check.C) {
|
||||
testHelperSuccess(c, "Check(1, Equals, 1)", true, func() interface{} {
|
||||
return c.Check(1, check.Equals, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tests for Check(), mostly the same as for Assert() following these.
|
||||
|
||||
func (s *HelpersS) TestCheckSucceedWithExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true}
|
||||
testHelperSuccess(c, "Check(1, checker, 2)", true, func() interface{} {
|
||||
return c.Check(1, checker, 2)
|
||||
})
|
||||
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
|
||||
c.Fatalf("Bad params for check: %#v", checker.params)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckSucceedWithoutExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
testHelperSuccess(c, "Check(1, checker)", true, func() interface{} {
|
||||
return c.Check(1, checker)
|
||||
})
|
||||
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
|
||||
c.Fatalf("Bad params for check: %#v", checker.params)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckFailWithExpected(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, 2\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckFailWithExpectedAndComment(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n" +
|
||||
"\\.+ Hello world!\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, 2, myComment("Hello world!"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckFailWithExpectedAndStaticComment(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" // Nice leading comment\\.\n" +
|
||||
" return c\\.Check\\(1, checker, 2\\) // Hello there\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
|
||||
func() interface{} {
|
||||
// Nice leading comment.
|
||||
return c.Check(1, checker, 2) // Hello there
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckFailWithoutExpected(c *check.C) {
|
||||
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker\\)\n" +
|
||||
"\\.+ myvalue int = 1\n\n"
|
||||
testHelperFailure(c, "Check(1, checker)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckFailWithoutExpectedAndMessage(c *check.C) {
|
||||
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
|
||||
"\\.+ myvalue int = 1\n" +
|
||||
"\\.+ Hello world!\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, msg)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, myComment("Hello world!"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckWithMissingExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker\\)\n" +
|
||||
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
|
||||
"\\.+ Wrong number of parameters for MyChecker: " +
|
||||
"want 3, got 2\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, !?)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckWithTooManyExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, 2, 3\\)\n" +
|
||||
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
|
||||
"\\.+ Wrong number of parameters for MyChecker: " +
|
||||
"want 3, got 4\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2, 3)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, 2, 3)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckWithError(c *check.C) {
|
||||
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, 2\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n" +
|
||||
"\\.+ Some not so cool data provided!\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckWithNilChecker(c *check.C) {
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, nil\\)\n" +
|
||||
"\\.+ Check\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
|
||||
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
|
||||
testHelperFailure(c, "Check(obtained, nil)", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestCheckWithParamsAndNamesMutation(c *check.C) {
|
||||
checker := &MyChecker{result: false, params: []interface{}{3, 4}, names: []string{"newobtained", "newexpected"}}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(1, checker, 2\\)\n" +
|
||||
"\\.+ newobtained int = 3\n" +
|
||||
"\\.+ newexpected int = 4\n\n"
|
||||
testHelperFailure(c, "Check(1, checker, 2) with mutation", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check(1, checker, 2)
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tests for Assert(), mostly the same as for Check() above.
|
||||
|
||||
func (s *HelpersS) TestAssertSucceedWithExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true}
|
||||
testHelperSuccess(c, "Assert(1, checker, 2)", nil, func() interface{} {
|
||||
c.Assert(1, checker, 2)
|
||||
return nil
|
||||
})
|
||||
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
|
||||
c.Fatalf("Bad params for check: %#v", checker.params)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertSucceedWithoutExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
testHelperSuccess(c, "Assert(1, checker)", nil, func() interface{} {
|
||||
c.Assert(1, checker)
|
||||
return nil
|
||||
})
|
||||
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
|
||||
c.Fatalf("Bad params for check: %#v", checker.params)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertFailWithExpected(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker, 2\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker, 2)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertFailWithExpectedAndMessage(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n" +
|
||||
"\\.+ Hello world!\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker, 2, msg)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker, 2, myComment("Hello world!"))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertFailWithoutExpected(c *check.C) {
|
||||
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker\\)\n" +
|
||||
"\\.+ myvalue int = 1\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertFailWithoutExpectedAndMessage(c *check.C) {
|
||||
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
|
||||
"\\.+ myvalue int = 1\n" +
|
||||
"\\.+ Hello world!\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker, msg)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker, myComment("Hello world!"))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertWithMissingExpected(c *check.C) {
|
||||
checker := &MyChecker{result: true}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker\\)\n" +
|
||||
"\\.+ Assert\\(myobtained, MyChecker, myexpected\\):\n" +
|
||||
"\\.+ Wrong number of parameters for MyChecker: " +
|
||||
"want 3, got 2\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker, !?)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertWithError(c *check.C) {
|
||||
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, checker, 2\\)\n" +
|
||||
"\\.+ myobtained int = 1\n" +
|
||||
"\\.+ myexpected int = 2\n" +
|
||||
"\\.+ Some not so cool data provided!\n\n"
|
||||
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, checker, 2)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestAssertWithNilChecker(c *check.C) {
|
||||
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
|
||||
" c\\.Assert\\(1, nil\\)\n" +
|
||||
"\\.+ Assert\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
|
||||
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
|
||||
testHelperFailure(c, "Assert(obtained, nil)", nil, true, log,
|
||||
func() interface{} {
|
||||
c.Assert(1, nil)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Ensure that values logged work properly in some interesting cases.
|
||||
|
||||
func (s *HelpersS) TestValueLoggingWithArrays(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(\\[\\]byte{1, 2}, checker, \\[\\]byte{1, 3}\\)\n" +
|
||||
"\\.+ myobtained \\[\\]uint8 = \\[\\]byte{0x1, 0x2}\n" +
|
||||
"\\.+ myexpected \\[\\]uint8 = \\[\\]byte{0x1, 0x3}\n\n"
|
||||
testHelperFailure(c, "Check([]byte{1}, chk, []byte{3})", false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check([]byte{1, 2}, checker, []byte{1, 3})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestValueLoggingWithMultiLine(c *check.C) {
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(\"a\\\\nb\\\\n\", checker, \"a\\\\nb\\\\nc\"\\)\n" +
|
||||
"\\.+ myobtained string = \"\" \\+\n" +
|
||||
"\\.+ \"a\\\\n\" \\+\n" +
|
||||
"\\.+ \"b\\\\n\"\n" +
|
||||
"\\.+ myexpected string = \"\" \\+\n" +
|
||||
"\\.+ \"a\\\\n\" \\+\n" +
|
||||
"\\.+ \"b\\\\n\" \\+\n" +
|
||||
"\\.+ \"c\"\n\n"
|
||||
testHelperFailure(c, `Check("a\nb\n", chk, "a\nb\nc")`, false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check("a\nb\n", checker, "a\nb\nc")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestValueLoggingWithMultiLineException(c *check.C) {
|
||||
// If the newline is at the end of the string, don't log as multi-line.
|
||||
checker := &MyChecker{result: false}
|
||||
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
|
||||
" return c\\.Check\\(\"a b\\\\n\", checker, \"a\\\\nb\"\\)\n" +
|
||||
"\\.+ myobtained string = \"a b\\\\n\"\n" +
|
||||
"\\.+ myexpected string = \"\" \\+\n" +
|
||||
"\\.+ \"a\\\\n\" \\+\n" +
|
||||
"\\.+ \"b\"\n\n"
|
||||
testHelperFailure(c, `Check("a b\n", chk, "a\nb")`, false, false, log,
|
||||
func() interface{} {
|
||||
return c.Check("a b\n", checker, "a\nb")
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// MakeDir() tests.
|
||||
|
||||
type MkDirHelper struct {
|
||||
path1 string
|
||||
path2 string
|
||||
isDir1 bool
|
||||
isDir2 bool
|
||||
isDir3 bool
|
||||
isDir4 bool
|
||||
}
|
||||
|
||||
func (s *MkDirHelper) SetUpSuite(c *check.C) {
|
||||
s.path1 = c.MkDir()
|
||||
s.isDir1 = isDir(s.path1)
|
||||
}
|
||||
|
||||
func (s *MkDirHelper) Test(c *check.C) {
|
||||
s.path2 = c.MkDir()
|
||||
s.isDir2 = isDir(s.path2)
|
||||
}
|
||||
|
||||
func (s *MkDirHelper) TearDownSuite(c *check.C) {
|
||||
s.isDir3 = isDir(s.path1)
|
||||
s.isDir4 = isDir(s.path2)
|
||||
}
|
||||
|
||||
func (s *HelpersS) TestMkDir(c *check.C) {
|
||||
helper := MkDirHelper{}
|
||||
output := String{}
|
||||
check.Run(&helper, &check.RunConf{Output: &output})
|
||||
c.Assert(output.value, check.Equals, "")
|
||||
c.Check(helper.isDir1, check.Equals, true)
|
||||
c.Check(helper.isDir2, check.Equals, true)
|
||||
c.Check(helper.isDir3, check.Equals, true)
|
||||
c.Check(helper.isDir4, check.Equals, true)
|
||||
c.Check(helper.path1, check.Not(check.Equals),
|
||||
helper.path2)
|
||||
c.Check(isDir(helper.path1), check.Equals, false)
|
||||
c.Check(isDir(helper.path2), check.Equals, false)
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
if stat, err := os.Stat(path); err == nil {
|
||||
return stat.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Concurrent logging should not corrupt the underling buffer.
|
||||
// Use go test -race to detect the race in this test.
|
||||
func (s *HelpersS) TestConcurrentLogging(c *check.C) {
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
|
||||
var start, stop sync.WaitGroup
|
||||
start.Add(1)
|
||||
for i, n := 0, runtime.NumCPU()*2; i < n; i++ {
|
||||
stop.Add(1)
|
||||
go func(i int) {
|
||||
start.Wait()
|
||||
for j := 0; j < 30; j++ {
|
||||
c.Logf("Worker %d: line %d", i, j)
|
||||
}
|
||||
stop.Done()
|
||||
}(i)
|
||||
}
|
||||
start.Done()
|
||||
stop.Wait()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test the TestName function
|
||||
|
||||
type TestNameHelper struct {
|
||||
name1 string
|
||||
name2 string
|
||||
name3 string
|
||||
name4 string
|
||||
name5 string
|
||||
}
|
||||
|
||||
func (s *TestNameHelper) SetUpSuite(c *check.C) { s.name1 = c.TestName() }
|
||||
func (s *TestNameHelper) SetUpTest(c *check.C) { s.name2 = c.TestName() }
|
||||
func (s *TestNameHelper) Test(c *check.C) { s.name3 = c.TestName() }
|
||||
func (s *TestNameHelper) TearDownTest(c *check.C) { s.name4 = c.TestName() }
|
||||
func (s *TestNameHelper) TearDownSuite(c *check.C) { s.name5 = c.TestName() }
|
||||
|
||||
func (s *HelpersS) TestTestName(c *check.C) {
|
||||
helper := TestNameHelper{}
|
||||
output := String{}
|
||||
check.Run(&helper, &check.RunConf{Output: &output})
|
||||
c.Check(helper.name1, check.Equals, "")
|
||||
c.Check(helper.name2, check.Equals, "TestNameHelper.Test")
|
||||
c.Check(helper.name3, check.Equals, "TestNameHelper.Test")
|
||||
c.Check(helper.name4, check.Equals, "TestNameHelper.Test")
|
||||
c.Check(helper.name5, check.Equals, "")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// A couple of helper functions to test helper functions. :-)
|
||||
|
||||
func testHelperSuccess(c *check.C, name string, expectedResult interface{}, closure func() interface{}) {
|
||||
var result interface{}
|
||||
defer (func() {
|
||||
if err := recover(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
checkState(c, result,
|
||||
&expectedState{
|
||||
name: name,
|
||||
result: expectedResult,
|
||||
failed: false,
|
||||
log: "",
|
||||
})
|
||||
})()
|
||||
result = closure()
|
||||
}
|
||||
|
||||
func testHelperFailure(c *check.C, name string, expectedResult interface{}, shouldStop bool, log string, closure func() interface{}) {
|
||||
var result interface{}
|
||||
defer (func() {
|
||||
if err := recover(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
checkState(c, result,
|
||||
&expectedState{
|
||||
name: name,
|
||||
result: expectedResult,
|
||||
failed: true,
|
||||
log: log,
|
||||
})
|
||||
})()
|
||||
result = closure()
|
||||
if shouldStop {
|
||||
c.Logf("%s didn't stop when it should", name)
|
||||
}
|
||||
}
|
||||
168
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/printer.go
generated
vendored
Normal file
168
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/printer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package check
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
)
|
||||
|
||||
func indent(s, with string) (r string) {
|
||||
eol := true
|
||||
for i := 0; i != len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
case eol && c == '\n' || c == '\r':
|
||||
case c == '\n' || c == '\r':
|
||||
eol = true
|
||||
case eol:
|
||||
eol = false
|
||||
s = s[:i] + with + s[i:]
|
||||
i += len(with)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func printLine(filename string, line int) (string, error) {
|
||||
fset := token.NewFileSet()
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}
|
||||
lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config}
|
||||
ast.Walk(lp, fnode)
|
||||
result := lp.output.Bytes()
|
||||
// Comments leave \n at the end.
|
||||
n := len(result)
|
||||
for n > 0 && result[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
return string(result[:n]), nil
|
||||
}
|
||||
|
||||
type linePrinter struct {
|
||||
config *printer.Config
|
||||
fset *token.FileSet
|
||||
fnode *ast.File
|
||||
line int
|
||||
output bytes.Buffer
|
||||
stmt ast.Stmt
|
||||
}
|
||||
|
||||
func (lp *linePrinter) emit() bool {
|
||||
if lp.stmt != nil {
|
||||
lp.trim(lp.stmt)
|
||||
lp.printWithComments(lp.stmt)
|
||||
lp.stmt = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (lp *linePrinter) printWithComments(n ast.Node) {
|
||||
nfirst := lp.fset.Position(n.Pos()).Line
|
||||
nlast := lp.fset.Position(n.End()).Line
|
||||
for _, g := range lp.fnode.Comments {
|
||||
cfirst := lp.fset.Position(g.Pos()).Line
|
||||
clast := lp.fset.Position(g.End()).Line
|
||||
if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column {
|
||||
for _, c := range g.List {
|
||||
lp.output.WriteString(c.Text)
|
||||
lp.output.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash {
|
||||
// The printer will not include the comment if it starts past
|
||||
// the node itself. Trick it into printing by overlapping the
|
||||
// slash with the end of the statement.
|
||||
g.List[0].Slash = n.End() - 1
|
||||
}
|
||||
}
|
||||
node := &printer.CommentedNode{n, lp.fnode.Comments}
|
||||
lp.config.Fprint(&lp.output, lp.fset, node)
|
||||
}
|
||||
|
||||
func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) {
|
||||
if n == nil {
|
||||
if lp.output.Len() == 0 {
|
||||
lp.emit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
first := lp.fset.Position(n.Pos()).Line
|
||||
last := lp.fset.Position(n.End()).Line
|
||||
if first <= lp.line && last >= lp.line {
|
||||
// Print the innermost statement containing the line.
|
||||
if stmt, ok := n.(ast.Stmt); ok {
|
||||
if _, ok := n.(*ast.BlockStmt); !ok {
|
||||
lp.stmt = stmt
|
||||
}
|
||||
}
|
||||
if first == lp.line && lp.emit() {
|
||||
return nil
|
||||
}
|
||||
return lp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lp *linePrinter) trim(n ast.Node) bool {
|
||||
stmt, ok := n.(ast.Stmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
line := lp.fset.Position(n.Pos()).Line
|
||||
if line != lp.line {
|
||||
return false
|
||||
}
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.IfStmt:
|
||||
stmt.Body = lp.trimBlock(stmt.Body)
|
||||
case *ast.SwitchStmt:
|
||||
stmt.Body = lp.trimBlock(stmt.Body)
|
||||
case *ast.TypeSwitchStmt:
|
||||
stmt.Body = lp.trimBlock(stmt.Body)
|
||||
case *ast.CaseClause:
|
||||
stmt.Body = lp.trimList(stmt.Body)
|
||||
case *ast.CommClause:
|
||||
stmt.Body = lp.trimList(stmt.Body)
|
||||
case *ast.BlockStmt:
|
||||
stmt.List = lp.trimList(stmt.List)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt {
|
||||
if !lp.trim(stmt) {
|
||||
return lp.emptyBlock(stmt)
|
||||
}
|
||||
stmt.Rbrace = stmt.Lbrace
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt {
|
||||
for i := 0; i != len(stmts); i++ {
|
||||
if !lp.trim(stmts[i]) {
|
||||
stmts[i] = lp.emptyStmt(stmts[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
return stmts
|
||||
}
|
||||
|
||||
func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt {
|
||||
return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}}
|
||||
}
|
||||
|
||||
func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt {
|
||||
p := n.Pos()
|
||||
return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p}
|
||||
}
|
||||
109
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/printer_test.go
generated
vendored
Normal file
109
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/printer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package check_test
|
||||
|
||||
import (
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
var _ = Suite(&PrinterS{})
|
||||
|
||||
type PrinterS struct{}
|
||||
|
||||
func (s *PrinterS) TestCountSuite(c *C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
var printTestFuncLine int
|
||||
|
||||
func init() {
|
||||
printTestFuncLine = getMyLine() + 3
|
||||
}
|
||||
|
||||
func printTestFunc() {
|
||||
println(1) // Comment1
|
||||
if 2 == 2 { // Comment2
|
||||
println(3) // Comment3
|
||||
}
|
||||
switch 5 {
|
||||
case 6:
|
||||
println(6) // Comment6
|
||||
println(7)
|
||||
}
|
||||
switch interface{}(9).(type) { // Comment9
|
||||
case int:
|
||||
println(10)
|
||||
println(11)
|
||||
}
|
||||
select {
|
||||
case <-(chan bool)(nil):
|
||||
println(14)
|
||||
println(15)
|
||||
default:
|
||||
println(16)
|
||||
println(17)
|
||||
}
|
||||
println(19,
|
||||
20)
|
||||
_ = func() {
|
||||
println(21)
|
||||
println(22)
|
||||
}
|
||||
println(24, func() {
|
||||
println(25)
|
||||
})
|
||||
// Leading comment
|
||||
// with multiple lines.
|
||||
println(29) // Comment29
|
||||
}
|
||||
|
||||
var printLineTests = []struct {
|
||||
line int
|
||||
output string
|
||||
}{
|
||||
{1, "println(1) // Comment1"},
|
||||
{2, "if 2 == 2 { // Comment2\n ...\n}"},
|
||||
{3, "println(3) // Comment3"},
|
||||
{5, "switch 5 {\n...\n}"},
|
||||
{6, "case 6:\n println(6) // Comment6\n ..."},
|
||||
{7, "println(7)"},
|
||||
{9, "switch interface{}(9).(type) { // Comment9\n...\n}"},
|
||||
{10, "case int:\n println(10)\n ..."},
|
||||
{14, "case <-(chan bool)(nil):\n println(14)\n ..."},
|
||||
{15, "println(15)"},
|
||||
{16, "default:\n println(16)\n ..."},
|
||||
{17, "println(17)"},
|
||||
{19, "println(19,\n 20)"},
|
||||
{20, "println(19,\n 20)"},
|
||||
{21, "_ = func() {\n println(21)\n println(22)\n}"},
|
||||
{22, "println(22)"},
|
||||
{24, "println(24, func() {\n println(25)\n})"},
|
||||
{25, "println(25)"},
|
||||
{26, "println(24, func() {\n println(25)\n})"},
|
||||
{29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"},
|
||||
}
|
||||
|
||||
func (s *PrinterS) TestPrintLine(c *C) {
|
||||
for _, test := range printLineTests {
|
||||
output, err := PrintLine("printer_test.go", printTestFuncLine+test.line)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(output, Equals, test.output)
|
||||
}
|
||||
}
|
||||
|
||||
var indentTests = []struct {
|
||||
in, out string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\n", "\n"},
|
||||
{"a", ">>>a"},
|
||||
{"a\n", ">>>a\n"},
|
||||
{"a\nb", ">>>a\n>>>b"},
|
||||
{" ", ">>> "},
|
||||
}
|
||||
|
||||
func (s *PrinterS) TestIndent(c *C) {
|
||||
for _, test := range indentTests {
|
||||
out := Indent(test.in, ">>>")
|
||||
c.Assert(out, Equals, test.out)
|
||||
}
|
||||
|
||||
}
|
||||
175
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/run.go
generated
vendored
Normal file
175
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/run.go
generated
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package check
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test suite registry.
|
||||
|
||||
var allSuites []interface{}
|
||||
|
||||
// Suite registers the given value as a test suite to be run. Any methods
|
||||
// starting with the Test prefix in the given value will be considered as
|
||||
// a test method.
|
||||
func Suite(suite interface{}) interface{} {
|
||||
allSuites = append(allSuites, suite)
|
||||
return suite
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Public running interface.
|
||||
|
||||
var (
|
||||
oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run")
|
||||
oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode")
|
||||
oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)")
|
||||
oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks")
|
||||
oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark")
|
||||
oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run")
|
||||
oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory")
|
||||
|
||||
newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run")
|
||||
newVerboseFlag = flag.Bool("check.v", false, "Verbose mode")
|
||||
newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)")
|
||||
newBenchFlag = flag.Bool("check.b", false, "Run benchmarks")
|
||||
newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark")
|
||||
newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks")
|
||||
newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run")
|
||||
newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory")
|
||||
)
|
||||
|
||||
// TestingT runs all test suites registered with the Suite function,
|
||||
// printing results to stdout, and reporting any failures back to
|
||||
// the "testing" package.
|
||||
func TestingT(testingT *testing.T) {
|
||||
benchTime := *newBenchTime
|
||||
if benchTime == 1*time.Second {
|
||||
benchTime = *oldBenchTime
|
||||
}
|
||||
conf := &RunConf{
|
||||
Filter: *oldFilterFlag + *newFilterFlag,
|
||||
Verbose: *oldVerboseFlag || *newVerboseFlag,
|
||||
Stream: *oldStreamFlag || *newStreamFlag,
|
||||
Benchmark: *oldBenchFlag || *newBenchFlag,
|
||||
BenchmarkTime: benchTime,
|
||||
BenchmarkMem: *newBenchMem,
|
||||
KeepWorkDir: *oldWorkFlag || *newWorkFlag,
|
||||
}
|
||||
if *oldListFlag || *newListFlag {
|
||||
w := bufio.NewWriter(os.Stdout)
|
||||
for _, name := range ListAll(conf) {
|
||||
fmt.Fprintln(w, name)
|
||||
}
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
result := RunAll(conf)
|
||||
println(result.String())
|
||||
if !result.Passed() {
|
||||
testingT.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// RunAll runs all test suites registered with the Suite function, using the
|
||||
// provided run configuration.
|
||||
func RunAll(runConf *RunConf) *Result {
|
||||
result := Result{}
|
||||
for _, suite := range allSuites {
|
||||
result.Add(Run(suite, runConf))
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
// Run runs the provided test suite using the provided run configuration.
|
||||
func Run(suite interface{}, runConf *RunConf) *Result {
|
||||
runner := newSuiteRunner(suite, runConf)
|
||||
return runner.run()
|
||||
}
|
||||
|
||||
// ListAll returns the names of all the test functions registered with the
|
||||
// Suite function that will be run with the provided run configuration.
|
||||
func ListAll(runConf *RunConf) []string {
|
||||
var names []string
|
||||
for _, suite := range allSuites {
|
||||
names = append(names, List(suite, runConf)...)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// List returns the names of the test functions in the given
|
||||
// suite that will be run with the provided run configuration.
|
||||
func List(suite interface{}, runConf *RunConf) []string {
|
||||
var names []string
|
||||
runner := newSuiteRunner(suite, runConf)
|
||||
for _, t := range runner.tests {
|
||||
names = append(names, t.String())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Result methods.
|
||||
|
||||
func (r *Result) Add(other *Result) {
|
||||
r.Succeeded += other.Succeeded
|
||||
r.Skipped += other.Skipped
|
||||
r.Failed += other.Failed
|
||||
r.Panicked += other.Panicked
|
||||
r.FixturePanicked += other.FixturePanicked
|
||||
r.ExpectedFailures += other.ExpectedFailures
|
||||
r.Missed += other.Missed
|
||||
if r.WorkDir != "" && other.WorkDir != "" {
|
||||
r.WorkDir += ":" + other.WorkDir
|
||||
} else if other.WorkDir != "" {
|
||||
r.WorkDir = other.WorkDir
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Result) Passed() bool {
|
||||
return (r.Failed == 0 && r.Panicked == 0 &&
|
||||
r.FixturePanicked == 0 && r.Missed == 0 &&
|
||||
r.RunError == nil)
|
||||
}
|
||||
|
||||
func (r *Result) String() string {
|
||||
if r.RunError != nil {
|
||||
return "ERROR: " + r.RunError.Error()
|
||||
}
|
||||
|
||||
var value string
|
||||
if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 &&
|
||||
r.Missed == 0 {
|
||||
value = "OK: "
|
||||
} else {
|
||||
value = "OOPS: "
|
||||
}
|
||||
value += fmt.Sprintf("%d passed", r.Succeeded)
|
||||
if r.Skipped != 0 {
|
||||
value += fmt.Sprintf(", %d skipped", r.Skipped)
|
||||
}
|
||||
if r.ExpectedFailures != 0 {
|
||||
value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures)
|
||||
}
|
||||
if r.Failed != 0 {
|
||||
value += fmt.Sprintf(", %d FAILED", r.Failed)
|
||||
}
|
||||
if r.Panicked != 0 {
|
||||
value += fmt.Sprintf(", %d PANICKED", r.Panicked)
|
||||
}
|
||||
if r.FixturePanicked != 0 {
|
||||
value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked)
|
||||
}
|
||||
if r.Missed != 0 {
|
||||
value += fmt.Sprintf(", %d MISSED", r.Missed)
|
||||
}
|
||||
if r.WorkDir != "" {
|
||||
value += "\nWORK=" + r.WorkDir
|
||||
}
|
||||
return value
|
||||
}
|
||||
419
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/run_test.go
generated
vendored
Normal file
419
vendor/github.com/magiconair/properties/_third_party/gopkg.in/check.v1/run_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
// These tests verify the test running logic.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var runnerS = Suite(&RunS{})
|
||||
|
||||
type RunS struct{}
|
||||
|
||||
func (s *RunS) TestCountSuite(c *C) {
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tests ensuring result counting works properly.
|
||||
|
||||
func (s *RunS) TestSuccess(c *C) {
|
||||
output := String{}
|
||||
result := Run(&SuccessHelper{}, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 1)
|
||||
c.Check(result.Failed, Equals, 0)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 0)
|
||||
c.Check(result.FixturePanicked, Equals, 0)
|
||||
c.Check(result.Missed, Equals, 0)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFailure(c *C) {
|
||||
output := String{}
|
||||
result := Run(&FailHelper{}, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 0)
|
||||
c.Check(result.Failed, Equals, 1)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 0)
|
||||
c.Check(result.FixturePanicked, Equals, 0)
|
||||
c.Check(result.Missed, Equals, 0)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFixture(c *C) {
|
||||
output := String{}
|
||||
result := Run(&FixtureHelper{}, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 2)
|
||||
c.Check(result.Failed, Equals, 0)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 0)
|
||||
c.Check(result.FixturePanicked, Equals, 0)
|
||||
c.Check(result.Missed, Equals, 0)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
func (s *RunS) TestPanicOnTest(c *C) {
|
||||
output := String{}
|
||||
helper := &FixtureHelper{panicOn: "Test1"}
|
||||
result := Run(helper, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 1)
|
||||
c.Check(result.Failed, Equals, 0)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 1)
|
||||
c.Check(result.FixturePanicked, Equals, 0)
|
||||
c.Check(result.Missed, Equals, 0)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
func (s *RunS) TestPanicOnSetUpTest(c *C) {
|
||||
output := String{}
|
||||
helper := &FixtureHelper{panicOn: "SetUpTest"}
|
||||
result := Run(helper, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 0)
|
||||
c.Check(result.Failed, Equals, 0)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 0)
|
||||
c.Check(result.FixturePanicked, Equals, 1)
|
||||
c.Check(result.Missed, Equals, 2)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
func (s *RunS) TestPanicOnSetUpSuite(c *C) {
|
||||
output := String{}
|
||||
helper := &FixtureHelper{panicOn: "SetUpSuite"}
|
||||
result := Run(helper, &RunConf{Output: &output})
|
||||
c.Check(result.Succeeded, Equals, 0)
|
||||
c.Check(result.Failed, Equals, 0)
|
||||
c.Check(result.Skipped, Equals, 0)
|
||||
c.Check(result.Panicked, Equals, 0)
|
||||
c.Check(result.FixturePanicked, Equals, 1)
|
||||
c.Check(result.Missed, Equals, 2)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Check result aggregation.
|
||||
|
||||
func (s *RunS) TestAdd(c *C) {
|
||||
result := &Result{
|
||||
Succeeded: 1,
|
||||
Skipped: 2,
|
||||
Failed: 3,
|
||||
Panicked: 4,
|
||||
FixturePanicked: 5,
|
||||
Missed: 6,
|
||||
ExpectedFailures: 7,
|
||||
}
|
||||
result.Add(&Result{
|
||||
Succeeded: 10,
|
||||
Skipped: 20,
|
||||
Failed: 30,
|
||||
Panicked: 40,
|
||||
FixturePanicked: 50,
|
||||
Missed: 60,
|
||||
ExpectedFailures: 70,
|
||||
})
|
||||
c.Check(result.Succeeded, Equals, 11)
|
||||
c.Check(result.Skipped, Equals, 22)
|
||||
c.Check(result.Failed, Equals, 33)
|
||||
c.Check(result.Panicked, Equals, 44)
|
||||
c.Check(result.FixturePanicked, Equals, 55)
|
||||
c.Check(result.Missed, Equals, 66)
|
||||
c.Check(result.ExpectedFailures, Equals, 77)
|
||||
c.Check(result.RunError, IsNil)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Check the Passed() method.
|
||||
|
||||
func (s *RunS) TestPassed(c *C) {
|
||||
c.Assert((&Result{}).Passed(), Equals, true)
|
||||
c.Assert((&Result{Succeeded: 1}).Passed(), Equals, true)
|
||||
c.Assert((&Result{Skipped: 1}).Passed(), Equals, true)
|
||||
c.Assert((&Result{Failed: 1}).Passed(), Equals, false)
|
||||
c.Assert((&Result{Panicked: 1}).Passed(), Equals, false)
|
||||
c.Assert((&Result{FixturePanicked: 1}).Passed(), Equals, false)
|
||||
c.Assert((&Result{Missed: 1}).Passed(), Equals, false)
|
||||
c.Assert((&Result{RunError: errors.New("!")}).Passed(), Equals, false)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Check that result printing is working correctly.
|
||||
|
||||
func (s *RunS) TestPrintSuccess(c *C) {
|
||||
result := &Result{Succeeded: 5}
|
||||
c.Check(result.String(), Equals, "OK: 5 passed")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintFailure(c *C) {
|
||||
result := &Result{Failed: 5}
|
||||
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FAILED")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintSkipped(c *C) {
|
||||
result := &Result{Skipped: 5}
|
||||
c.Check(result.String(), Equals, "OK: 0 passed, 5 skipped")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintExpectedFailures(c *C) {
|
||||
result := &Result{ExpectedFailures: 5}
|
||||
c.Check(result.String(), Equals, "OK: 0 passed, 5 expected failures")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintPanicked(c *C) {
|
||||
result := &Result{Panicked: 5}
|
||||
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 PANICKED")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintFixturePanicked(c *C) {
|
||||
result := &Result{FixturePanicked: 5}
|
||||
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FIXTURE-PANICKED")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintMissed(c *C) {
|
||||
result := &Result{Missed: 5}
|
||||
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 MISSED")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintAll(c *C) {
|
||||
result := &Result{Succeeded: 1, Skipped: 2, ExpectedFailures: 3,
|
||||
Panicked: 4, FixturePanicked: 5, Missed: 6}
|
||||
c.Check(result.String(), Equals,
|
||||
"OOPS: 1 passed, 2 skipped, 3 expected failures, 4 PANICKED, "+
|
||||
"5 FIXTURE-PANICKED, 6 MISSED")
|
||||
}
|
||||
|
||||
func (s *RunS) TestPrintRunError(c *C) {
|
||||
result := &Result{Succeeded: 1, Failed: 1,
|
||||
RunError: errors.New("Kaboom!")}
|
||||
c.Check(result.String(), Equals, "ERROR: Kaboom!")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that the method pattern flag works correctly.
|
||||
|
||||
func (s *RunS) TestFilterTestName(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "Test[91]"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 5)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFilterTestNameWithAll(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: ".*"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Test2")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[7], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFilterSuiteName(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "FixtureHelper"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test1")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[5], Equals, "Test2")
|
||||
c.Check(helper.calls[6], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[7], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFilterSuiteNameAndTestName(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "FixtureHelper\\.Test2"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(helper.calls[0], Equals, "SetUpSuite")
|
||||
c.Check(helper.calls[1], Equals, "SetUpTest")
|
||||
c.Check(helper.calls[2], Equals, "Test2")
|
||||
c.Check(helper.calls[3], Equals, "TearDownTest")
|
||||
c.Check(helper.calls[4], Equals, "TearDownSuite")
|
||||
c.Check(len(helper.calls), Equals, 5)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFilterAllOut(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "NotFound"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *RunS) TestRequirePartialMatch(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "est"}
|
||||
Run(&helper, &runConf)
|
||||
c.Check(len(helper.calls), Equals, 8)
|
||||
}
|
||||
|
||||
func (s *RunS) TestFilterError(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Filter: "]["}
|
||||
result := Run(&helper, &runConf)
|
||||
c.Check(result.String(), Equals,
|
||||
"ERROR: Bad filter expression: error parsing regexp: missing closing ]: `[`")
|
||||
c.Check(len(helper.calls), Equals, 0)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that List works correctly.
|
||||
|
||||
func (s *RunS) TestListFiltered(c *C) {
|
||||
names := List(&FixtureHelper{}, &RunConf{Filter: "1"})
|
||||
c.Assert(names, DeepEquals, []string{
|
||||
"FixtureHelper.Test1",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RunS) TestList(c *C) {
|
||||
names := List(&FixtureHelper{}, &RunConf{})
|
||||
c.Assert(names, DeepEquals, []string{
|
||||
"FixtureHelper.Test1",
|
||||
"FixtureHelper.Test2",
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that verbose mode prints tests which pass as well.
|
||||
|
||||
func (s *RunS) TestVerboseMode(c *C) {
|
||||
helper := FixtureHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Verbose: true}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t *[.0-9]+s\n" +
|
||||
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
|
||||
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
func (s *RunS) TestVerboseModeWithFailBeforePass(c *C) {
|
||||
helper := FixtureHelper{panicOn: "Test1"}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Verbose: true}
|
||||
Run(&helper, &runConf)
|
||||
|
||||
expected := "(?s).*PANIC.*\n-+\n" + // Should have an extra line.
|
||||
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
|
||||
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify the stream output mode. In this mode there's no output caching.
|
||||
|
||||
type StreamHelper struct {
|
||||
l2 sync.Mutex
|
||||
l3 sync.Mutex
|
||||
}
|
||||
|
||||
func (s *StreamHelper) SetUpSuite(c *C) {
|
||||
c.Log("0")
|
||||
}
|
||||
|
||||
func (s *StreamHelper) Test1(c *C) {
|
||||
c.Log("1")
|
||||
s.l2.Lock()
|
||||
s.l3.Lock()
|
||||
go func() {
|
||||
s.l2.Lock() // Wait for "2".
|
||||
c.Log("3")
|
||||
s.l3.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *StreamHelper) Test2(c *C) {
|
||||
c.Log("2")
|
||||
s.l2.Unlock()
|
||||
s.l3.Lock() // Wait for "3".
|
||||
c.Fail()
|
||||
c.Log("4")
|
||||
}
|
||||
|
||||
func (s *RunS) TestStreamMode(c *C) {
|
||||
helper := &StreamHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Stream: true}
|
||||
Run(helper, &runConf)
|
||||
|
||||
expected := "START: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\n0\n" +
|
||||
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\t *[.0-9]+s\n\n" +
|
||||
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test1\n1\n" +
|
||||
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.Test1\t *[.0-9]+s\n\n" +
|
||||
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n2\n3\n4\n" +
|
||||
"FAIL: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n\n"
|
||||
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
type StreamMissHelper struct{}
|
||||
|
||||
func (s *StreamMissHelper) SetUpSuite(c *C) {
|
||||
c.Log("0")
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
func (s *StreamMissHelper) Test1(c *C) {
|
||||
c.Log("1")
|
||||
}
|
||||
|
||||
func (s *RunS) TestStreamModeWithMiss(c *C) {
|
||||
helper := &StreamMissHelper{}
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Stream: true}
|
||||
Run(helper, &runConf)
|
||||
|
||||
expected := "START: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n0\n" +
|
||||
"FAIL: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n\n" +
|
||||
"START: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n" +
|
||||
"MISS: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n\n"
|
||||
|
||||
c.Assert(output.value, Matches, expected)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Verify that that the keep work dir request indeed does so.
|
||||
|
||||
type WorkDirSuite struct{}
|
||||
|
||||
func (s *WorkDirSuite) Test(c *C) {
|
||||
c.MkDir()
|
||||
}
|
||||
|
||||
func (s *RunS) TestKeepWorkDir(c *C) {
|
||||
output := String{}
|
||||
runConf := RunConf{Output: &output, Verbose: true, KeepWorkDir: true}
|
||||
result := Run(&WorkDirSuite{}, &runConf)
|
||||
|
||||
c.Assert(result.String(), Matches, ".*\nWORK="+result.WorkDir)
|
||||
|
||||
stat, err := os.Stat(result.WorkDir)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(stat.IsDir(), Equals, true)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2013-2014 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Benchmarks the decoder by creating a property file with 1000 key/value pairs.
|
||||
func BenchmarkLoad(b *testing.B) {
|
||||
input := ""
|
||||
for i := 0; i < 1000; i++ {
|
||||
input += fmt.Sprintf("key%d=value%d\n", i, i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Load([]byte(input), ISO_8859_1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Decode assigns property values to exported fields of a struct.
|
||||
//
|
||||
// Decode traverses v recursively and returns an error if a value cannot be
|
||||
// converted to the field type or a required value is missing for a field.
|
||||
//
|
||||
// The following type dependent decodings are used:
|
||||
//
|
||||
// String, boolean, numeric fields have the value of the property key assigned.
|
||||
// The property key name is the name of the field. A different key and a default
|
||||
// value can be set in the field's tag. Fields without default value are
|
||||
// required. If the value cannot be converted to the field type an error is
|
||||
// returned.
|
||||
//
|
||||
// time.Duration fields have the result of time.ParseDuration() assigned.
|
||||
//
|
||||
// time.Time fields have the vaule of time.Parse() assigned. The default layout
|
||||
// is time.RFC3339 but can be set in the field's tag.
|
||||
//
|
||||
// Arrays and slices of string, boolean, numeric, time.Duration and time.Time
|
||||
// fields have the value interpreted as a comma separated list of values. The
|
||||
// individual values are trimmed of whitespace and empty values are ignored. A
|
||||
// default value can be provided as a semicolon separated list in the field's
|
||||
// tag.
|
||||
//
|
||||
// Struct fields are decoded recursively using the field name plus "." as
|
||||
// prefix. The prefix (without dot) can be overridden in the field's tag.
|
||||
// Default values are not supported in the field's tag. Specify them on the
|
||||
// fields of the inner struct instead.
|
||||
//
|
||||
// Map fields must have a key of type string and are decoded recursively by
|
||||
// using the field's name plus ".' as prefix and the next element of the key
|
||||
// name as map key. The prefix (without dot) can be overridden in the field's
|
||||
// tag. Default values are not supported.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// // Field is ignored.
|
||||
// Field int `properties:"-"`
|
||||
//
|
||||
// // Field is assigned value of 'Field'.
|
||||
// Field int
|
||||
//
|
||||
// // Field is assigned value of 'myName'.
|
||||
// Field int `properties:"myName"`
|
||||
//
|
||||
// // Field is assigned value of key 'myName' and has a default
|
||||
// // value 15 if the key does not exist.
|
||||
// Field int `properties:"myName,default=15"`
|
||||
//
|
||||
// // Field is assigned value of key 'Field' and has a default
|
||||
// // value 15 if the key does not exist.
|
||||
// Field int `properties:",default=15"`
|
||||
//
|
||||
// // Field is assigned value of key 'date' and the date
|
||||
// // is in format 2006-01-02
|
||||
// Field time.Time `properties:"date,layout=2006-01-02"`
|
||||
//
|
||||
// // Field is assigned the non-empty and whitespace trimmed
|
||||
// // values of key 'Field' split by commas.
|
||||
// Field []string
|
||||
//
|
||||
// // Field is assigned the non-empty and whitespace trimmed
|
||||
// // values of key 'Field' split by commas and has a default
|
||||
// // value ["a", "b", "c"] if the key does not exist.
|
||||
// Field []string `properties:",default=a;b;c"`
|
||||
//
|
||||
// // Field is decoded recursively with "Field." as key prefix.
|
||||
// Field SomeStruct
|
||||
//
|
||||
// // Field is decoded recursively with "myName." as key prefix.
|
||||
// Field SomeStruct `properties:"myName"`
|
||||
//
|
||||
// // Field is decoded recursively with "Field." as key prefix
|
||||
// // and the next dotted element of the key as map key.
|
||||
// Field map[string]string
|
||||
//
|
||||
// // Field is decoded recursively with "myName." as key prefix
|
||||
// // and the next dotted element of the key as map key.
|
||||
// Field map[string]string `properties:"myName"`
|
||||
func (p *Properties) Decode(x interface{}) error {
|
||||
t, v := reflect.TypeOf(x), reflect.ValueOf(x)
|
||||
if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("not a pointer to struct: %s", t)
|
||||
}
|
||||
if err := dec(p, "", nil, nil, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error {
|
||||
t := v.Type()
|
||||
|
||||
// value returns the property value for key or the default if provided.
|
||||
value := func() (string, error) {
|
||||
if val, ok := p.Get(key); ok {
|
||||
return val, nil
|
||||
}
|
||||
if def != nil {
|
||||
return *def, nil
|
||||
}
|
||||
return "", fmt.Errorf("missing required key %s", key)
|
||||
}
|
||||
|
||||
// conv converts a string to a value of the given type.
|
||||
conv := func(s string, t reflect.Type) (val reflect.Value, err error) {
|
||||
var v interface{}
|
||||
|
||||
switch {
|
||||
case isDuration(t):
|
||||
v, err = time.ParseDuration(s)
|
||||
|
||||
case isTime(t):
|
||||
layout := opts["layout"]
|
||||
if layout == "" {
|
||||
layout = time.RFC3339
|
||||
}
|
||||
v, err = time.Parse(layout, s)
|
||||
|
||||
case isBool(t):
|
||||
v, err = boolVal(s), nil
|
||||
|
||||
case isString(t):
|
||||
v, err = s, nil
|
||||
|
||||
case isFloat(t):
|
||||
v, err = strconv.ParseFloat(s, 64)
|
||||
|
||||
case isInt(t):
|
||||
v, err = strconv.ParseInt(s, 10, 64)
|
||||
|
||||
case isUint(t):
|
||||
v, err = strconv.ParseUint(s, 10, 64)
|
||||
|
||||
default:
|
||||
return reflect.Zero(t), fmt.Errorf("unsupported type %s", t)
|
||||
}
|
||||
if err != nil {
|
||||
return reflect.Zero(t), err
|
||||
}
|
||||
return reflect.ValueOf(v).Convert(t), nil
|
||||
}
|
||||
|
||||
// keydef returns the property key and the default value based on the
|
||||
// name of the struct field and the options in the tag.
|
||||
keydef := func(f reflect.StructField) (string, *string, map[string]string) {
|
||||
key, opts := parseTag(f.Tag.Get("properties"))
|
||||
|
||||
var def *string
|
||||
if d, ok := opts["default"]; ok {
|
||||
def = &d
|
||||
}
|
||||
if key != "" {
|
||||
return key, def, opts
|
||||
}
|
||||
return f.Name, def, opts
|
||||
}
|
||||
|
||||
switch {
|
||||
case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t):
|
||||
s, err := value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val, err := conv(s, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(val)
|
||||
|
||||
case isPtr(t):
|
||||
return dec(p, key, def, opts, v.Elem())
|
||||
|
||||
case isStruct(t):
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fv := v.Field(i)
|
||||
fk, def, opts := keydef(t.Field(i))
|
||||
if !fv.CanSet() {
|
||||
return fmt.Errorf("cannot set %s", t.Field(i).Name)
|
||||
}
|
||||
if fk == "-" {
|
||||
continue
|
||||
}
|
||||
if key != "" {
|
||||
fk = key + "." + fk
|
||||
}
|
||||
if err := dec(p, fk, def, opts, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case isArray(t):
|
||||
val, err := value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vals := split(val, ";")
|
||||
a := reflect.MakeSlice(t, 0, len(vals))
|
||||
for _, s := range vals {
|
||||
val, err := conv(s, t.Elem())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a = reflect.Append(a, val)
|
||||
}
|
||||
v.Set(a)
|
||||
|
||||
case isMap(t):
|
||||
valT := t.Elem()
|
||||
m := reflect.MakeMap(t)
|
||||
for postfix, _ := range p.FilterStripPrefix(key + ".").m {
|
||||
pp := strings.SplitN(postfix, ".", 2)
|
||||
mk, mv := pp[0], reflect.New(valT)
|
||||
if err := dec(p, key+"."+mk, nil, nil, mv); err != nil {
|
||||
return err
|
||||
}
|
||||
m.SetMapIndex(reflect.ValueOf(mk), mv.Elem())
|
||||
}
|
||||
v.Set(m)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %s", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// split splits a string on sep, trims whitespace of elements
|
||||
// and omits empty elements
|
||||
func split(s string, sep string) []string {
|
||||
var a []string
|
||||
for _, v := range strings.Split(s, sep) {
|
||||
if v = strings.TrimSpace(v); v != "" {
|
||||
a = append(a, v)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// parseTag parses a "key,k=v,k=v,..."
|
||||
func parseTag(tag string) (key string, opts map[string]string) {
|
||||
opts = map[string]string{}
|
||||
for i, s := range strings.Split(tag, ",") {
|
||||
if i == 0 {
|
||||
key = s
|
||||
continue
|
||||
}
|
||||
|
||||
pp := strings.SplitN(s, "=", 2)
|
||||
if len(pp) == 1 {
|
||||
opts[pp[0]] = ""
|
||||
} else {
|
||||
opts[pp[0]] = pp[1]
|
||||
}
|
||||
}
|
||||
return key, opts
|
||||
}
|
||||
|
||||
func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice }
|
||||
func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool }
|
||||
func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
|
||||
func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }
|
||||
func isNumeric(t reflect.Type) bool { return isInt(t) || isUint(t) || isFloat(t) }
|
||||
func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr }
|
||||
func isString(t reflect.Type) bool { return t.Kind() == reflect.String }
|
||||
func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct }
|
||||
func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) }
|
||||
func isFloat(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64
|
||||
}
|
||||
func isInt(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64
|
||||
}
|
||||
func isUint(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDecodeValues(t *testing.T) {
|
||||
type S struct {
|
||||
S string
|
||||
BT bool
|
||||
BF bool
|
||||
I int
|
||||
I8 int8
|
||||
I16 int16
|
||||
I32 int32
|
||||
I64 int64
|
||||
U uint
|
||||
U8 uint8
|
||||
U16 uint16
|
||||
U32 uint32
|
||||
U64 uint64
|
||||
F32 float32
|
||||
F64 float64
|
||||
D time.Duration
|
||||
TM time.Time
|
||||
}
|
||||
in := `
|
||||
S=abc
|
||||
BT=true
|
||||
BF=false
|
||||
I=-1
|
||||
I8=-8
|
||||
I16=-16
|
||||
I32=-32
|
||||
I64=-64
|
||||
U=1
|
||||
U8=8
|
||||
U16=16
|
||||
U32=32
|
||||
U64=64
|
||||
F32=3.2
|
||||
F64=6.4
|
||||
D=5s
|
||||
TM=2015-01-02T12:34:56Z
|
||||
`
|
||||
out := &S{
|
||||
S: "abc",
|
||||
BT: true,
|
||||
BF: false,
|
||||
I: -1,
|
||||
I8: -8,
|
||||
I16: -16,
|
||||
I32: -32,
|
||||
I64: -64,
|
||||
U: 1,
|
||||
U8: 8,
|
||||
U16: 16,
|
||||
U32: 32,
|
||||
U64: 64,
|
||||
F32: 3.2,
|
||||
F64: 6.4,
|
||||
D: 5 * time.Second,
|
||||
TM: tm(t, time.RFC3339, "2015-01-02T12:34:56Z"),
|
||||
}
|
||||
testDecode(t, in, &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeValueDefaults(t *testing.T) {
|
||||
type S struct {
|
||||
S string `properties:",default=abc"`
|
||||
BT bool `properties:",default=true"`
|
||||
BF bool `properties:",default=false"`
|
||||
I int `properties:",default=-1"`
|
||||
I8 int8 `properties:",default=-8"`
|
||||
I16 int16 `properties:",default=-16"`
|
||||
I32 int32 `properties:",default=-32"`
|
||||
I64 int64 `properties:",default=-64"`
|
||||
U uint `properties:",default=1"`
|
||||
U8 uint8 `properties:",default=8"`
|
||||
U16 uint16 `properties:",default=16"`
|
||||
U32 uint32 `properties:",default=32"`
|
||||
U64 uint64 `properties:",default=64"`
|
||||
F32 float32 `properties:",default=3.2"`
|
||||
F64 float64 `properties:",default=6.4"`
|
||||
D time.Duration `properties:",default=5s"`
|
||||
TM time.Time `properties:",default=2015-01-02T12:34:56Z"`
|
||||
}
|
||||
out := &S{
|
||||
S: "abc",
|
||||
BT: true,
|
||||
BF: false,
|
||||
I: -1,
|
||||
I8: -8,
|
||||
I16: -16,
|
||||
I32: -32,
|
||||
I64: -64,
|
||||
U: 1,
|
||||
U8: 8,
|
||||
U16: 16,
|
||||
U32: 32,
|
||||
U64: 64,
|
||||
F32: 3.2,
|
||||
F64: 6.4,
|
||||
D: 5 * time.Second,
|
||||
TM: tm(t, time.RFC3339, "2015-01-02T12:34:56Z"),
|
||||
}
|
||||
testDecode(t, "", &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeArrays(t *testing.T) {
|
||||
type S struct {
|
||||
S []string
|
||||
B []bool
|
||||
I []int
|
||||
I8 []int8
|
||||
I16 []int16
|
||||
I32 []int32
|
||||
I64 []int64
|
||||
U []uint
|
||||
U8 []uint8
|
||||
U16 []uint16
|
||||
U32 []uint32
|
||||
U64 []uint64
|
||||
F32 []float32
|
||||
F64 []float64
|
||||
D []time.Duration
|
||||
TM []time.Time
|
||||
}
|
||||
in := `
|
||||
S=a;b
|
||||
B=true;false
|
||||
I=-1;-2
|
||||
I8=-8;-9
|
||||
I16=-16;-17
|
||||
I32=-32;-33
|
||||
I64=-64;-65
|
||||
U=1;2
|
||||
U8=8;9
|
||||
U16=16;17
|
||||
U32=32;33
|
||||
U64=64;65
|
||||
F32=3.2;3.3
|
||||
F64=6.4;6.5
|
||||
D=4s;5s
|
||||
TM=2015-01-01T00:00:00Z;2016-01-01T00:00:00Z
|
||||
`
|
||||
out := &S{
|
||||
S: []string{"a", "b"},
|
||||
B: []bool{true, false},
|
||||
I: []int{-1, -2},
|
||||
I8: []int8{-8, -9},
|
||||
I16: []int16{-16, -17},
|
||||
I32: []int32{-32, -33},
|
||||
I64: []int64{-64, -65},
|
||||
U: []uint{1, 2},
|
||||
U8: []uint8{8, 9},
|
||||
U16: []uint16{16, 17},
|
||||
U32: []uint32{32, 33},
|
||||
U64: []uint64{64, 65},
|
||||
F32: []float32{3.2, 3.3},
|
||||
F64: []float64{6.4, 6.5},
|
||||
D: []time.Duration{4 * time.Second, 5 * time.Second},
|
||||
TM: []time.Time{tm(t, time.RFC3339, "2015-01-01T00:00:00Z"), tm(t, time.RFC3339, "2016-01-01T00:00:00Z")},
|
||||
}
|
||||
testDecode(t, in, &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeArrayDefaults(t *testing.T) {
|
||||
type S struct {
|
||||
S []string `properties:",default=a;b"`
|
||||
B []bool `properties:",default=true;false"`
|
||||
I []int `properties:",default=-1;-2"`
|
||||
I8 []int8 `properties:",default=-8;-9"`
|
||||
I16 []int16 `properties:",default=-16;-17"`
|
||||
I32 []int32 `properties:",default=-32;-33"`
|
||||
I64 []int64 `properties:",default=-64;-65"`
|
||||
U []uint `properties:",default=1;2"`
|
||||
U8 []uint8 `properties:",default=8;9"`
|
||||
U16 []uint16 `properties:",default=16;17"`
|
||||
U32 []uint32 `properties:",default=32;33"`
|
||||
U64 []uint64 `properties:",default=64;65"`
|
||||
F32 []float32 `properties:",default=3.2;3.3"`
|
||||
F64 []float64 `properties:",default=6.4;6.5"`
|
||||
D []time.Duration `properties:",default=4s;5s"`
|
||||
TM []time.Time `properties:",default=2015-01-01T00:00:00Z;2016-01-01T00:00:00Z"`
|
||||
}
|
||||
out := &S{
|
||||
S: []string{"a", "b"},
|
||||
B: []bool{true, false},
|
||||
I: []int{-1, -2},
|
||||
I8: []int8{-8, -9},
|
||||
I16: []int16{-16, -17},
|
||||
I32: []int32{-32, -33},
|
||||
I64: []int64{-64, -65},
|
||||
U: []uint{1, 2},
|
||||
U8: []uint8{8, 9},
|
||||
U16: []uint16{16, 17},
|
||||
U32: []uint32{32, 33},
|
||||
U64: []uint64{64, 65},
|
||||
F32: []float32{3.2, 3.3},
|
||||
F64: []float64{6.4, 6.5},
|
||||
D: []time.Duration{4 * time.Second, 5 * time.Second},
|
||||
TM: []time.Time{tm(t, time.RFC3339, "2015-01-01T00:00:00Z"), tm(t, time.RFC3339, "2016-01-01T00:00:00Z")},
|
||||
}
|
||||
testDecode(t, "", &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeSkipUndef(t *testing.T) {
|
||||
type S struct {
|
||||
X string `properties:"-"`
|
||||
Undef string `properties:",default=some value"`
|
||||
}
|
||||
in := `X=ignore`
|
||||
out := &S{"", "some value"}
|
||||
testDecode(t, in, &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeStruct(t *testing.T) {
|
||||
type A struct {
|
||||
S string
|
||||
T string `properties:"t"`
|
||||
U string `properties:"u,default=uuu"`
|
||||
}
|
||||
type S struct {
|
||||
A A
|
||||
B A `properties:"b"`
|
||||
}
|
||||
in := `
|
||||
A.S=sss
|
||||
A.t=ttt
|
||||
b.S=SSS
|
||||
b.t=TTT
|
||||
`
|
||||
out := &S{
|
||||
A{S: "sss", T: "ttt", U: "uuu"},
|
||||
A{S: "SSS", T: "TTT", U: "uuu"},
|
||||
}
|
||||
testDecode(t, in, &S{}, out)
|
||||
}
|
||||
|
||||
func TestDecodeMap(t *testing.T) {
|
||||
type S struct {
|
||||
A string `properties:"a"`
|
||||
}
|
||||
type X struct {
|
||||
A map[string]string
|
||||
B map[string][]string
|
||||
C map[string]map[string]string
|
||||
D map[string]S
|
||||
E map[string]int
|
||||
F map[string]int `properties:"-"`
|
||||
}
|
||||
in := `
|
||||
A.foo=bar
|
||||
A.bar=bang
|
||||
B.foo=a;b;c
|
||||
B.bar=1;2;3
|
||||
C.foo.one=1
|
||||
C.foo.two=2
|
||||
C.bar.three=3
|
||||
C.bar.four=4
|
||||
D.foo.a=bar
|
||||
`
|
||||
out := &X{
|
||||
A: map[string]string{"foo": "bar", "bar": "bang"},
|
||||
B: map[string][]string{"foo": []string{"a", "b", "c"}, "bar": []string{"1", "2", "3"}},
|
||||
C: map[string]map[string]string{"foo": map[string]string{"one": "1", "two": "2"}, "bar": map[string]string{"three": "3", "four": "4"}},
|
||||
D: map[string]S{"foo": S{"bar"}},
|
||||
E: map[string]int{},
|
||||
}
|
||||
testDecode(t, in, &X{}, out)
|
||||
}
|
||||
|
||||
func testDecode(t *testing.T, in string, v, out interface{}) {
|
||||
p, err := parse(in)
|
||||
if err != nil {
|
||||
t.Fatalf("got %v want nil", err)
|
||||
}
|
||||
if err := p.Decode(v); err != nil {
|
||||
t.Fatalf("got %v want nil", err)
|
||||
}
|
||||
if got, want := v, out; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func tm(t *testing.T, layout, s string) time.Time {
|
||||
tm, err := time.Parse(layout, s)
|
||||
if err != nil {
|
||||
t.Fatalf("got %v want nil", err)
|
||||
}
|
||||
return tm
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package properties provides functions for reading and writing
|
||||
// ISO-8859-1 and UTF-8 encoded .properties files and has
|
||||
// support for recursive property expansion.
|
||||
//
|
||||
// Java properties files are ISO-8859-1 encoded and use Unicode
|
||||
// literals for characters outside the ISO character set. Unicode
|
||||
// literals can be used in UTF-8 encoded properties files but
|
||||
// aren't necessary.
|
||||
//
|
||||
// To load a single properties file use MustLoadFile():
|
||||
//
|
||||
// p := properties.MustLoadFile(filename, properties.UTF8)
|
||||
//
|
||||
// To load multiple properties files use MustLoadFiles()
|
||||
// which loads the files in the given order and merges the
|
||||
// result. Missing properties files can be ignored if the
|
||||
// 'ignoreMissing' flag is set to true.
|
||||
//
|
||||
// Filenames can contain environment variables which are expanded
|
||||
// before loading.
|
||||
//
|
||||
// f1 := "/etc/myapp/myapp.conf"
|
||||
// f2 := "/home/${USER}/myapp.conf"
|
||||
// p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true)
|
||||
//
|
||||
// All of the different key/value delimiters ' ', ':' and '=' are
|
||||
// supported as well as the comment characters '!' and '#' and
|
||||
// multi-line values.
|
||||
//
|
||||
// ! this is a comment
|
||||
// # and so is this
|
||||
//
|
||||
// # the following expressions are equal
|
||||
// key value
|
||||
// key=value
|
||||
// key:value
|
||||
// key = value
|
||||
// key : value
|
||||
// key = val\
|
||||
// ue
|
||||
//
|
||||
// Properties stores all comments preceding a key and provides
|
||||
// GetComments() and SetComments() methods to retrieve and
|
||||
// update them. The convenience functions GetComment() and
|
||||
// SetComment() allow access to the last comment. The
|
||||
// WriteComment() method writes properties files including
|
||||
// the comments and with the keys in the original order.
|
||||
// This can be used for sanitizing properties files.
|
||||
//
|
||||
// Property expansion is recursive and circular references
|
||||
// and malformed expressions are not allowed and cause an
|
||||
// error. Expansion of environment variables is supported.
|
||||
//
|
||||
// # standard property
|
||||
// key = value
|
||||
//
|
||||
// # property expansion: key2 = value
|
||||
// key2 = ${key}
|
||||
//
|
||||
// # recursive expansion: key3 = value
|
||||
// key3 = ${key2}
|
||||
//
|
||||
// # circular reference (error)
|
||||
// key = ${key}
|
||||
//
|
||||
// # malformed expression (error)
|
||||
// key = ${ke
|
||||
//
|
||||
// # refers to the users' home dir
|
||||
// home = ${HOME}
|
||||
//
|
||||
// # local key takes precendence over env var: u = foo
|
||||
// USER = foo
|
||||
// u = ${USER}
|
||||
//
|
||||
// The default property expansion format is ${key} but can be
|
||||
// changed by setting different pre- and postfix values on the
|
||||
// Properties object.
|
||||
//
|
||||
// p := properties.NewProperties()
|
||||
// p.Prefix = "#["
|
||||
// p.Postfix = "]#"
|
||||
//
|
||||
// Properties provides convenience functions for getting typed
|
||||
// values with default values if the key does not exist or the
|
||||
// type conversion failed.
|
||||
//
|
||||
// # Returns true if the value is either "1", "on", "yes" or "true"
|
||||
// # Returns false for every other value and the default value if
|
||||
// # the key does not exist.
|
||||
// v = p.GetBool("key", false)
|
||||
//
|
||||
// # Returns the value if the key exists and the format conversion
|
||||
// # was successful. Otherwise, the default value is returned.
|
||||
// v = p.GetInt64("key", 999)
|
||||
// v = p.GetUint64("key", 999)
|
||||
// v = p.GetFloat64("key", 123.0)
|
||||
// v = p.GetString("key", "def")
|
||||
// v = p.GetDuration("key", 999)
|
||||
//
|
||||
// As an alterantive properties may be applied with the standard
|
||||
// library's flag implementation at any time.
|
||||
//
|
||||
// # Standard configuration
|
||||
// v = flag.Int("key", 999, "help message")
|
||||
// flag.Parse()
|
||||
//
|
||||
// # Merge p into the flag set
|
||||
// p.MustFlag(flag.CommandLine)
|
||||
//
|
||||
// Properties provides several MustXXX() convenience functions
|
||||
// which will terminate the app if an error occurs. The behavior
|
||||
// of the failure is configurable and the default is to call
|
||||
// log.Fatal(err). To have the MustXXX() functions panic instead
|
||||
// of logging the error set a different ErrorHandler before
|
||||
// you use the Properties package.
|
||||
//
|
||||
// properties.ErrorHandler = properties.PanicHandler
|
||||
//
|
||||
// # Will panic instead of logging an error
|
||||
// p := properties.MustLoadFile("config.properties")
|
||||
//
|
||||
// You can also provide your own ErrorHandler function. The only requirement
|
||||
// is that the error handler function must exit after handling the error.
|
||||
//
|
||||
// properties.ErrorHandler = func(err error) {
|
||||
// fmt.Println(err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//
|
||||
// # Will write to stdout and then exit
|
||||
// p := properties.MustLoadFile("config.properties")
|
||||
//
|
||||
// Properties can also be loaded into a struct via the `Decode`
|
||||
// method, e.g.
|
||||
//
|
||||
// type S struct {
|
||||
// A string `properties:"a,default=foo"`
|
||||
// D time.Duration `properties:"timeout,default=5s"`
|
||||
// E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"`
|
||||
// }
|
||||
//
|
||||
// See `Decode()` method for the full documentation.
|
||||
//
|
||||
// The following documents provide a description of the properties
|
||||
// file format.
|
||||
//
|
||||
// http://en.wikipedia.org/wiki/.properties
|
||||
//
|
||||
// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
|
||||
//
|
||||
package properties
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func ExampleLoad_iso88591() {
|
||||
buf := []byte("key = ISO-8859-1 value with unicode literal \\u2318 and umlaut \xE4") // 0xE4 == ä
|
||||
p, _ := Load(buf, ISO_8859_1)
|
||||
v, ok := p.Get("key")
|
||||
fmt.Println(ok)
|
||||
fmt.Println(v)
|
||||
// Output:
|
||||
// true
|
||||
// ISO-8859-1 value with unicode literal ⌘ and umlaut ä
|
||||
}
|
||||
|
||||
func ExampleLoad_utf8() {
|
||||
p, _ := Load([]byte("key = UTF-8 value with unicode character ⌘ and umlaut ä"), UTF8)
|
||||
v, ok := p.Get("key")
|
||||
fmt.Println(ok)
|
||||
fmt.Println(v)
|
||||
// Output:
|
||||
// true
|
||||
// UTF-8 value with unicode character ⌘ and umlaut ä
|
||||
}
|
||||
|
||||
func ExampleProperties_GetBool() {
|
||||
var input = `
|
||||
key=1
|
||||
key2=On
|
||||
key3=YES
|
||||
key4=true`
|
||||
p, _ := Load([]byte(input), ISO_8859_1)
|
||||
fmt.Println(p.GetBool("key", false))
|
||||
fmt.Println(p.GetBool("key2", false))
|
||||
fmt.Println(p.GetBool("key3", false))
|
||||
fmt.Println(p.GetBool("key4", false))
|
||||
fmt.Println(p.GetBool("keyX", false))
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleProperties_GetString() {
|
||||
p, _ := Load([]byte("key=value"), ISO_8859_1)
|
||||
v := p.GetString("another key", "default value")
|
||||
fmt.Println(v)
|
||||
// Output:
|
||||
// default value
|
||||
}
|
||||
|
||||
func Example() {
|
||||
// Decode some key/value pairs with expressions
|
||||
p, err := Load([]byte("key=value\nkey2=${key}"), ISO_8859_1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get a valid key
|
||||
if v, ok := p.Get("key"); ok {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Get an invalid key
|
||||
if _, ok := p.Get("does not exist"); !ok {
|
||||
fmt.Println("invalid key")
|
||||
}
|
||||
|
||||
// Get a key with a default value
|
||||
v := p.GetString("does not exist", "some value")
|
||||
fmt.Println(v)
|
||||
|
||||
// Dump the expanded key/value pairs of the Properties
|
||||
fmt.Println("Expanded key/value pairs")
|
||||
fmt.Println(p)
|
||||
|
||||
// Output:
|
||||
// value
|
||||
// invalid key
|
||||
// some value
|
||||
// Expanded key/value pairs
|
||||
// key = value
|
||||
// key2 = value
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import "flag"
|
||||
|
||||
// MustFlag sets flags that are skipped by dst.Parse when p contains
|
||||
// the respective key for flag.Flag.Name.
|
||||
//
|
||||
// It's use is recommended with command line arguments as in:
|
||||
// flag.Parse()
|
||||
// p.MustFlag(flag.CommandLine)
|
||||
func (p *Properties) MustFlag(dst *flag.FlagSet) {
|
||||
m := make(map[string]*flag.Flag)
|
||||
dst.VisitAll(func(f *flag.Flag) {
|
||||
m[f.Name] = f
|
||||
})
|
||||
dst.Visit(func(f *flag.Flag) {
|
||||
delete(m, f.Name) // overridden
|
||||
})
|
||||
|
||||
for name, f := range m {
|
||||
v, ok := p.Get(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := f.Value.Set(v); err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFlag verifies Properties.MustFlag without flag.FlagSet.Parse
|
||||
func TestFlag(t *testing.T) {
|
||||
f := flag.NewFlagSet("src", flag.PanicOnError)
|
||||
gotS := f.String("s", "?", "string flag")
|
||||
gotI := f.Int("i", -1, "int flag")
|
||||
|
||||
p := NewProperties()
|
||||
p.Set("s", "t")
|
||||
p.Set("i", "9")
|
||||
p.MustFlag(f)
|
||||
|
||||
if want := "t"; *gotS != want {
|
||||
t.Errorf("Got string s=%q, want %q", *gotS, want)
|
||||
}
|
||||
if want := 9; *gotI != want {
|
||||
t.Errorf("Got int i=%d, want %d", *gotI, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFlagOverride verifies Properties.MustFlag with flag.FlagSet.Parse.
|
||||
func TestFlagOverride(t *testing.T) {
|
||||
f := flag.NewFlagSet("src", flag.PanicOnError)
|
||||
gotA := f.Int("a", 1, "remain default")
|
||||
gotB := f.Int("b", 2, "customized")
|
||||
gotC := f.Int("c", 3, "overridden")
|
||||
|
||||
f.Parse([]string{"-c", "4"})
|
||||
|
||||
p := NewProperties()
|
||||
p.Set("b", "5")
|
||||
p.Set("c", "6")
|
||||
p.MustFlag(f)
|
||||
|
||||
if want := 1; *gotA != want {
|
||||
t.Errorf("Got remain default a=%d, want %d", *gotA, want)
|
||||
}
|
||||
if want := 5; *gotB != want {
|
||||
t.Errorf("Got customized b=%d, want %d", *gotB, want)
|
||||
}
|
||||
if want := 4; *gotC != want {
|
||||
t.Errorf("Got overriden c=%d, want %d", *gotC, want)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleProperties_MustFlag() {
|
||||
x := flag.Int("x", 0, "demo customize")
|
||||
y := flag.Int("y", 0, "demo override")
|
||||
|
||||
// Demo alternative for flag.Parse():
|
||||
flag.CommandLine.Parse([]string{"-y", "10"})
|
||||
fmt.Printf("flagged as x=%d, y=%d\n", *x, *y)
|
||||
|
||||
p := NewProperties()
|
||||
p.Set("x", "7")
|
||||
p.Set("y", "42") // note discard
|
||||
p.MustFlag(flag.CommandLine)
|
||||
fmt.Printf("configured to x=%d, y=%d\n", *x, *y)
|
||||
|
||||
// Output:
|
||||
// flagged as x=0, y=10
|
||||
// configured to x=7, y=10
|
||||
}
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// Parts of the lexer are from the template/text/parser package
|
||||
// For these parts the following applies:
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file of the go 1.2
|
||||
// distribution.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos int // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemEOF
|
||||
itemKey // a key
|
||||
itemValue // a value
|
||||
itemComment // a comment
|
||||
)
|
||||
|
||||
// defines a constant for EOF
|
||||
const eof = -1
|
||||
|
||||
// permitted whitespace characters space, FF and TAB
|
||||
const whitespace = " \f\t"
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
input string // the string being scanned
|
||||
state stateFn // the next lexing function to enter
|
||||
pos int // current position in the input
|
||||
start int // start position of this item
|
||||
width int // width of last rune read from input
|
||||
lastPos int // position of most recent item returned by nextItem
|
||||
runes []rune // scanned runes for this item
|
||||
items chan item // channel of scanned items
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = w
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
item := item{t, l.start, string(l.runes)}
|
||||
l.items <- item
|
||||
l.start = l.pos
|
||||
l.runes = l.runes[:0]
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// appends the rune to the current value
|
||||
func (l *lexer) appendRune(r rune) {
|
||||
l.runes = append(l.runes, r)
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// acceptRunUntil consumes a run of runes up to a terminator.
|
||||
func (l *lexer) acceptRunUntil(term rune) {
|
||||
for term != l.next() {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// hasText returns true if the current parsed text is not empty.
|
||||
func (l *lexer) isNotEmpty() bool {
|
||||
return l.pos > l.start
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string) *lexer {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make(chan item),
|
||||
runes: make([]rune, 0, 32),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexBeforeKey(l); l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
// lexBeforeKey scans until a key begins.
|
||||
func lexBeforeKey(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case isEOF(r):
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
|
||||
case isEOL(r):
|
||||
l.ignore()
|
||||
return lexBeforeKey
|
||||
|
||||
case isComment(r):
|
||||
return lexComment
|
||||
|
||||
case isWhitespace(r):
|
||||
l.acceptRun(whitespace)
|
||||
l.ignore()
|
||||
return lexKey
|
||||
|
||||
default:
|
||||
l.backup()
|
||||
return lexKey
|
||||
}
|
||||
}
|
||||
|
||||
// lexComment scans a comment line. The comment character has already been scanned.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.acceptRun(whitespace)
|
||||
l.ignore()
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isEOF(r):
|
||||
l.ignore()
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
case isEOL(r):
|
||||
l.emit(itemComment)
|
||||
return lexBeforeKey
|
||||
default:
|
||||
l.appendRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lexKey scans the key up to a delimiter
|
||||
func lexKey(l *lexer) stateFn {
|
||||
var r rune
|
||||
|
||||
Loop:
|
||||
for {
|
||||
switch r = l.next(); {
|
||||
|
||||
case isEscape(r):
|
||||
err := l.scanEscapeSequence()
|
||||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
|
||||
case isEndOfKey(r):
|
||||
l.backup()
|
||||
break Loop
|
||||
|
||||
case isEOF(r):
|
||||
break Loop
|
||||
|
||||
default:
|
||||
l.appendRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
if len(l.runes) > 0 {
|
||||
l.emit(itemKey)
|
||||
}
|
||||
|
||||
if isEOF(r) {
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
return lexBeforeValue
|
||||
}
|
||||
|
||||
// lexBeforeValue scans the delimiter between key and value.
|
||||
// Leading and trailing whitespace is ignored.
|
||||
// We expect to be just after the key.
|
||||
func lexBeforeValue(l *lexer) stateFn {
|
||||
l.acceptRun(whitespace)
|
||||
l.accept(":=")
|
||||
l.acceptRun(whitespace)
|
||||
l.ignore()
|
||||
return lexValue
|
||||
}
|
||||
|
||||
// lexValue scans text until the end of the line. We expect to be just after the delimiter.
|
||||
func lexValue(l *lexer) stateFn {
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isEscape(r):
|
||||
r := l.peek()
|
||||
if isEOL(r) {
|
||||
l.next()
|
||||
l.acceptRun(whitespace)
|
||||
} else {
|
||||
err := l.scanEscapeSequence()
|
||||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
case isEOL(r):
|
||||
l.emit(itemValue)
|
||||
l.ignore()
|
||||
return lexBeforeKey
|
||||
|
||||
case isEOF(r):
|
||||
l.emit(itemValue)
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
|
||||
default:
|
||||
l.appendRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanEscapeSequence scans either one of the escaped characters
|
||||
// or a unicode literal. We expect to be after the escape character.
|
||||
func (l *lexer) scanEscapeSequence() error {
|
||||
switch r := l.next(); {
|
||||
|
||||
case isEscapedCharacter(r):
|
||||
l.appendRune(decodeEscapedCharacter(r))
|
||||
return nil
|
||||
|
||||
case atUnicodeLiteral(r):
|
||||
return l.scanUnicodeLiteral()
|
||||
|
||||
case isEOF(r):
|
||||
return fmt.Errorf("premature EOF")
|
||||
|
||||
// silently drop the escape character and append the rune as is
|
||||
default:
|
||||
l.appendRune(r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// scans a unicode literal in the form \uXXXX. We expect to be after the \u.
|
||||
func (l *lexer) scanUnicodeLiteral() error {
|
||||
// scan the digits
|
||||
d := make([]rune, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
d[i] = l.next()
|
||||
if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) {
|
||||
return fmt.Errorf("invalid unicode literal")
|
||||
}
|
||||
}
|
||||
|
||||
// decode the digits into a rune
|
||||
r, err := strconv.ParseInt(string(d), 16, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.appendRune(rune(r))
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeEscapedCharacter returns the unescaped rune. We expect to be after the escape character.
|
||||
func decodeEscapedCharacter(r rune) rune {
|
||||
switch r {
|
||||
case 'f':
|
||||
return '\f'
|
||||
case 'n':
|
||||
return '\n'
|
||||
case 'r':
|
||||
return '\r'
|
||||
case 't':
|
||||
return '\t'
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// atUnicodeLiteral reports whether we are at a unicode literal.
|
||||
// The escape character has already been consumed.
|
||||
func atUnicodeLiteral(r rune) bool {
|
||||
return r == 'u'
|
||||
}
|
||||
|
||||
// isComment reports whether we are at the start of a comment.
|
||||
func isComment(r rune) bool {
|
||||
return r == '#' || r == '!'
|
||||
}
|
||||
|
||||
// isEndOfKey reports whether the rune terminates the current key.
|
||||
func isEndOfKey(r rune) bool {
|
||||
return strings.ContainsRune(" \f\t\r\n:=", r)
|
||||
}
|
||||
|
||||
// isEOF reports whether we are at EOF.
|
||||
func isEOF(r rune) bool {
|
||||
return r == eof
|
||||
}
|
||||
|
||||
// isEOL reports whether we are at a new line character.
|
||||
func isEOL(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
// isEscape reports whether the rune is the escape character which
|
||||
// prefixes unicode literals and other escaped characters.
|
||||
func isEscape(r rune) bool {
|
||||
return r == '\\'
|
||||
}
|
||||
|
||||
// isEscapedCharacter reports whether we are at one of the characters that need escaping.
|
||||
// The escape character has already been consumed.
|
||||
func isEscapedCharacter(r rune) bool {
|
||||
return strings.ContainsRune(" :=fnrt", r)
|
||||
}
|
||||
|
||||
// isWhitespace reports whether the rune is a whitespace character.
|
||||
func isWhitespace(r rune) bool {
|
||||
return strings.ContainsRune(whitespace, r)
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Encoding specifies encoding of the input data.
|
||||
type Encoding uint
|
||||
|
||||
const (
|
||||
// UTF8 interprets the input data as UTF-8.
|
||||
UTF8 Encoding = 1 << iota
|
||||
|
||||
// ISO_8859_1 interprets the input data as ISO-8859-1.
|
||||
ISO_8859_1
|
||||
)
|
||||
|
||||
// Load reads a buffer into a Properties struct.
|
||||
func Load(buf []byte, enc Encoding) (*Properties, error) {
|
||||
return loadBuf(buf, enc)
|
||||
}
|
||||
|
||||
// LoadString reads an UTF8 string into a properties struct.
|
||||
func LoadString(s string) (*Properties, error) {
|
||||
return loadBuf([]byte(s), UTF8)
|
||||
}
|
||||
|
||||
// LoadFile reads a file into a Properties struct.
|
||||
func LoadFile(filename string, enc Encoding) (*Properties, error) {
|
||||
return loadAll([]string{filename}, enc, false)
|
||||
}
|
||||
|
||||
// LoadFiles reads multiple files in the given order into
|
||||
// a Properties struct. If 'ignoreMissing' is true then
|
||||
// non-existent files will not be reported as error.
|
||||
func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||
return loadAll(filenames, enc, ignoreMissing)
|
||||
}
|
||||
|
||||
// LoadURL reads the content of the URL into a Properties struct.
|
||||
//
|
||||
// The encoding is determined via the Content-Type header which
|
||||
// should be set to 'text/plain'. If the 'charset' parameter is
|
||||
// missing, 'iso-8859-1' or 'latin1' the encoding is set to
|
||||
// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
|
||||
// encoding is set to UTF-8. A missing content type header is
|
||||
// interpreted as 'text/plain; charset=utf-8'.
|
||||
func LoadURL(url string) (*Properties, error) {
|
||||
return loadAll([]string{url}, UTF8, false)
|
||||
}
|
||||
|
||||
// LoadURLs reads the content of multiple URLs in the given order into a
|
||||
// Properties struct. If 'ignoreMissing' is true then a 404 status code will
|
||||
// not be reported as error. See LoadURL for the Content-Type header
|
||||
// and the encoding.
|
||||
func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
|
||||
return loadAll(urls, UTF8, ignoreMissing)
|
||||
}
|
||||
|
||||
// LoadAll reads the content of multiple URLs or files in the given order into a
|
||||
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
|
||||
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
||||
// LoadURL for the Content-Type header and the encoding.
|
||||
func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||
return loadAll(names, enc, ignoreMissing)
|
||||
}
|
||||
|
||||
// MustLoadString reads an UTF8 string into a Properties struct and
|
||||
// panics on error.
|
||||
func MustLoadString(s string) *Properties {
|
||||
return must(LoadString(s))
|
||||
}
|
||||
|
||||
// MustLoadFile reads a file into a Properties struct and
|
||||
// panics on error.
|
||||
func MustLoadFile(filename string, enc Encoding) *Properties {
|
||||
return must(LoadFile(filename, enc))
|
||||
}
|
||||
|
||||
// MustLoadFiles reads multiple files in the given order into
|
||||
// a Properties struct and panics on error. If 'ignoreMissing'
|
||||
// is true then non-existent files will not be reported as error.
|
||||
func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties {
|
||||
return must(LoadFiles(filenames, enc, ignoreMissing))
|
||||
}
|
||||
|
||||
// MustLoadURL reads the content of a URL into a Properties struct and
|
||||
// panics on error.
|
||||
func MustLoadURL(url string) *Properties {
|
||||
return must(LoadURL(url))
|
||||
}
|
||||
|
||||
// MustLoadFiles reads the content of multiple URLs in the given order into a
|
||||
// Properties struct and panics on error. If 'ignoreMissing' is true then a 404
|
||||
// status code will not be reported as error.
|
||||
func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
|
||||
return must(LoadURLs(urls, ignoreMissing))
|
||||
}
|
||||
|
||||
// MustLoadAll reads the content of multiple URLs or files in the given order into a
|
||||
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
|
||||
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
||||
// LoadURL for the Content-Type header and the encoding. It panics on error.
|
||||
func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
|
||||
return must(LoadAll(names, enc, ignoreMissing))
|
||||
}
|
||||
|
||||
func loadBuf(buf []byte, enc Encoding) (*Properties, error) {
|
||||
p, err := parse(convert(buf, enc))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, p.check()
|
||||
}
|
||||
|
||||
func loadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||
result := NewProperties()
|
||||
for _, name := range names {
|
||||
n, err := expandName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var p *Properties
|
||||
if strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") {
|
||||
p, err = loadURL(n, ignoreMissing)
|
||||
} else {
|
||||
p, err = loadFile(n, enc, ignoreMissing)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Merge(p)
|
||||
|
||||
}
|
||||
return result, result.check()
|
||||
}
|
||||
|
||||
func loadFile(filename string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
if ignoreMissing && os.IsNotExist(err) {
|
||||
LogPrintf("properties: %s not found. skipping", filename)
|
||||
return NewProperties(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
p, err := parse(convert(data, enc))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func loadURL(url string, ignoreMissing bool) (*Properties, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
|
||||
}
|
||||
if resp.StatusCode == 404 && ignoreMissing {
|
||||
LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
|
||||
return NewProperties(), nil
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
|
||||
}
|
||||
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
var enc Encoding
|
||||
switch strings.ToLower(ct) {
|
||||
case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1":
|
||||
enc = ISO_8859_1
|
||||
case "", "text/plain; charset=utf-8":
|
||||
enc = UTF8
|
||||
default:
|
||||
return nil, fmt.Errorf("properties: invalid content type %s", ct)
|
||||
}
|
||||
|
||||
p, err := parse(convert(body, enc))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func must(p *Properties, err error) *Properties {
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// expandName expands ${ENV_VAR} expressions in a name.
|
||||
// If the environment variable does not exist then it will be replaced
|
||||
// with an empty string. Malformed expressions like "${ENV_VAR" will
|
||||
// be reported as error.
|
||||
func expandName(name string) (string, error) {
|
||||
return expand(name, make(map[string]bool), "${", "}", make(map[string]string))
|
||||
}
|
||||
|
||||
// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.
|
||||
// For ISO-8859-1 we can convert each byte straight into a rune since the
|
||||
// first 256 unicode code points cover ISO-8859-1.
|
||||
func convert(buf []byte, enc Encoding) string {
|
||||
switch enc {
|
||||
case UTF8:
|
||||
return string(buf)
|
||||
case ISO_8859_1:
|
||||
runes := make([]rune, len(buf))
|
||||
for i, b := range buf {
|
||||
runes[i] = rune(b)
|
||||
}
|
||||
return string(runes)
|
||||
default:
|
||||
ErrorHandler(fmt.Errorf("unsupported encoding %v", enc))
|
||||
}
|
||||
panic("ErrorHandler should exit")
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type LoadSuite struct {
|
||||
tempFiles []string
|
||||
}
|
||||
|
||||
var _ = Suite(&LoadSuite{})
|
||||
|
||||
func (s *LoadSuite) TestLoadFailsWithNotExistingFile(c *C) {
|
||||
_, err := LoadFile("doesnotexist.properties", ISO_8859_1)
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(err, ErrorMatches, "open.*no such file or directory")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadFilesFailsOnNotExistingFile(c *C) {
|
||||
_, err := LoadFiles([]string{"doesnotexist.properties"}, ISO_8859_1, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(err, ErrorMatches, "open.*no such file or directory")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadFilesDoesNotFailOnNotExistingFileAndIgnoreMissing(c *C) {
|
||||
p, err := LoadFiles([]string{"doesnotexist.properties"}, ISO_8859_1, true)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadString(c *C) {
|
||||
x := "key=äüö"
|
||||
p1 := MustLoadString(x)
|
||||
p2 := must(Load([]byte(x), UTF8))
|
||||
c.Assert(p1, DeepEquals, p2)
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadFile(c *C) {
|
||||
filename := s.makeFile(c, "key=value")
|
||||
p := MustLoadFile(filename, ISO_8859_1)
|
||||
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
assertKeyValues(c, "", p, "key", "value")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadFiles(c *C) {
|
||||
filename := s.makeFile(c, "key=value")
|
||||
filename2 := s.makeFile(c, "key2=value2")
|
||||
p := MustLoadFiles([]string{filename, filename2}, ISO_8859_1, false)
|
||||
assertKeyValues(c, "", p, "key", "value", "key2", "value2")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadExpandedFile(c *C) {
|
||||
os.Setenv("_VARX", "some-value")
|
||||
filename := s.makeFilePrefix(c, os.Getenv("_VARX"), "key=value")
|
||||
filename = strings.Replace(filename, os.Getenv("_VARX"), "${_VARX}", -1)
|
||||
p := MustLoadFile(filename, ISO_8859_1)
|
||||
assertKeyValues(c, "", p, "key", "value")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadFilesAndIgnoreMissing(c *C) {
|
||||
filename := s.makeFile(c, "key=value")
|
||||
filename2 := s.makeFile(c, "key2=value2")
|
||||
p := MustLoadFiles([]string{filename, filename + "foo", filename2, filename2 + "foo"}, ISO_8859_1, true)
|
||||
assertKeyValues(c, "", p, "key", "value", "key2", "value2")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURL(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
p := MustLoadURL(srv.URL + "/a")
|
||||
assertKeyValues(c, "", p, "key", "value")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURLs(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
p := MustLoadURLs([]string{srv.URL + "/a", srv.URL + "/b"}, false)
|
||||
assertKeyValues(c, "", p, "key", "value", "key2", "value2")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURLsAndFailMissing(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
p, err := LoadURLs([]string{srv.URL + "/a", srv.URL + "/c"}, false)
|
||||
c.Assert(p, IsNil)
|
||||
c.Assert(err, ErrorMatches, ".*returned 404.*")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURLsAndIgnoreMissing(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
p := MustLoadURLs([]string{srv.URL + "/a", srv.URL + "/b", srv.URL + "/c"}, true)
|
||||
assertKeyValues(c, "", p, "key", "value", "key2", "value2")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURLEncoding(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
|
||||
uris := []string{"/none", "/utf8", "/plain", "/latin1", "/iso88591"}
|
||||
for i, uri := range uris {
|
||||
p := MustLoadURL(srv.URL + uri)
|
||||
c.Assert(p.GetString("key", ""), Equals, "äöü", Commentf("%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadURLFailInvalidEncoding(c *C) {
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
|
||||
p, err := LoadURL(srv.URL + "/json")
|
||||
c.Assert(p, IsNil)
|
||||
c.Assert(err, ErrorMatches, ".*invalid content type.*")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TestLoadAll(c *C) {
|
||||
filename := s.makeFile(c, "key=value")
|
||||
filename2 := s.makeFile(c, "key2=value3")
|
||||
filename3 := s.makeFile(c, "key=value4")
|
||||
srv := testServer()
|
||||
defer srv.Close()
|
||||
p := MustLoadAll([]string{filename, filename2, srv.URL + "/a", srv.URL + "/b", filename3}, UTF8, false)
|
||||
assertKeyValues(c, "", p, "key", "value4", "key2", "value2")
|
||||
}
|
||||
|
||||
func (s *LoadSuite) SetUpSuite(c *C) {
|
||||
s.tempFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
func (s *LoadSuite) TearDownSuite(c *C) {
|
||||
for _, path := range s.tempFiles {
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
fmt.Printf("os.Remove: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LoadSuite) makeFile(c *C, data string) string {
|
||||
return s.makeFilePrefix(c, "properties", data)
|
||||
}
|
||||
|
||||
func (s *LoadSuite) makeFilePrefix(c *C, prefix, data string) string {
|
||||
f, err := ioutil.TempFile("", prefix)
|
||||
if err != nil {
|
||||
fmt.Printf("ioutil.TempFile: %v", err)
|
||||
c.FailNow()
|
||||
}
|
||||
|
||||
// remember the temp file so that we can remove it later
|
||||
s.tempFiles = append(s.tempFiles, f.Name())
|
||||
|
||||
n, err := fmt.Fprint(f, data)
|
||||
if err != nil {
|
||||
fmt.Printf("fmt.Fprintln: %v", err)
|
||||
c.FailNow()
|
||||
}
|
||||
if n != len(data) {
|
||||
fmt.Printf("Data size mismatch. expected=%d wrote=%d\n", len(data), n)
|
||||
c.FailNow()
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("f.Close: %v", err)
|
||||
c.FailNow()
|
||||
}
|
||||
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func testServer() *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
send := func(data []byte, contentType string) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
utf8 := []byte("key=äöü")
|
||||
iso88591 := []byte{0x6b, 0x65, 0x79, 0x3d, 0xe4, 0xf6, 0xfc} // key=äöü
|
||||
|
||||
switch r.RequestURI {
|
||||
case "/a":
|
||||
send([]byte("key=value"), "")
|
||||
case "/b":
|
||||
send([]byte("key2=value2"), "")
|
||||
case "/none":
|
||||
send(utf8, "")
|
||||
case "/utf8":
|
||||
send(utf8, "text/plain; charset=utf-8")
|
||||
case "/json":
|
||||
send(utf8, "application/json; charset=utf-8")
|
||||
case "/plain":
|
||||
send(iso88591, "text/plain")
|
||||
case "/latin1":
|
||||
send(iso88591, "text/plain; charset=latin1")
|
||||
case "/iso88591":
|
||||
send(iso88591, "text/plain; charset=iso-8859-1")
|
||||
default:
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
lex *lexer
|
||||
}
|
||||
|
||||
func parse(input string) (properties *Properties, err error) {
|
||||
p := &parser{lex: lex(input)}
|
||||
defer p.recover(&err)
|
||||
|
||||
properties = NewProperties()
|
||||
key := ""
|
||||
comments := []string{}
|
||||
|
||||
for {
|
||||
token := p.expectOneOf(itemComment, itemKey, itemEOF)
|
||||
switch token.typ {
|
||||
case itemEOF:
|
||||
goto done
|
||||
case itemComment:
|
||||
comments = append(comments, token.val)
|
||||
continue
|
||||
case itemKey:
|
||||
key = token.val
|
||||
if _, ok := properties.m[key]; !ok {
|
||||
properties.k = append(properties.k, key)
|
||||
}
|
||||
}
|
||||
|
||||
token = p.expectOneOf(itemValue, itemEOF)
|
||||
if len(comments) > 0 {
|
||||
properties.c[key] = comments
|
||||
comments = []string{}
|
||||
}
|
||||
switch token.typ {
|
||||
case itemEOF:
|
||||
properties.m[key] = ""
|
||||
goto done
|
||||
case itemValue:
|
||||
properties.m[key] = token.val
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
func (p *parser) errorf(format string, args ...interface{}) {
|
||||
format = fmt.Sprintf("properties: Line %d: %s", p.lex.lineNumber(), format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(expected itemType) (token item) {
|
||||
token = p.lex.nextItem()
|
||||
if token.typ != expected {
|
||||
p.unexpected(token)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *parser) expectOneOf(expected ...itemType) (token item) {
|
||||
token = p.lex.nextItem()
|
||||
for _, v := range expected {
|
||||
if token.typ == v {
|
||||
return token
|
||||
}
|
||||
}
|
||||
p.unexpected(token)
|
||||
panic("unexpected token")
|
||||
}
|
||||
|
||||
func (p *parser) unexpected(token item) {
|
||||
p.errorf(token.String())
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (p *parser) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,770 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer.
|
||||
// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrorHandlerFunc defines the type of function which handles failures
|
||||
// of the MustXXX() functions. An error handler function must exit
|
||||
// the application after handling the error.
|
||||
type ErrorHandlerFunc func(error)
|
||||
|
||||
// ErrorHandler is the function which handles failures of the MustXXX()
|
||||
// functions. The default is LogFatalHandler.
|
||||
var ErrorHandler ErrorHandlerFunc = LogFatalHandler
|
||||
|
||||
type LogHandlerFunc func(fmt string, args ...interface{})
|
||||
|
||||
var LogPrintf LogHandlerFunc = log.Printf
|
||||
|
||||
// LogFatalHandler handles the error by logging a fatal error and exiting.
|
||||
func LogFatalHandler(err error) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// PanicHandler handles the error by panicking.
|
||||
func PanicHandler(err error) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// A Properties contains the key/value pairs from the properties input.
|
||||
// All values are stored in unexpanded form and are expanded at runtime
|
||||
type Properties struct {
|
||||
// Pre-/Postfix for property expansion.
|
||||
Prefix string
|
||||
Postfix string
|
||||
|
||||
// DisableExpansion controls the expansion of properties on Get()
|
||||
// and the check for circular references on Set(). When set to
|
||||
// true Properties behaves like a simple key/value store and does
|
||||
// not check for circular references on Get() or on Set().
|
||||
DisableExpansion bool
|
||||
|
||||
// Stores the key/value pairs
|
||||
m map[string]string
|
||||
|
||||
// Stores the comments per key.
|
||||
c map[string][]string
|
||||
|
||||
// Stores the keys in order of appearance.
|
||||
k []string
|
||||
}
|
||||
|
||||
// NewProperties creates a new Properties struct with the default
|
||||
// configuration for "${key}" expressions.
|
||||
func NewProperties() *Properties {
|
||||
return &Properties{
|
||||
Prefix: "${",
|
||||
Postfix: "}",
|
||||
m: map[string]string{},
|
||||
c: map[string][]string{},
|
||||
k: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the expanded value for the given key if exists.
|
||||
// Otherwise, ok is false.
|
||||
func (p *Properties) Get(key string) (value string, ok bool) {
|
||||
v, ok := p.m[key]
|
||||
if p.DisableExpansion {
|
||||
return v, ok
|
||||
}
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
expanded, err := p.expand(v)
|
||||
|
||||
// we guarantee that the expanded value is free of
|
||||
// circular references and malformed expressions
|
||||
// so we panic if we still get an error here.
|
||||
if err != nil {
|
||||
ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v))
|
||||
}
|
||||
|
||||
return expanded, true
|
||||
}
|
||||
|
||||
// MustGet returns the expanded value for the given key if exists.
|
||||
// Otherwise, it panics.
|
||||
func (p *Properties) MustGet(key string) string {
|
||||
if v, ok := p.Get(key); ok {
|
||||
return v
|
||||
}
|
||||
ErrorHandler(invalidKeyError(key))
|
||||
panic("ErrorHandler should exit")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ClearComments removes the comments for all keys.
|
||||
func (p *Properties) ClearComments() {
|
||||
p.c = map[string][]string{}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetComment returns the last comment before the given key or an empty string.
|
||||
func (p *Properties) GetComment(key string) string {
|
||||
comments, ok := p.c[key]
|
||||
if !ok || len(comments) == 0 {
|
||||
return ""
|
||||
}
|
||||
return comments[len(comments)-1]
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetComments returns all comments that appeared before the given key or nil.
|
||||
func (p *Properties) GetComments(key string) []string {
|
||||
if comments, ok := p.c[key]; ok {
|
||||
return comments
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// SetComment sets the comment for the key.
|
||||
func (p *Properties) SetComment(key, comment string) {
|
||||
p.c[key] = []string{comment}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// SetComments sets the comments for the key. If the comments are nil then
|
||||
// all comments for this key are deleted.
|
||||
func (p *Properties) SetComments(key string, comments []string) {
|
||||
if comments == nil {
|
||||
delete(p.c, key)
|
||||
return
|
||||
}
|
||||
p.c[key] = comments
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetBool checks if the expanded value is one of '1', 'yes',
|
||||
// 'true' or 'on' if the key exists. The comparison is case-insensitive.
|
||||
// If the key does not exist the default value is returned.
|
||||
func (p *Properties) GetBool(key string, def bool) bool {
|
||||
v, err := p.getBool(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustGetBool checks if the expanded value is one of '1', 'yes',
|
||||
// 'true' or 'on' if the key exists. The comparison is case-insensitive.
|
||||
// If the key does not exist the function panics.
|
||||
func (p *Properties) MustGetBool(key string) bool {
|
||||
v, err := p.getBool(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *Properties) getBool(key string) (value bool, err error) {
|
||||
if v, ok := p.Get(key); ok {
|
||||
return boolVal(v), nil
|
||||
}
|
||||
return false, invalidKeyError(key)
|
||||
}
|
||||
|
||||
func boolVal(v string) bool {
|
||||
v = strings.ToLower(v)
|
||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetDuration parses the expanded value as an time.Duration (in ns) if the
|
||||
// key exists. If key does not exist or the value cannot be parsed the default
|
||||
// value is returned. In almost all cases you want to use GetParsedDuration().
|
||||
func (p *Properties) GetDuration(key string, def time.Duration) time.Duration {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return time.Duration(v)
|
||||
}
|
||||
|
||||
// MustGetDuration parses the expanded value as an time.Duration (in ns) if
|
||||
// the key exists. If key does not exist or the value cannot be parsed the
|
||||
// function panics. In almost all cases you want to use MustGetParsedDuration().
|
||||
func (p *Properties) MustGetDuration(key string) time.Duration {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return time.Duration(v)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned.
|
||||
func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration {
|
||||
s, ok := p.Get(key)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
v, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
func (p *Properties) MustGetParsedDuration(key string) time.Duration {
|
||||
s, ok := p.Get(key)
|
||||
if !ok {
|
||||
ErrorHandler(invalidKeyError(key))
|
||||
}
|
||||
v, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetFloat64 parses the expanded value as a float64 if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned.
|
||||
func (p *Properties) GetFloat64(key string, def float64) float64 {
|
||||
v, err := p.getFloat64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustGetFloat64 parses the expanded value as a float64 if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
func (p *Properties) MustGetFloat64(key string) float64 {
|
||||
v, err := p.getFloat64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *Properties) getFloat64(key string) (value float64, err error) {
|
||||
if v, ok := p.Get(key); ok {
|
||||
value, err = strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return 0, invalidKeyError(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetInt parses the expanded value as an int if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned. If the value does not fit into an int the
|
||||
// function panics with an out of range error.
|
||||
func (p *Properties) GetInt(key string, def int) int {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return intRangeCheck(key, v)
|
||||
}
|
||||
|
||||
// MustGetInt parses the expanded value as an int if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
// If the value does not fit into an int the function panics with
|
||||
// an out of range error.
|
||||
func (p *Properties) MustGetInt(key string) int {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return intRangeCheck(key, v)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetInt64 parses the expanded value as an int64 if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned.
|
||||
func (p *Properties) GetInt64(key string, def int64) int64 {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustGetInt64 parses the expanded value as an int if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
func (p *Properties) MustGetInt64(key string) int64 {
|
||||
v, err := p.getInt64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *Properties) getInt64(key string) (value int64, err error) {
|
||||
if v, ok := p.Get(key); ok {
|
||||
value, err = strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return 0, invalidKeyError(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetUint parses the expanded value as an uint if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned. If the value does not fit into an int the
|
||||
// function panics with an out of range error.
|
||||
func (p *Properties) GetUint(key string, def uint) uint {
|
||||
v, err := p.getUint64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return uintRangeCheck(key, v)
|
||||
}
|
||||
|
||||
// MustGetUint parses the expanded value as an int if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
// If the value does not fit into an int the function panics with
|
||||
// an out of range error.
|
||||
func (p *Properties) MustGetUint(key string) uint {
|
||||
v, err := p.getUint64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return uintRangeCheck(key, v)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetUint64 parses the expanded value as an uint64 if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the default
|
||||
// value is returned.
|
||||
func (p *Properties) GetUint64(key string, def uint64) uint64 {
|
||||
v, err := p.getUint64(key)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustGetUint64 parses the expanded value as an int if the key exists.
|
||||
// If key does not exist or the value cannot be parsed the function panics.
|
||||
func (p *Properties) MustGetUint64(key string) uint64 {
|
||||
v, err := p.getUint64(key)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *Properties) getUint64(key string) (value uint64, err error) {
|
||||
if v, ok := p.Get(key); ok {
|
||||
value, err = strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return 0, invalidKeyError(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetString returns the expanded value for the given key if exists or
|
||||
// the default value otherwise.
|
||||
func (p *Properties) GetString(key, def string) string {
|
||||
if v, ok := p.Get(key); ok {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// MustGetString returns the expanded value for the given key if exists or
|
||||
// panics otherwise.
|
||||
func (p *Properties) MustGetString(key string) string {
|
||||
if v, ok := p.Get(key); ok {
|
||||
return v
|
||||
}
|
||||
ErrorHandler(invalidKeyError(key))
|
||||
panic("ErrorHandler should exit")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Filter returns a new properties object which contains all properties
|
||||
// for which the key matches the pattern.
|
||||
func (p *Properties) Filter(pattern string) (*Properties, error) {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.FilterRegexp(re), nil
|
||||
}
|
||||
|
||||
// FilterRegexp returns a new properties object which contains all properties
|
||||
// for which the key matches the regular expression.
|
||||
func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties {
|
||||
pp := NewProperties()
|
||||
for _, k := range p.k {
|
||||
if re.MatchString(k) {
|
||||
pp.Set(k, p.m[k])
|
||||
}
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
||||
// FilterPrefix returns a new properties object with a subset of all keys
|
||||
// with the given prefix.
|
||||
func (p *Properties) FilterPrefix(prefix string) *Properties {
|
||||
pp := NewProperties()
|
||||
for _, k := range p.k {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
pp.Set(k, p.m[k])
|
||||
}
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
||||
// FilterStripPrefix returns a new properties object with a subset of all keys
|
||||
// with the given prefix and the prefix removed from the keys.
|
||||
func (p *Properties) FilterStripPrefix(prefix string) *Properties {
|
||||
pp := NewProperties()
|
||||
n := len(prefix)
|
||||
for _, k := range p.k {
|
||||
if len(k) > len(prefix) && strings.HasPrefix(k, prefix) {
|
||||
pp.Set(k[n:], p.m[k])
|
||||
}
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
||||
// Len returns the number of keys.
|
||||
func (p *Properties) Len() int {
|
||||
return len(p.m)
|
||||
}
|
||||
|
||||
// Keys returns all keys in the same order as in the input.
|
||||
func (p *Properties) Keys() []string {
|
||||
keys := make([]string, len(p.k))
|
||||
for i, k := range p.k {
|
||||
keys[i] = k
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Set sets the property key to the corresponding value.
|
||||
// If a value for key existed before then ok is true and prev
|
||||
// contains the previous value. If the value contains a
|
||||
// circular reference or a malformed expression then
|
||||
// an error is returned.
|
||||
// An empty key is silently ignored.
|
||||
func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
|
||||
if key == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// if expansion is disabled we allow circular references
|
||||
if p.DisableExpansion {
|
||||
prev, ok = p.Get(key)
|
||||
p.m[key] = value
|
||||
return prev, ok, nil
|
||||
}
|
||||
|
||||
// to check for a circular reference we temporarily need
|
||||
// to set the new value. If there is an error then revert
|
||||
// to the previous state. Only if all tests are successful
|
||||
// then we add the key to the p.k list.
|
||||
prev, ok = p.Get(key)
|
||||
p.m[key] = value
|
||||
|
||||
// now check for a circular reference
|
||||
_, err = p.expand(value)
|
||||
if err != nil {
|
||||
|
||||
// revert to the previous state
|
||||
if ok {
|
||||
p.m[key] = prev
|
||||
} else {
|
||||
delete(p.m, key)
|
||||
}
|
||||
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
p.k = append(p.k, key)
|
||||
}
|
||||
|
||||
return prev, ok, nil
|
||||
}
|
||||
|
||||
// MustSet sets the property key to the corresponding value.
|
||||
// If a value for key existed before then ok is true and prev
|
||||
// contains the previous value. An empty key is silently ignored.
|
||||
func (p *Properties) MustSet(key, value string) (prev string, ok bool) {
|
||||
prev, ok, err := p.Set(key, value)
|
||||
if err != nil {
|
||||
ErrorHandler(err)
|
||||
}
|
||||
return prev, ok
|
||||
}
|
||||
|
||||
// String returns a string of all expanded 'key = value' pairs.
|
||||
func (p *Properties) String() string {
|
||||
var s string
|
||||
for _, key := range p.k {
|
||||
value, _ := p.Get(key)
|
||||
s = fmt.Sprintf("%s%s = %s\n", s, key, value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Write writes all unexpanded 'key = value' pairs to the given writer.
|
||||
// Write returns the number of bytes written and any write error encountered.
|
||||
func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) {
|
||||
return p.WriteComment(w, "", enc)
|
||||
}
|
||||
|
||||
// WriteComment writes all unexpanced 'key = value' pairs to the given writer.
|
||||
// If prefix is not empty then comments are written with a blank line and the
|
||||
// given prefix. The prefix should be either "# " or "! " to be compatible with
|
||||
// the properties file format. Otherwise, the properties parser will not be
|
||||
// able to read the file back in. It returns the number of bytes written and
|
||||
// any write error encountered.
|
||||
func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) {
|
||||
var x int
|
||||
|
||||
for _, key := range p.k {
|
||||
value := p.m[key]
|
||||
|
||||
if prefix != "" {
|
||||
if comments, ok := p.c[key]; ok {
|
||||
// don't print comments if they are all empty
|
||||
allEmpty := true
|
||||
for _, c := range comments {
|
||||
if c != "" {
|
||||
allEmpty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allEmpty {
|
||||
// add a blank line between entries but not at the top
|
||||
if len(comments) > 0 && n > 0 {
|
||||
x, err = fmt.Fprintln(w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n += x
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n += x
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n += x
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Delete removes the key and its comments.
|
||||
func (p *Properties) Delete(key string) {
|
||||
delete(p.m, key)
|
||||
delete(p.c, key)
|
||||
newKeys := []string{}
|
||||
for _, k := range p.k {
|
||||
if k != key {
|
||||
newKeys = append(newKeys, key)
|
||||
}
|
||||
}
|
||||
p.k = newKeys
|
||||
}
|
||||
|
||||
// Merge merges properties, comments and keys from other *Properties into p
|
||||
func (p *Properties) Merge(other *Properties) {
|
||||
for k,v := range other.m {
|
||||
p.m[k] = v
|
||||
}
|
||||
for k,v := range other.c {
|
||||
p.c[k] = v
|
||||
}
|
||||
|
||||
outer:
|
||||
for _, otherKey := range other.k {
|
||||
for _, key := range p.k {
|
||||
if otherKey == key {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
p.k = append(p.k, otherKey)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// check expands all values and returns an error if a circular reference or
|
||||
// a malformed expression was found.
|
||||
func (p *Properties) check() error {
|
||||
for _, value := range p.m {
|
||||
if _, err := p.expand(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Properties) expand(input string) (string, error) {
|
||||
// no pre/postfix -> nothing to expand
|
||||
if p.Prefix == "" && p.Postfix == "" {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
return expand(input, make(map[string]bool), p.Prefix, p.Postfix, p.m)
|
||||
}
|
||||
|
||||
// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values.
|
||||
// The function keeps track of the keys that were already expanded and stops if it
|
||||
// detects a circular reference or a malformed expression of the form '(prefix)key'.
|
||||
func expand(s string, keys map[string]bool, prefix, postfix string, values map[string]string) (string, error) {
|
||||
start := strings.Index(s, prefix)
|
||||
if start == -1 {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
keyStart := start + len(prefix)
|
||||
keyLen := strings.Index(s[keyStart:], postfix)
|
||||
if keyLen == -1 {
|
||||
return "", fmt.Errorf("malformed expression")
|
||||
}
|
||||
|
||||
end := keyStart + keyLen + len(postfix) - 1
|
||||
key := s[keyStart : keyStart+keyLen]
|
||||
|
||||
// fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key)
|
||||
|
||||
if _, ok := keys[key]; ok {
|
||||
return "", fmt.Errorf("circular reference")
|
||||
}
|
||||
|
||||
val, ok := values[key]
|
||||
if !ok {
|
||||
val = os.Getenv(key)
|
||||
}
|
||||
|
||||
// remember that we've seen the key
|
||||
keys[key] = true
|
||||
|
||||
return expand(s[:start]+val+s[end+1:], keys, prefix, postfix, values)
|
||||
}
|
||||
|
||||
// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
|
||||
func encode(s string, special string, enc Encoding) string {
|
||||
switch enc {
|
||||
case UTF8:
|
||||
return encodeUtf8(s, special)
|
||||
case ISO_8859_1:
|
||||
return encodeIso(s, special)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported encoding %v", enc))
|
||||
}
|
||||
}
|
||||
|
||||
func encodeUtf8(s string, special string) string {
|
||||
v := ""
|
||||
for pos := 0; pos < len(s); {
|
||||
r, w := utf8.DecodeRuneInString(s[pos:])
|
||||
pos += w
|
||||
v += escape(r, special)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func encodeIso(s string, special string) string {
|
||||
var r rune
|
||||
var w int
|
||||
var v string
|
||||
for pos := 0; pos < len(s); {
|
||||
switch r, w = utf8.DecodeRuneInString(s[pos:]); {
|
||||
case r < 1<<8: // single byte rune -> escape special chars only
|
||||
v += escape(r, special)
|
||||
case r < 1<<16: // two byte rune -> unicode literal
|
||||
v += fmt.Sprintf("\\u%04x", r)
|
||||
default: // more than two bytes per rune -> can't encode
|
||||
v += "?"
|
||||
}
|
||||
pos += w
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func escape(r rune, special string) string {
|
||||
switch r {
|
||||
case '\f':
|
||||
return "\\f"
|
||||
case '\n':
|
||||
return "\\n"
|
||||
case '\r':
|
||||
return "\\r"
|
||||
case '\t':
|
||||
return "\\t"
|
||||
default:
|
||||
if strings.ContainsRune(special, r) {
|
||||
return "\\" + string(r)
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidKeyError(key string) error {
|
||||
return fmt.Errorf("unknown property: %s", key)
|
||||
}
|
||||
|
|
@ -0,0 +1,920 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/magiconair/properties/_third_party/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
type TestSuite struct {
|
||||
prevHandler ErrorHandlerFunc
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Suite(&TestSuite{})
|
||||
verbose = flag.Bool("verbose", false, "Verbose output")
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func (s *TestSuite) SetUpSuite(c *C) {
|
||||
s.prevHandler = ErrorHandler
|
||||
ErrorHandler = PanicHandler
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func (s *TestSuite) TearDownSuite(c *C) {
|
||||
ErrorHandler = s.prevHandler
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// define test cases in the form of
|
||||
// {"input", "key1", "value1", "key2", "value2", ...}
|
||||
var complexTests = [][]string{
|
||||
// whitespace prefix
|
||||
{" key=value", "key", "value"}, // SPACE prefix
|
||||
{"\fkey=value", "key", "value"}, // FF prefix
|
||||
{"\tkey=value", "key", "value"}, // TAB prefix
|
||||
{" \f\tkey=value", "key", "value"}, // mix prefix
|
||||
|
||||
// multiple keys
|
||||
{"key1=value1\nkey2=value2\n", "key1", "value1", "key2", "value2"},
|
||||
{"key1=value1\rkey2=value2\r", "key1", "value1", "key2", "value2"},
|
||||
{"key1=value1\r\nkey2=value2\r\n", "key1", "value1", "key2", "value2"},
|
||||
|
||||
// blank lines
|
||||
{"\nkey=value\n", "key", "value"},
|
||||
{"\rkey=value\r", "key", "value"},
|
||||
{"\r\nkey=value\r\n", "key", "value"},
|
||||
|
||||
// escaped chars in key
|
||||
{"k\\ ey = value", "k ey", "value"},
|
||||
{"k\\:ey = value", "k:ey", "value"},
|
||||
{"k\\=ey = value", "k=ey", "value"},
|
||||
{"k\\fey = value", "k\fey", "value"},
|
||||
{"k\\ney = value", "k\ney", "value"},
|
||||
{"k\\rey = value", "k\rey", "value"},
|
||||
{"k\\tey = value", "k\tey", "value"},
|
||||
|
||||
// escaped chars in value
|
||||
{"key = v\\ alue", "key", "v alue"},
|
||||
{"key = v\\:alue", "key", "v:alue"},
|
||||
{"key = v\\=alue", "key", "v=alue"},
|
||||
{"key = v\\falue", "key", "v\falue"},
|
||||
{"key = v\\nalue", "key", "v\nalue"},
|
||||
{"key = v\\ralue", "key", "v\ralue"},
|
||||
{"key = v\\talue", "key", "v\talue"},
|
||||
|
||||
// silently dropped escape character
|
||||
{"k\\zey = value", "kzey", "value"},
|
||||
{"key = v\\zalue", "key", "vzalue"},
|
||||
|
||||
// unicode literals
|
||||
{"key\\u2318 = value", "key⌘", "value"},
|
||||
{"k\\u2318ey = value", "k⌘ey", "value"},
|
||||
{"key = value\\u2318", "key", "value⌘"},
|
||||
{"key = valu\\u2318e", "key", "valu⌘e"},
|
||||
|
||||
// multiline values
|
||||
{"key = valueA,\\\n valueB", "key", "valueA,valueB"}, // SPACE indent
|
||||
{"key = valueA,\\\n\f\f\fvalueB", "key", "valueA,valueB"}, // FF indent
|
||||
{"key = valueA,\\\n\t\t\tvalueB", "key", "valueA,valueB"}, // TAB indent
|
||||
{"key = valueA,\\\n \f\tvalueB", "key", "valueA,valueB"}, // mix indent
|
||||
|
||||
// comments
|
||||
{"# this is a comment\n! and so is this\nkey1=value1\nkey#2=value#2\n\nkey!3=value!3\n# and another one\n! and the final one", "key1", "value1", "key#2", "value#2", "key!3", "value!3"},
|
||||
|
||||
// expansion tests
|
||||
{"key=value\nkey2=${key}", "key", "value", "key2", "value"},
|
||||
{"key=value\nkey2=aa${key}", "key", "value", "key2", "aavalue"},
|
||||
{"key=value\nkey2=${key}bb", "key", "value", "key2", "valuebb"},
|
||||
{"key=value\nkey2=aa${key}bb", "key", "value", "key2", "aavaluebb"},
|
||||
{"key=value\nkey2=${key}\nkey3=${key2}", "key", "value", "key2", "value", "key3", "value"},
|
||||
{"key=${USER}", "key", os.Getenv("USER")},
|
||||
{"key=${USER}\nUSER=value", "key", "value", "USER", "value"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var commentTests = []struct {
|
||||
input, key, value string
|
||||
comments []string
|
||||
}{
|
||||
{"key=value", "key", "value", nil},
|
||||
{"#\nkey=value", "key", "value", []string{""}},
|
||||
{"#comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"# comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"# comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"# comment\n\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"# comment1\n# comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}},
|
||||
{"# comment1\n\n# comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}},
|
||||
{"!comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"! comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"! comment\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"! comment\n\nkey=value", "key", "value", []string{"comment"}},
|
||||
{"! comment1\n! comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}},
|
||||
{"! comment1\n\n! comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var errorTests = []struct {
|
||||
input, msg string
|
||||
}{
|
||||
// unicode literals
|
||||
{"key\\u1 = value", "invalid unicode literal"},
|
||||
{"key\\u12 = value", "invalid unicode literal"},
|
||||
{"key\\u123 = value", "invalid unicode literal"},
|
||||
{"key\\u123g = value", "invalid unicode literal"},
|
||||
{"key\\u123", "invalid unicode literal"},
|
||||
|
||||
// circular references
|
||||
{"key=${key}", "circular reference"},
|
||||
{"key1=${key2}\nkey2=${key1}", "circular reference"},
|
||||
|
||||
// malformed expressions
|
||||
{"key=${ke", "malformed expression"},
|
||||
{"key=valu${ke", "malformed expression"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var writeTests = []struct {
|
||||
input, output, encoding string
|
||||
}{
|
||||
// ISO-8859-1 tests
|
||||
{"key = value", "key = value\n", "ISO-8859-1"},
|
||||
{"key = value \\\n continued", "key = value continued\n", "ISO-8859-1"},
|
||||
{"key⌘ = value", "key\\u2318 = value\n", "ISO-8859-1"},
|
||||
{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "ISO-8859-1"},
|
||||
|
||||
// UTF-8 tests
|
||||
{"key = value", "key = value\n", "UTF-8"},
|
||||
{"key = value \\\n continued", "key = value continued\n", "UTF-8"},
|
||||
{"key⌘ = value⌘", "key⌘ = value⌘\n", "UTF-8"},
|
||||
{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "UTF-8"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var writeCommentTests = []struct {
|
||||
input, output, encoding string
|
||||
}{
|
||||
// ISO-8859-1 tests
|
||||
{"key = value", "key = value\n", "ISO-8859-1"},
|
||||
{"#\nkey = value", "key = value\n", "ISO-8859-1"},
|
||||
{"#\n#\n#\nkey = value", "key = value\n", "ISO-8859-1"},
|
||||
{"# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
|
||||
{"\n# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
|
||||
{"# comment\n\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
|
||||
{"# comment1\n# comment2\nkey = value", "# comment1\n# comment2\nkey = value\n", "ISO-8859-1"},
|
||||
{"#comment1\nkey1 = value1\n#comment2\nkey2 = value2", "# comment1\nkey1 = value1\n\n# comment2\nkey2 = value2\n", "ISO-8859-1"},
|
||||
|
||||
// UTF-8 tests
|
||||
{"key = value", "key = value\n", "UTF-8"},
|
||||
{"# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
|
||||
{"\n# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
|
||||
{"# comment⌘\n\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
|
||||
{"# comment1⌘\n# comment2⌘\nkey = value⌘", "# comment1⌘\n# comment2⌘\nkey = value⌘\n", "UTF-8"},
|
||||
{"#comment1⌘\nkey1 = value1⌘\n#comment2⌘\nkey2 = value2⌘", "# comment1⌘\nkey1 = value1⌘\n\n# comment2⌘\nkey2 = value2⌘\n", "UTF-8"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var boolTests = []struct {
|
||||
input, key string
|
||||
def, value bool
|
||||
}{
|
||||
// valid values for TRUE
|
||||
{"key = 1", "key", false, true},
|
||||
{"key = on", "key", false, true},
|
||||
{"key = On", "key", false, true},
|
||||
{"key = ON", "key", false, true},
|
||||
{"key = true", "key", false, true},
|
||||
{"key = True", "key", false, true},
|
||||
{"key = TRUE", "key", false, true},
|
||||
{"key = yes", "key", false, true},
|
||||
{"key = Yes", "key", false, true},
|
||||
{"key = YES", "key", false, true},
|
||||
|
||||
// valid values for FALSE (all other)
|
||||
{"key = 0", "key", true, false},
|
||||
{"key = off", "key", true, false},
|
||||
{"key = false", "key", true, false},
|
||||
{"key = no", "key", true, false},
|
||||
|
||||
// non existent key
|
||||
{"key = true", "key2", false, false},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var durationTests = []struct {
|
||||
input, key string
|
||||
def, value time.Duration
|
||||
}{
|
||||
// valid values
|
||||
{"key = 1", "key", 999, 1},
|
||||
{"key = 0", "key", 999, 0},
|
||||
{"key = -1", "key", 999, -1},
|
||||
{"key = 0123", "key", 999, 123},
|
||||
|
||||
// invalid values
|
||||
{"key = 0xff", "key", 999, 999},
|
||||
{"key = 1.0", "key", 999, 999},
|
||||
{"key = a", "key", 999, 999},
|
||||
|
||||
// non existent key
|
||||
{"key = 1", "key2", 999, 999},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var parsedDurationTests = []struct {
|
||||
input, key string
|
||||
def, value time.Duration
|
||||
}{
|
||||
// valid values
|
||||
{"key = -1ns", "key", 999, -1 * time.Nanosecond},
|
||||
{"key = 300ms", "key", 999, 300 * time.Millisecond},
|
||||
{"key = 5s", "key", 999, 5 * time.Second},
|
||||
{"key = 3h", "key", 999, 3 * time.Hour},
|
||||
{"key = 2h45m", "key", 999, 2*time.Hour + 45*time.Minute},
|
||||
|
||||
// invalid values
|
||||
{"key = 0xff", "key", 999, 999},
|
||||
{"key = 1.0", "key", 999, 999},
|
||||
{"key = a", "key", 999, 999},
|
||||
{"key = 1", "key", 999, 999},
|
||||
{"key = 0", "key", 999, 0},
|
||||
|
||||
// non existent key
|
||||
{"key = 1", "key2", 999, 999},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var floatTests = []struct {
|
||||
input, key string
|
||||
def, value float64
|
||||
}{
|
||||
// valid values
|
||||
{"key = 1.0", "key", 999, 1.0},
|
||||
{"key = 0.0", "key", 999, 0.0},
|
||||
{"key = -1.0", "key", 999, -1.0},
|
||||
{"key = 1", "key", 999, 1},
|
||||
{"key = 0", "key", 999, 0},
|
||||
{"key = -1", "key", 999, -1},
|
||||
{"key = 0123", "key", 999, 123},
|
||||
|
||||
// invalid values
|
||||
{"key = 0xff", "key", 999, 999},
|
||||
{"key = a", "key", 999, 999},
|
||||
|
||||
// non existent key
|
||||
{"key = 1", "key2", 999, 999},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var int64Tests = []struct {
|
||||
input, key string
|
||||
def, value int64
|
||||
}{
|
||||
// valid values
|
||||
{"key = 1", "key", 999, 1},
|
||||
{"key = 0", "key", 999, 0},
|
||||
{"key = -1", "key", 999, -1},
|
||||
{"key = 0123", "key", 999, 123},
|
||||
|
||||
// invalid values
|
||||
{"key = 0xff", "key", 999, 999},
|
||||
{"key = 1.0", "key", 999, 999},
|
||||
{"key = a", "key", 999, 999},
|
||||
|
||||
// non existent key
|
||||
{"key = 1", "key2", 999, 999},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var uint64Tests = []struct {
|
||||
input, key string
|
||||
def, value uint64
|
||||
}{
|
||||
// valid values
|
||||
{"key = 1", "key", 999, 1},
|
||||
{"key = 0", "key", 999, 0},
|
||||
{"key = 0123", "key", 999, 123},
|
||||
|
||||
// invalid values
|
||||
{"key = -1", "key", 999, 999},
|
||||
{"key = 0xff", "key", 999, 999},
|
||||
{"key = 1.0", "key", 999, 999},
|
||||
{"key = a", "key", 999, 999},
|
||||
|
||||
// non existent key
|
||||
{"key = 1", "key2", 999, 999},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var stringTests = []struct {
|
||||
input, key string
|
||||
def, value string
|
||||
}{
|
||||
// valid values
|
||||
{"key = abc", "key", "def", "abc"},
|
||||
|
||||
// non existent key
|
||||
{"key = abc", "key2", "def", "def"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var keysTests = []struct {
|
||||
input string
|
||||
keys []string
|
||||
}{
|
||||
{"", []string{}},
|
||||
{"key = abc", []string{"key"}},
|
||||
{"key = abc\nkey2=def", []string{"key", "key2"}},
|
||||
{"key2 = abc\nkey=def", []string{"key2", "key"}},
|
||||
{"key = abc\nkey=def", []string{"key"}},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var filterTests = []struct {
|
||||
input string
|
||||
pattern string
|
||||
keys []string
|
||||
err string
|
||||
}{
|
||||
{"", "", []string{}, ""},
|
||||
{"", "abc", []string{}, ""},
|
||||
{"key=value", "", []string{"key"}, ""},
|
||||
{"key=value", "key=", []string{}, ""},
|
||||
{"key=value\nfoo=bar", "", []string{"foo", "key"}, ""},
|
||||
{"key=value\nfoo=bar", "f", []string{"foo"}, ""},
|
||||
{"key=value\nfoo=bar", "fo", []string{"foo"}, ""},
|
||||
{"key=value\nfoo=bar", "foo", []string{"foo"}, ""},
|
||||
{"key=value\nfoo=bar", "fooo", []string{}, ""},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "ey", []string{"key", "key2"}, ""},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}, ""},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "^key", []string{"key", "key2"}, ""},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "^(key|foo)", []string{"foo", "key", "key2"}, ""},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "[ abc", nil, "error parsing regexp.*"},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var filterPrefixTests = []struct {
|
||||
input string
|
||||
prefix string
|
||||
keys []string
|
||||
}{
|
||||
{"", "", []string{}},
|
||||
{"", "abc", []string{}},
|
||||
{"key=value", "", []string{"key"}},
|
||||
{"key=value", "key=", []string{}},
|
||||
{"key=value\nfoo=bar", "", []string{"foo", "key"}},
|
||||
{"key=value\nfoo=bar", "f", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "fo", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "foo", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "fooo", []string{}},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var filterStripPrefixTests = []struct {
|
||||
input string
|
||||
prefix string
|
||||
keys []string
|
||||
}{
|
||||
{"", "", []string{}},
|
||||
{"", "abc", []string{}},
|
||||
{"key=value", "", []string{"key"}},
|
||||
{"key=value", "key=", []string{}},
|
||||
{"key=value\nfoo=bar", "", []string{"foo", "key"}},
|
||||
{"key=value\nfoo=bar", "f", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "fo", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "foo", []string{"foo"}},
|
||||
{"key=value\nfoo=bar", "fooo", []string{}},
|
||||
{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var setTests = []struct {
|
||||
input string
|
||||
key, value string
|
||||
prev string
|
||||
ok bool
|
||||
err string
|
||||
keys []string
|
||||
}{
|
||||
{"", "", "", "", false, "", []string{}},
|
||||
{"", "key", "value", "", false, "", []string{"key"}},
|
||||
{"key=value", "key2", "value2", "", false, "", []string{"key", "key2"}},
|
||||
{"key=value", "abc", "value3", "", false, "", []string{"key", "abc"}},
|
||||
{"key=value", "key", "value3", "value", true, "", []string{"key"}},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// TestBasic tests basic single key/value combinations with all possible
|
||||
// whitespace, delimiter and newline permutations.
|
||||
func (s *TestSuite) TestBasic(c *C) {
|
||||
testWhitespaceAndDelimiterCombinations(c, "key", "")
|
||||
testWhitespaceAndDelimiterCombinations(c, "key", "value")
|
||||
testWhitespaceAndDelimiterCombinations(c, "key", "value ")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestComplex(c *C) {
|
||||
for _, test := range complexTests {
|
||||
testKeyValue(c, test[0], test[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestErrors(c *C) {
|
||||
for _, test := range errorTests {
|
||||
_, err := Load([]byte(test.input), ISO_8859_1)
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(strings.Contains(err.Error(), test.msg), Equals, true, Commentf("Expected %q got %q", test.msg, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestDisableExpansion(c *C) {
|
||||
input := "key=value\nkey2=${key}"
|
||||
p, err := parse(input)
|
||||
p.DisableExpansion = true
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGet("key"), Equals, "value")
|
||||
c.Assert(p.MustGet("key2"), Equals, "${key}")
|
||||
|
||||
// with expansion disabled we can introduce circular references
|
||||
p.Set("keyA", "${keyB}")
|
||||
p.Set("keyB", "${keyA}")
|
||||
c.Assert(p.MustGet("keyA"), Equals, "${keyB}")
|
||||
c.Assert(p.MustGet("keyB"), Equals, "${keyA}")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGet(c *C) {
|
||||
input := "key = value\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGet("key"), Equals, "value")
|
||||
c.Assert(func() { p.MustGet("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetBool(c *C) {
|
||||
for _, test := range boolTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetBool(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetBool(c *C) {
|
||||
input := "key = true\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetBool("key"), Equals, true)
|
||||
c.Assert(func() { p.MustGetBool("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetDuration(c *C) {
|
||||
for _, test := range durationTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetDuration(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetDuration(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetDuration("key"), Equals, time.Duration(123))
|
||||
c.Assert(func() { p.MustGetDuration("key2") }, PanicMatches, "strconv.ParseInt: parsing.*")
|
||||
c.Assert(func() { p.MustGetDuration("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetParsedDuration(c *C) {
|
||||
for _, test := range parsedDurationTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetParsedDuration(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetParsedDuration(c *C) {
|
||||
input := "key = 123ms\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetParsedDuration("key"), Equals, 123*time.Millisecond)
|
||||
c.Assert(func() { p.MustGetParsedDuration("key2") }, PanicMatches, "time: invalid duration ghi")
|
||||
c.Assert(func() { p.MustGetParsedDuration("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetFloat64(c *C) {
|
||||
for _, test := range floatTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetFloat64(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetFloat64(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetFloat64("key"), Equals, float64(123))
|
||||
c.Assert(func() { p.MustGetFloat64("key2") }, PanicMatches, "strconv.ParseFloat: parsing.*")
|
||||
c.Assert(func() { p.MustGetFloat64("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetInt(c *C) {
|
||||
for _, test := range int64Tests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetInt(test.key, int(test.def)), Equals, int(test.value))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetInt(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetInt("key"), Equals, int(123))
|
||||
c.Assert(func() { p.MustGetInt("key2") }, PanicMatches, "strconv.ParseInt: parsing.*")
|
||||
c.Assert(func() { p.MustGetInt("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetInt64(c *C) {
|
||||
for _, test := range int64Tests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetInt64(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetInt64(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetInt64("key"), Equals, int64(123))
|
||||
c.Assert(func() { p.MustGetInt64("key2") }, PanicMatches, "strconv.ParseInt: parsing.*")
|
||||
c.Assert(func() { p.MustGetInt64("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetUint(c *C) {
|
||||
for _, test := range uint64Tests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetUint(test.key, uint(test.def)), Equals, uint(test.value))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetUint(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetUint("key"), Equals, uint(123))
|
||||
c.Assert(func() { p.MustGetUint64("key2") }, PanicMatches, "strconv.ParseUint: parsing.*")
|
||||
c.Assert(func() { p.MustGetUint64("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetUint64(c *C) {
|
||||
for _, test := range uint64Tests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetUint64(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetUint64(c *C) {
|
||||
input := "key = 123\nkey2 = ghi"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetUint64("key"), Equals, uint64(123))
|
||||
c.Assert(func() { p.MustGetUint64("key2") }, PanicMatches, "strconv.ParseUint: parsing.*")
|
||||
c.Assert(func() { p.MustGetUint64("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestGetString(c *C) {
|
||||
for _, test := range stringTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, 1)
|
||||
c.Assert(p.GetString(test.key, test.def), Equals, test.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustGetString(c *C) {
|
||||
input := `key = value`
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetString("key"), Equals, "value")
|
||||
c.Assert(func() { p.MustGetString("invalid") }, PanicMatches, "unknown property: invalid")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestComment(c *C) {
|
||||
for _, test := range commentTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetString(test.key), Equals, test.value)
|
||||
c.Assert(p.GetComments(test.key), DeepEquals, test.comments)
|
||||
if test.comments != nil {
|
||||
c.Assert(p.GetComment(test.key), Equals, test.comments[len(test.comments)-1])
|
||||
} else {
|
||||
c.Assert(p.GetComment(test.key), Equals, "")
|
||||
}
|
||||
|
||||
// test setting comments
|
||||
if len(test.comments) > 0 {
|
||||
// set single comment
|
||||
p.ClearComments()
|
||||
c.Assert(len(p.c), Equals, 0)
|
||||
p.SetComment(test.key, test.comments[0])
|
||||
c.Assert(p.GetComment(test.key), Equals, test.comments[0])
|
||||
|
||||
// set multiple comments
|
||||
p.ClearComments()
|
||||
c.Assert(len(p.c), Equals, 0)
|
||||
p.SetComments(test.key, test.comments)
|
||||
c.Assert(p.GetComments(test.key), DeepEquals, test.comments)
|
||||
|
||||
// clear comments for a key
|
||||
p.SetComments(test.key, nil)
|
||||
c.Assert(p.GetComment(test.key), Equals, "")
|
||||
c.Assert(p.GetComments(test.key), IsNil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestFilter(c *C) {
|
||||
for _, test := range filterTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
pp, err := p.Filter(test.pattern)
|
||||
if err != nil {
|
||||
c.Assert(err, ErrorMatches, test.err)
|
||||
continue
|
||||
}
|
||||
c.Assert(pp, NotNil)
|
||||
c.Assert(pp.Len(), Equals, len(test.keys))
|
||||
for _, key := range test.keys {
|
||||
v1, ok1 := p.Get(key)
|
||||
v2, ok2 := pp.Get(key)
|
||||
c.Assert(ok1, Equals, true)
|
||||
c.Assert(ok2, Equals, true)
|
||||
c.Assert(v1, Equals, v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestFilterPrefix(c *C) {
|
||||
for _, test := range filterPrefixTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
pp := p.FilterPrefix(test.prefix)
|
||||
c.Assert(pp, NotNil)
|
||||
c.Assert(pp.Len(), Equals, len(test.keys))
|
||||
for _, key := range test.keys {
|
||||
v1, ok1 := p.Get(key)
|
||||
v2, ok2 := pp.Get(key)
|
||||
c.Assert(ok1, Equals, true)
|
||||
c.Assert(ok2, Equals, true)
|
||||
c.Assert(v1, Equals, v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestKeys(c *C) {
|
||||
for _, test := range keysTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.Len(), Equals, len(test.keys))
|
||||
c.Assert(len(p.Keys()), Equals, len(test.keys))
|
||||
c.Assert(p.Keys(), DeepEquals, test.keys)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestSet(c *C) {
|
||||
for _, test := range setTests {
|
||||
p, err := parse(test.input)
|
||||
c.Assert(err, IsNil)
|
||||
prev, ok, err := p.Set(test.key, test.value)
|
||||
if test.err != "" {
|
||||
c.Assert(err, ErrorMatches, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(ok, Equals, test.ok)
|
||||
if ok {
|
||||
c.Assert(prev, Equals, test.prev)
|
||||
}
|
||||
c.Assert(p.Keys(), DeepEquals, test.keys)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMustSet(c *C) {
|
||||
input := "key=${key}"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(func() { p.MustSet("key", "${key}") }, PanicMatches, "circular reference .*")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestWrite(c *C) {
|
||||
for _, test := range writeTests {
|
||||
p, err := parse(test.input)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
var n int
|
||||
switch test.encoding {
|
||||
case "UTF-8":
|
||||
n, err = p.Write(buf, UTF8)
|
||||
case "ISO-8859-1":
|
||||
n, err = p.Write(buf, ISO_8859_1)
|
||||
}
|
||||
c.Assert(err, IsNil)
|
||||
s := string(buf.Bytes())
|
||||
c.Assert(n, Equals, len(test.output), Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s))
|
||||
c.Assert(s, Equals, test.output, Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestWriteComment(c *C) {
|
||||
for _, test := range writeCommentTests {
|
||||
p, err := parse(test.input)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
var n int
|
||||
switch test.encoding {
|
||||
case "UTF-8":
|
||||
n, err = p.WriteComment(buf, "# ", UTF8)
|
||||
case "ISO-8859-1":
|
||||
n, err = p.WriteComment(buf, "# ", ISO_8859_1)
|
||||
}
|
||||
c.Assert(err, IsNil)
|
||||
s := string(buf.Bytes())
|
||||
c.Assert(n, Equals, len(test.output), Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s))
|
||||
c.Assert(s, Equals, test.output, Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestCustomExpansionExpression(c *C) {
|
||||
testKeyValuePrePostfix(c, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestPanicOn32BitIntOverflow(c *C) {
|
||||
is32Bit = true
|
||||
var min, max int64 = math.MinInt32 - 1, math.MaxInt32 + 1
|
||||
input := fmt.Sprintf("min=%d\nmax=%d", min, max)
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetInt64("min"), Equals, min)
|
||||
c.Assert(p.MustGetInt64("max"), Equals, max)
|
||||
c.Assert(func() { p.MustGetInt("min") }, PanicMatches, ".* out of range")
|
||||
c.Assert(func() { p.MustGetInt("max") }, PanicMatches, ".* out of range")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestPanicOn32BitUintOverflow(c *C) {
|
||||
is32Bit = true
|
||||
var max uint64 = math.MaxUint32 + 1
|
||||
input := fmt.Sprintf("max=%d", max)
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.MustGetUint64("max"), Equals, max)
|
||||
c.Assert(func() { p.MustGetUint("max") }, PanicMatches, ".* out of range")
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestDeleteKey(c *C) {
|
||||
input := "#comments should also be gone\nkey=to-be-deleted\nsecond=key"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(len(p.m), Equals, 2)
|
||||
c.Check(len(p.c), Equals, 1)
|
||||
c.Check(len(p.k), Equals, 2)
|
||||
p.Delete("key")
|
||||
c.Check(len(p.m), Equals, 1)
|
||||
c.Check(len(p.c), Equals, 0)
|
||||
c.Check(len(p.k), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestDeleteUnknownKey(c *C) {
|
||||
input := "#comments should also be gone\nkey=to-be-deleted"
|
||||
p, err := parse(input)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(len(p.m), Equals, 1)
|
||||
c.Check(len(p.c), Equals, 1)
|
||||
c.Check(len(p.k), Equals, 1)
|
||||
p.Delete("wrong-key")
|
||||
c.Check(len(p.m), Equals, 1)
|
||||
c.Check(len(p.c), Equals, 1)
|
||||
c.Check(len(p.k), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestMerge(c *C) {
|
||||
input1 := "#comment\nkey=value\nkey2=value2"
|
||||
input2 := "#another comment\nkey=another value\nkey3=value3"
|
||||
p1, err := parse(input1)
|
||||
c.Assert(err, IsNil)
|
||||
p2, err := parse(input2)
|
||||
p1.Merge(p2)
|
||||
c.Check(len(p1.m), Equals, 3)
|
||||
c.Check(len(p1.c), Equals, 1)
|
||||
c.Check(len(p1.k), Equals, 3)
|
||||
c.Check(p1.MustGet("key"), Equals, "another value")
|
||||
c.Check(p1.GetComment("key"), Equals, "another comment")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// tests all combinations of delimiters, leading and/or trailing whitespace and newlines.
|
||||
func testWhitespaceAndDelimiterCombinations(c *C, key, value string) {
|
||||
whitespace := []string{"", " ", "\f", "\t"}
|
||||
delimiters := []string{"", " ", "=", ":"}
|
||||
newlines := []string{"", "\r", "\n", "\r\n"}
|
||||
for _, dl := range delimiters {
|
||||
for _, ws1 := range whitespace {
|
||||
for _, ws2 := range whitespace {
|
||||
for _, nl := range newlines {
|
||||
// skip the one case where there is nothing between a key and a value
|
||||
if ws1 == "" && dl == "" && ws2 == "" && value != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
input := fmt.Sprintf("%s%s%s%s%s%s", key, ws1, dl, ws2, value, nl)
|
||||
testKeyValue(c, input, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests whether key/value pairs exist for a given input.
|
||||
// keyvalues is expected to be an even number of strings of "key", "value", ...
|
||||
func testKeyValue(c *C, input string, keyvalues ...string) {
|
||||
testKeyValuePrePostfix(c, "${", "}", input, keyvalues...)
|
||||
}
|
||||
|
||||
// tests whether key/value pairs exist for a given input.
|
||||
// keyvalues is expected to be an even number of strings of "key", "value", ...
|
||||
func testKeyValuePrePostfix(c *C, prefix, postfix, input string, keyvalues ...string) {
|
||||
printf("%q\n", input)
|
||||
|
||||
p, err := Load([]byte(input), ISO_8859_1)
|
||||
c.Assert(err, IsNil)
|
||||
p.Prefix = prefix
|
||||
p.Postfix = postfix
|
||||
assertKeyValues(c, input, p, keyvalues...)
|
||||
}
|
||||
|
||||
// tests whether key/value pairs exist for a given input.
|
||||
// keyvalues is expected to be an even number of strings of "key", "value", ...
|
||||
func assertKeyValues(c *C, input string, p *Properties, keyvalues ...string) {
|
||||
c.Assert(p, NotNil)
|
||||
c.Assert(2*p.Len(), Equals, len(keyvalues), Commentf("Odd number of key/value pairs."))
|
||||
|
||||
for i := 0; i < len(keyvalues); i += 2 {
|
||||
key, value := keyvalues[i], keyvalues[i+1]
|
||||
v, ok := p.Get(key)
|
||||
c.Assert(ok, Equals, true, Commentf("No key %q found (input=%q)", key, input))
|
||||
c.Assert(v, Equals, value, Commentf("Value %q does not match %q (input=%q)", v, value, input))
|
||||
}
|
||||
}
|
||||
|
||||
// prints to stderr if the -verbose flag was given.
|
||||
func printf(format string, args ...interface{}) {
|
||||
if *verbose {
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2016 Frank Schroeder. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// make this a var to overwrite it in a test
|
||||
var is32Bit = ^uint(0) == math.MaxUint32
|
||||
|
||||
// intRangeCheck checks if the value fits into the int type and
|
||||
// panics if it does not.
|
||||
func intRangeCheck(key string, v int64) int {
|
||||
if is32Bit && (v < math.MinInt32 || v > math.MaxInt32) {
|
||||
panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
// uintRangeCheck checks if the value fits into the uint type and
|
||||
// panics if it does not.
|
||||
func uintRangeCheck(key string, v uint64) uint {
|
||||
if is32Bit && v > math.MaxUint32 {
|
||||
panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
|
||||
}
|
||||
return uint(v)
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
|
||||
script:
|
||||
- go test
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# mapstructure
|
||||
|
||||
mapstructure is a Go library for decoding generic map values to structures
|
||||
and vice versa, while providing helpful error handling.
|
||||
|
||||
This library is most useful when decoding values from some data stream (JSON,
|
||||
Gob, etc.) where you don't _quite_ know the structure of the underlying data
|
||||
until you read a part of it. You can therefore read a `map[string]interface{}`
|
||||
and use this library to decode it into the proper underlying native Go
|
||||
structure.
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/mapstructure
|
||||
```
|
||||
|
||||
## Usage & Example
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
|
||||
|
||||
The `Decode` function has examples associated with it there.
|
||||
|
||||
## But Why?!
|
||||
|
||||
Go offers fantastic standard libraries for decoding formats such as JSON.
|
||||
The standard method is to have a struct pre-created, and populate that struct
|
||||
from the bytes of the encoded format. This is great, but the problem is if
|
||||
you have configuration or an encoding that changes slightly depending on
|
||||
specific fields. For example, consider this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "person",
|
||||
"name": "Mitchell"
|
||||
}
|
||||
```
|
||||
|
||||
Perhaps we can't populate a specific structure without first reading
|
||||
the "type" field from the JSON. We could always do two passes over the
|
||||
decoding of the JSON (reading the "type" first, and the rest later).
|
||||
However, it is much simpler to just decode this into a `map[string]interface{}`
|
||||
structure, read the "type" key, then use something like this library
|
||||
to decode it into the proper structure.
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
||||
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
||||
// Create variables here so we can reference them with the reflect pkg
|
||||
var f1 DecodeHookFuncType
|
||||
var f2 DecodeHookFuncKind
|
||||
|
||||
// Fill in the variables into this interface and the rest is done
|
||||
// automatically using the reflect package.
|
||||
potential := []interface{}{f1, f2}
|
||||
|
||||
v := reflect.ValueOf(h)
|
||||
vt := v.Type()
|
||||
for _, raw := range potential {
|
||||
pt := reflect.ValueOf(raw).Type()
|
||||
if vt.ConvertibleTo(pt) {
|
||||
return v.Convert(pt).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeHookExec executes the given decode hook. This should be used
|
||||
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||
// that took reflect.Kind instead of reflect.Type.
|
||||
func DecodeHookExec(
|
||||
raw DecodeHookFunc,
|
||||
from reflect.Type, to reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
// Build our arguments that reflect expects
|
||||
argVals := make([]reflect.Value, 3)
|
||||
argVals[0] = reflect.ValueOf(from)
|
||||
argVals[1] = reflect.ValueOf(to)
|
||||
argVals[2] = reflect.ValueOf(data)
|
||||
|
||||
switch f := typedDecodeHook(raw).(type) {
|
||||
case DecodeHookFuncType:
|
||||
return f(from, to, data)
|
||||
case DecodeHookFuncKind:
|
||||
return f(from.Kind(), to.Kind(), data)
|
||||
default:
|
||||
return nil, errors.New("invalid decode hook signature")
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||
// automatically composes multiple DecodeHookFuncs.
|
||||
//
|
||||
// The composed funcs are called in order, with the result of the
|
||||
// previous transformation.
|
||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, f1 := range fs {
|
||||
data, err = DecodeHookExec(f1, f, t, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Modify the from kind to be correct with the new data
|
||||
f = nil
|
||||
if val := reflect.ValueOf(data); val.IsValid() {
|
||||
f = val.Type()
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||
// string to []string by splitting on the given sep.
|
||||
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f != reflect.String || t != reflect.Slice {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return strings.Split(raw, sep), nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Duration.
|
||||
func StringToTimeDurationHookFunc() DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(time.Duration(5)) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
return time.ParseDuration(data.(string))
|
||||
}
|
||||
}
|
||||
|
||||
func WeaklyTypedHook(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
switch t {
|
||||
case reflect.String:
|
||||
switch f {
|
||||
case reflect.Bool:
|
||||
if dataVal.Bool() {
|
||||
return "1", nil
|
||||
} else {
|
||||
return "0", nil
|
||||
}
|
||||
case reflect.Float32:
|
||||
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
||||
case reflect.Int:
|
||||
return strconv.FormatInt(dataVal.Int(), 10), nil
|
||||
case reflect.Slice:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
if elemKind == reflect.Uint8 {
|
||||
return string(dataVal.Interface().([]uint8)), nil
|
||||
}
|
||||
case reflect.Uint:
|
||||
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestComposeDecodeHookFunc(t *testing.T) {
|
||||
f1 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return data.(string) + "foo", nil
|
||||
}
|
||||
|
||||
f2 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return data.(string) + "bar", nil
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
result, err := DecodeHookExec(
|
||||
f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
if result.(string) != "foobar" {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDecodeHookFunc_err(t *testing.T) {
|
||||
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||
panic("NOPE")
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
_, err := DecodeHookExec(
|
||||
f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), 42)
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
|
||||
var f2From reflect.Kind
|
||||
|
||||
f1 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return int(42), nil
|
||||
}
|
||||
|
||||
f2 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
f2From = f
|
||||
return data, nil
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
_, err := DecodeHookExec(
|
||||
f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
if f2From != reflect.Int {
|
||||
t.Fatalf("bad: %#v", f2From)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToSliceHookFunc(t *testing.T) {
|
||||
f := StringToSliceHookFunc(",")
|
||||
|
||||
strType := reflect.TypeOf("")
|
||||
sliceType := reflect.TypeOf([]byte(""))
|
||||
cases := []struct {
|
||||
f, t reflect.Type
|
||||
data interface{}
|
||||
result interface{}
|
||||
err bool
|
||||
}{
|
||||
{sliceType, sliceType, 42, 42, false},
|
||||
{strType, strType, 42, 42, false},
|
||||
{
|
||||
strType,
|
||||
sliceType,
|
||||
"foo,bar,baz",
|
||||
[]string{"foo", "bar", "baz"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
strType,
|
||||
sliceType,
|
||||
"",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
|
||||
if tc.err != (err != nil) {
|
||||
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tc.result) {
|
||||
t.Fatalf(
|
||||
"case %d: expected %#v, got %#v",
|
||||
i, tc.result, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToTimeDurationHookFunc(t *testing.T) {
|
||||
f := StringToTimeDurationHookFunc()
|
||||
|
||||
strType := reflect.TypeOf("")
|
||||
timeType := reflect.TypeOf(time.Duration(5))
|
||||
cases := []struct {
|
||||
f, t reflect.Type
|
||||
data interface{}
|
||||
result interface{}
|
||||
err bool
|
||||
}{
|
||||
{strType, timeType, "5s", 5 * time.Second, false},
|
||||
{strType, timeType, "5", time.Duration(0), true},
|
||||
{strType, strType, "5", "5", false},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
|
||||
if tc.err != (err != nil) {
|
||||
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tc.result) {
|
||||
t.Fatalf(
|
||||
"case %d: expected %#v, got %#v",
|
||||
i, tc.result, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeaklyTypedHook(t *testing.T) {
|
||||
var f DecodeHookFunc = WeaklyTypedHook
|
||||
|
||||
boolType := reflect.TypeOf(true)
|
||||
strType := reflect.TypeOf("")
|
||||
sliceType := reflect.TypeOf([]byte(""))
|
||||
cases := []struct {
|
||||
f, t reflect.Type
|
||||
data interface{}
|
||||
result interface{}
|
||||
err bool
|
||||
}{
|
||||
// TO STRING
|
||||
{
|
||||
boolType,
|
||||
strType,
|
||||
false,
|
||||
"0",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
boolType,
|
||||
strType,
|
||||
true,
|
||||
"1",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.TypeOf(float32(1)),
|
||||
strType,
|
||||
float32(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.TypeOf(int(1)),
|
||||
strType,
|
||||
int(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
sliceType,
|
||||
strType,
|
||||
[]uint8("foo"),
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.TypeOf(uint(1)),
|
||||
strType,
|
||||
uint(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
|
||||
if tc.err != (err != nil) {
|
||||
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tc.result) {
|
||||
t.Fatalf(
|
||||
"case %d: expected %#v, got %#v",
|
||||
i, tc.result, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error implements the error interface and can represents multiple
|
||||
// errors that occur in the course of a single decode.
|
||||
type Error struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
points := make([]string, len(e.Errors))
|
||||
for i, err := range e.Errors {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
}
|
||||
|
||||
sort.Strings(points)
|
||||
return fmt.Sprintf(
|
||||
"%d error(s) decoding:\n\n%s",
|
||||
len(e.Errors), strings.Join(points, "\n"))
|
||||
}
|
||||
|
||||
// WrappedErrors implements the errwrap.Wrapper interface to make this
|
||||
// return value more useful with the errwrap and go-multierror libraries.
|
||||
func (e *Error) WrappedErrors() []error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]error, len(e.Errors))
|
||||
for i, e := range e.Errors {
|
||||
result[i] = errors.New(e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func appendErrors(errors []string, err error) []string {
|
||||
switch e := err.(type) {
|
||||
case *Error:
|
||||
return append(errors, e.Errors...)
|
||||
default:
|
||||
return append(errors, e.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,771 @@
|
|||
// The mapstructure package exposes functionality to convert an
|
||||
// abitrary map[string]interface{} into a native Go structure.
|
||||
//
|
||||
// The Go structure can be arbitrarily complex, containing slices,
|
||||
// other structs, etc. and the decoder will properly decode nested
|
||||
// maps and so on into the proper structures in the native Go struct.
|
||||
// See the examples to see what the decoder is capable of.
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DecodeHookFunc is the callback function that can be used for
|
||||
// data transformations. See "DecodeHook" in the DecoderConfig
|
||||
// struct.
|
||||
//
|
||||
// The type should be DecodeHookFuncType or DecodeHookFuncKind.
|
||||
// Either is accepted. Types are a superset of Kinds (Types can return
|
||||
// Kinds) and are generally a richer thing to use, but Kinds are simpler
|
||||
// if you only need those.
|
||||
//
|
||||
// The reason DecodeHookFunc is multi-typed is for backwards compatibility:
|
||||
// we started with Kinds and then realized Types were the better solution,
|
||||
// but have a promise to not break backwards compat so we now support
|
||||
// both.
|
||||
type DecodeHookFunc interface{}
|
||||
|
||||
type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
|
||||
type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
|
||||
|
||||
// DecoderConfig is the configuration that is used to create a new decoder
|
||||
// and allows customization of various aspects of decoding.
|
||||
type DecoderConfig struct {
|
||||
// DecodeHook, if set, will be called before any decoding and any
|
||||
// type conversion (if WeaklyTypedInput is on). This lets you modify
|
||||
// the values before they're set down onto the resulting struct.
|
||||
//
|
||||
// If an error is returned, the entire decode will fail with that
|
||||
// error.
|
||||
DecodeHook DecodeHookFunc
|
||||
|
||||
// If ErrorUnused is true, then it is an error for there to exist
|
||||
// keys in the original map that were unused in the decoding process
|
||||
// (extra keys).
|
||||
ErrorUnused bool
|
||||
|
||||
// ZeroFields, if set to true, will zero fields before writing them.
|
||||
// For example, a map will be emptied before decoded values are put in
|
||||
// it. If this is false, a map will be merged.
|
||||
ZeroFields bool
|
||||
|
||||
// If WeaklyTypedInput is true, the decoder will make the following
|
||||
// "weak" conversions:
|
||||
//
|
||||
// - bools to string (true = "1", false = "0")
|
||||
// - numbers to string (base 10)
|
||||
// - bools to int/uint (true = 1, false = 0)
|
||||
// - strings to int/uint (base implied by prefix)
|
||||
// - int to bool (true if value != 0)
|
||||
// - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
|
||||
// FALSE, false, False. Anything else is an error)
|
||||
// - empty array = empty map and vice versa
|
||||
// - negative numbers to overflowed uint values (base 10)
|
||||
// - slice of maps to a merged map
|
||||
//
|
||||
WeaklyTypedInput bool
|
||||
|
||||
// Metadata is the struct that will contain extra metadata about
|
||||
// the decoding. If this is nil, then no metadata will be tracked.
|
||||
Metadata *Metadata
|
||||
|
||||
// Result is a pointer to the struct that will contain the decoded
|
||||
// value.
|
||||
Result interface{}
|
||||
|
||||
// The tag name that mapstructure reads for field names. This
|
||||
// defaults to "mapstructure"
|
||||
TagName string
|
||||
}
|
||||
|
||||
// A Decoder takes a raw interface value and turns it into structured
|
||||
// data, keeping track of rich error information along the way in case
|
||||
// anything goes wrong. Unlike the basic top-level Decode method, you can
|
||||
// more finely control how the Decoder behaves using the DecoderConfig
|
||||
// structure. The top-level Decode method is just a convenience that sets
|
||||
// up the most basic Decoder.
|
||||
type Decoder struct {
|
||||
config *DecoderConfig
|
||||
}
|
||||
|
||||
// Metadata contains information about decoding a structure that
|
||||
// is tedious or difficult to get otherwise.
|
||||
type Metadata struct {
|
||||
// Keys are the keys of the structure which were successfully decoded
|
||||
Keys []string
|
||||
|
||||
// Unused is a slice of keys that were found in the raw value but
|
||||
// weren't decoded since there was no matching field in the result interface
|
||||
Unused []string
|
||||
}
|
||||
|
||||
// Decode takes a map and uses reflection to convert it into the
|
||||
// given Go native structure. val must be a pointer to a struct.
|
||||
func Decode(m interface{}, rawVal interface{}) error {
|
||||
config := &DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: rawVal,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decoder.Decode(m)
|
||||
}
|
||||
|
||||
// WeakDecode is the same as Decode but is shorthand to enable
|
||||
// WeaklyTypedInput. See DecoderConfig for more info.
|
||||
func WeakDecode(input, output interface{}) error {
|
||||
config := &DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: output,
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decoder.Decode(input)
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder for the given configuration. Once
|
||||
// a decoder has been returned, the same configuration must not be used
|
||||
// again.
|
||||
func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||
val := reflect.ValueOf(config.Result)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return nil, errors.New("result must be a pointer")
|
||||
}
|
||||
|
||||
val = val.Elem()
|
||||
if !val.CanAddr() {
|
||||
return nil, errors.New("result must be addressable (a pointer)")
|
||||
}
|
||||
|
||||
if config.Metadata != nil {
|
||||
if config.Metadata.Keys == nil {
|
||||
config.Metadata.Keys = make([]string, 0)
|
||||
}
|
||||
|
||||
if config.Metadata.Unused == nil {
|
||||
config.Metadata.Unused = make([]string, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if config.TagName == "" {
|
||||
config.TagName = "mapstructure"
|
||||
}
|
||||
|
||||
result := &Decoder{
|
||||
config: config,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Decode decodes the given raw interface to the target pointer specified
|
||||
// by the configuration.
|
||||
func (d *Decoder) Decode(raw interface{}) error {
|
||||
return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
|
||||
}
|
||||
|
||||
// Decodes an unknown data type into a specific reflection value.
|
||||
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
|
||||
if data == nil {
|
||||
// If the data is nil, then we don't set anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
dataVal := reflect.ValueOf(data)
|
||||
if !dataVal.IsValid() {
|
||||
// If the data value is invalid, then we just set the value
|
||||
// to be the zero value.
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.config.DecodeHook != nil {
|
||||
// We have a DecodeHook, so let's pre-process the data.
|
||||
var err error
|
||||
data, err = DecodeHookExec(
|
||||
d.config.DecodeHook,
|
||||
dataVal.Type(), val.Type(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
dataKind := getKind(val)
|
||||
switch dataKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, data, val)
|
||||
case reflect.Interface:
|
||||
err = d.decodeBasic(name, data, val)
|
||||
case reflect.String:
|
||||
err = d.decodeString(name, data, val)
|
||||
case reflect.Int:
|
||||
err = d.decodeInt(name, data, val)
|
||||
case reflect.Uint:
|
||||
err = d.decodeUint(name, data, val)
|
||||
case reflect.Float32:
|
||||
err = d.decodeFloat(name, data, val)
|
||||
case reflect.Struct:
|
||||
err = d.decodeStruct(name, data, val)
|
||||
case reflect.Map:
|
||||
err = d.decodeMap(name, data, val)
|
||||
case reflect.Ptr:
|
||||
err = d.decodePtr(name, data, val)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, data, val)
|
||||
default:
|
||||
// If we reached this point then we weren't able to decode it
|
||||
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
|
||||
}
|
||||
|
||||
// If we reached here, then we successfully decoded SOMETHING, so
|
||||
// mark the key as used if we're tracking metadata.
|
||||
if d.config.Metadata != nil && name != "" {
|
||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// This decodes a basic type (bool, int, string, etc.) and sets the
|
||||
// value to "data" of that type.
|
||||
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
if !dataVal.IsValid() {
|
||||
dataVal = reflect.Zero(val.Type())
|
||||
}
|
||||
|
||||
dataValType := dataVal.Type()
|
||||
if !dataValType.AssignableTo(val.Type()) {
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got '%s'",
|
||||
name, val.Type(), dataValType)
|
||||
}
|
||||
|
||||
val.Set(dataVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
converted := true
|
||||
switch {
|
||||
case dataKind == reflect.String:
|
||||
val.SetString(dataVal.String())
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetString("1")
|
||||
} else {
|
||||
val.SetString("0")
|
||||
}
|
||||
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
||||
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
|
||||
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
|
||||
case dataKind == reflect.Slice && d.config.WeaklyTypedInput:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
switch {
|
||||
case elemKind == reflect.Uint8:
|
||||
val.SetString(string(dataVal.Interface().([]uint8)))
|
||||
default:
|
||||
converted = false
|
||||
}
|
||||
default:
|
||||
converted = false
|
||||
}
|
||||
|
||||
if !converted {
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
val.SetInt(dataVal.Int())
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetInt(int64(dataVal.Uint()))
|
||||
case dataKind == reflect.Float32:
|
||||
val.SetInt(int64(dataVal.Float()))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetInt(1)
|
||||
} else {
|
||||
val.SetInt(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetInt(i)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
i := dataVal.Int()
|
||||
if i < 0 && !d.config.WeaklyTypedInput {
|
||||
return fmt.Errorf("cannot parse '%s', %d overflows uint",
|
||||
name, i)
|
||||
}
|
||||
val.SetUint(uint64(i))
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetUint(dataVal.Uint())
|
||||
case dataKind == reflect.Float32:
|
||||
f := dataVal.Float()
|
||||
if f < 0 && !d.config.WeaklyTypedInput {
|
||||
return fmt.Errorf("cannot parse '%s', %f overflows uint",
|
||||
name, f)
|
||||
}
|
||||
val.SetUint(uint64(f))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetUint(1)
|
||||
} else {
|
||||
val.SetUint(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetUint(i)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Bool:
|
||||
val.SetBool(dataVal.Bool())
|
||||
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Int() != 0)
|
||||
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Uint() != 0)
|
||||
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Float() != 0)
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
b, err := strconv.ParseBool(dataVal.String())
|
||||
if err == nil {
|
||||
val.SetBool(b)
|
||||
} else if dataVal.String() == "" {
|
||||
val.SetBool(false)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
val.SetFloat(float64(dataVal.Int()))
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetFloat(float64(dataVal.Uint()))
|
||||
case dataKind == reflect.Float32:
|
||||
val.SetFloat(float64(dataVal.Float()))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetFloat(1)
|
||||
} else {
|
||||
val.SetFloat(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetFloat(f)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
|
||||
valType := val.Type()
|
||||
valKeyType := valType.Key()
|
||||
valElemType := valType.Elem()
|
||||
|
||||
// By default we overwrite keys in the current map
|
||||
valMap := val
|
||||
|
||||
// If the map is nil or we're purposely zeroing fields, make a new map
|
||||
if valMap.IsNil() || d.config.ZeroFields {
|
||||
// Make a new map to hold our result
|
||||
mapType := reflect.MapOf(valKeyType, valElemType)
|
||||
valMap = reflect.MakeMap(mapType)
|
||||
}
|
||||
|
||||
// Check input type
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
if dataVal.Kind() != reflect.Map {
|
||||
// In weak mode, we accept a slice of maps as an input...
|
||||
if d.config.WeaklyTypedInput {
|
||||
switch dataVal.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Special case for BC reasons (covered by tests)
|
||||
if dataVal.Len() == 0 {
|
||||
val.Set(valMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < dataVal.Len(); i++ {
|
||||
err := d.decode(
|
||||
fmt.Sprintf("%s[%d]", name, i),
|
||||
dataVal.Index(i).Interface(), val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
|
||||
}
|
||||
|
||||
// Accumulate errors
|
||||
errors := make([]string, 0)
|
||||
|
||||
for _, k := range dataVal.MapKeys() {
|
||||
fieldName := fmt.Sprintf("%s[%s]", name, k)
|
||||
|
||||
// First decode the key into the proper type
|
||||
currentKey := reflect.Indirect(reflect.New(valKeyType))
|
||||
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Next decode the data into the proper type
|
||||
v := dataVal.MapIndex(k).Interface()
|
||||
currentVal := reflect.Indirect(reflect.New(valElemType))
|
||||
if err := d.decode(fieldName, v, currentVal); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
valMap.SetMapIndex(currentKey, currentVal)
|
||||
}
|
||||
|
||||
// Set the built up map to the value
|
||||
val.Set(valMap)
|
||||
|
||||
// If we had errors, return those
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
// into that. Then set the value of the pointer to this type.
|
||||
valType := val.Type()
|
||||
valElemType := valType.Elem()
|
||||
realVal := reflect.New(valElemType)
|
||||
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.Set(realVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
dataValKind := dataVal.Kind()
|
||||
valType := val.Type()
|
||||
valElemType := valType.Elem()
|
||||
sliceType := reflect.SliceOf(valElemType)
|
||||
|
||||
// Check input type
|
||||
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
|
||||
// Accept empty map instead of array/slice in weakly typed mode
|
||||
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
|
||||
val.Set(reflect.MakeSlice(sliceType, 0, 0))
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"'%s': source data must be an array or slice, got %s", name, dataValKind)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a new slice to hold our result, same size as the original data.
|
||||
valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
|
||||
|
||||
// Accumulate any errors
|
||||
errors := make([]string, 0)
|
||||
|
||||
for i := 0; i < dataVal.Len(); i++ {
|
||||
currentData := dataVal.Index(i).Interface()
|
||||
currentField := valSlice.Index(i)
|
||||
|
||||
fieldName := fmt.Sprintf("%s[%d]", name, i)
|
||||
if err := d.decode(fieldName, currentData, currentField); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, set the value to the slice we built up
|
||||
val.Set(valSlice)
|
||||
|
||||
// If there were errors, we return those
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
|
||||
// If the type of the value to write to and the data match directly,
|
||||
// then we just set it directly instead of recursing into the structure.
|
||||
if dataVal.Type() == val.Type() {
|
||||
val.Set(dataVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
dataValKind := dataVal.Kind()
|
||||
if dataValKind != reflect.Map {
|
||||
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
|
||||
}
|
||||
|
||||
dataValType := dataVal.Type()
|
||||
if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
|
||||
return fmt.Errorf(
|
||||
"'%s' needs a map with string keys, has '%s' keys",
|
||||
name, dataValType.Key().Kind())
|
||||
}
|
||||
|
||||
dataValKeys := make(map[reflect.Value]struct{})
|
||||
dataValKeysUnused := make(map[interface{}]struct{})
|
||||
for _, dataValKey := range dataVal.MapKeys() {
|
||||
dataValKeys[dataValKey] = struct{}{}
|
||||
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
|
||||
}
|
||||
|
||||
errors := make([]string, 0)
|
||||
|
||||
// This slice will keep track of all the structs we'll be decoding.
|
||||
// There can be more than one struct if there are embedded structs
|
||||
// that are squashed.
|
||||
structs := make([]reflect.Value, 1, 5)
|
||||
structs[0] = val
|
||||
|
||||
// Compile the list of all the fields that we're going to be decoding
|
||||
// from all the structs.
|
||||
fields := make(map[*reflect.StructField]reflect.Value)
|
||||
for len(structs) > 0 {
|
||||
structVal := structs[0]
|
||||
structs = structs[1:]
|
||||
|
||||
structType := structVal.Type()
|
||||
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
fieldType := structType.Field(i)
|
||||
fieldKind := fieldType.Type.Kind()
|
||||
|
||||
if fieldType.Anonymous {
|
||||
if fieldKind != reflect.Struct {
|
||||
errors = appendErrors(errors,
|
||||
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
squash := false
|
||||
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if squash {
|
||||
if fieldKind != reflect.Struct {
|
||||
errors = appendErrors(errors,
|
||||
fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind))
|
||||
} else {
|
||||
structs = append(structs, val.FieldByName(fieldType.Name))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Normal struct field, store it away
|
||||
fields[&fieldType] = structVal.Field(i)
|
||||
}
|
||||
}
|
||||
|
||||
for fieldType, field := range fields {
|
||||
fieldName := fieldType.Name
|
||||
|
||||
tagValue := fieldType.Tag.Get(d.config.TagName)
|
||||
tagValue = strings.SplitN(tagValue, ",", 2)[0]
|
||||
if tagValue != "" {
|
||||
fieldName = tagValue
|
||||
}
|
||||
|
||||
rawMapKey := reflect.ValueOf(fieldName)
|
||||
rawMapVal := dataVal.MapIndex(rawMapKey)
|
||||
if !rawMapVal.IsValid() {
|
||||
// Do a slower search by iterating over each key and
|
||||
// doing case-insensitive search.
|
||||
for dataValKey, _ := range dataValKeys {
|
||||
mK, ok := dataValKey.Interface().(string)
|
||||
if !ok {
|
||||
// Not a string key
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold(mK, fieldName) {
|
||||
rawMapKey = dataValKey
|
||||
rawMapVal = dataVal.MapIndex(dataValKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rawMapVal.IsValid() {
|
||||
// There was no matching key in the map for the value in
|
||||
// the struct. Just ignore.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the key we're using from the unused map so we stop tracking
|
||||
delete(dataValKeysUnused, rawMapKey.Interface())
|
||||
|
||||
if !field.IsValid() {
|
||||
// This should never happen
|
||||
panic("field is not valid")
|
||||
}
|
||||
|
||||
// If we can't set the field, then it is unexported or something,
|
||||
// and we just continue onwards.
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the name is empty string, then we're at the root, and we
|
||||
// don't dot-join the fields.
|
||||
if name != "" {
|
||||
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
|
||||
}
|
||||
|
||||
if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
|
||||
keys := make([]string, 0, len(dataValKeysUnused))
|
||||
for rawKey, _ := range dataValKeysUnused {
|
||||
keys = append(keys, rawKey.(string))
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
// Add the unused keys to the list of unused keys if we're tracking metadata
|
||||
if d.config.Metadata != nil {
|
||||
for rawKey, _ := range dataValKeysUnused {
|
||||
key := rawKey.(string)
|
||||
if name != "" {
|
||||
key = fmt.Sprintf("%s.%s", name, key)
|
||||
}
|
||||
|
||||
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKind(val reflect.Value) reflect.Kind {
|
||||
kind := val.Kind()
|
||||
|
||||
switch {
|
||||
case kind >= reflect.Int && kind <= reflect.Int64:
|
||||
return reflect.Int
|
||||
case kind >= reflect.Uint && kind <= reflect.Uint64:
|
||||
return reflect.Uint
|
||||
case kind >= reflect.Float32 && kind <= reflect.Float64:
|
||||
return reflect.Float32
|
||||
default:
|
||||
return kind
|
||||
}
|
||||
}
|
||||
279
vendor/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
279
vendor/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Decode(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"emails": []string{"one", "two", "three"},
|
||||
"extra": map[string]string{
|
||||
"twitter": "mitchellh",
|
||||
},
|
||||
}
|
||||
|
||||
var result Person
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeViaJSON takes the map data and passes it through encoding/json to convert it into the
|
||||
// given Go native structure pointed to by v. v must be a pointer to a struct.
|
||||
func decodeViaJSON(data interface{}, v interface{}) error {
|
||||
// Perform the task by simply marshalling the input into JSON,
|
||||
// then unmarshalling it into target native Go struct.
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(b, v)
|
||||
}
|
||||
|
||||
func Benchmark_DecodeViaJSON(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"emails": []string{"one", "two", "three"},
|
||||
"extra": map[string]string{
|
||||
"twitter": "mitchellh",
|
||||
},
|
||||
}
|
||||
|
||||
var result Person
|
||||
for i := 0; i < b.N; i++ {
|
||||
decodeViaJSON(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeBasic(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vint": 42,
|
||||
"Vuint": 42,
|
||||
"vbool": true,
|
||||
"Vfloat": 42.42,
|
||||
"vsilent": true,
|
||||
"vdata": 42,
|
||||
}
|
||||
|
||||
var result Basic
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeEmbedded(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"Basic": map[string]interface{}{
|
||||
"vstring": "innerfoo",
|
||||
},
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var result Embedded
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeTypeConversion(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"IntToFloat": 42,
|
||||
"IntToUint": 42,
|
||||
"IntToBool": 1,
|
||||
"IntToString": 42,
|
||||
"UintToInt": 42,
|
||||
"UintToFloat": 42,
|
||||
"UintToBool": 42,
|
||||
"UintToString": 42,
|
||||
"BoolToInt": true,
|
||||
"BoolToUint": true,
|
||||
"BoolToFloat": true,
|
||||
"BoolToString": true,
|
||||
"FloatToInt": 42.42,
|
||||
"FloatToUint": 42.42,
|
||||
"FloatToBool": 42.42,
|
||||
"FloatToString": 42.42,
|
||||
"StringToInt": "42",
|
||||
"StringToUint": "42",
|
||||
"StringToBool": "1",
|
||||
"StringToFloat": "42.42",
|
||||
"SliceToMap": []interface{}{},
|
||||
"MapToSlice": map[string]interface{}{},
|
||||
}
|
||||
|
||||
var resultStrict TypeConversionResult
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &resultStrict)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMap(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vother": map[interface{}]interface{}{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
var result Map
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMapOfStruct(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"value": map[string]interface{}{
|
||||
"foo": map[string]string{"vstring": "one"},
|
||||
"bar": map[string]string{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result MapOfStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeSlice(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": []string{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
var result Slice
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeSliceOfStruct(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"value": []map[string]interface{}{
|
||||
{"vstring": "one"},
|
||||
{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result SliceOfStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON, generated by a weakly typed language
|
||||
// such as PHP.
|
||||
input := map[string]interface{}{
|
||||
"name": 123, // number => string
|
||||
"age": "42", // string => number
|
||||
"emails": map[string]interface{}{}, // empty map => empty array
|
||||
}
|
||||
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMetadata(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"email": "foo@bar.com",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result EmbeddedSquash
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
b.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeTagged(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "value",
|
||||
}
|
||||
|
||||
var result Tagged
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
47
vendor/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package mapstructure
|
||||
|
||||
import "testing"
|
||||
|
||||
// GH-1
|
||||
func TestDecode_NilValue(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": nil,
|
||||
"vother": nil,
|
||||
}
|
||||
|
||||
var result Map
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("should not error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != "" {
|
||||
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vother != nil {
|
||||
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||
}
|
||||
}
|
||||
|
||||
// GH-10
|
||||
func TestDecode_mapInterfaceInterface(t *testing.T) {
|
||||
input := map[interface{}]interface{}{
|
||||
"vfoo": nil,
|
||||
"vother": nil,
|
||||
}
|
||||
|
||||
var result Map
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("should not error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != "" {
|
||||
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vother != nil {
|
||||
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||
}
|
||||
}
|
||||
203
vendor/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
203
vendor/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleDecode() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"emails": []string{"one", "two", "three"},
|
||||
"extra": map[string]string{
|
||||
"twitter": "mitchellh",
|
||||
},
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output:
|
||||
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
|
||||
}
|
||||
|
||||
func ExampleDecode_errors() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": 123,
|
||||
"age": "bad value",
|
||||
"emails": []int{1, 2, 3},
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err == nil {
|
||||
panic("should have an error")
|
||||
}
|
||||
|
||||
fmt.Println(err.Error())
|
||||
// Output:
|
||||
// 5 error(s) decoding:
|
||||
//
|
||||
// * 'Age' expected type 'int', got unconvertible type 'string'
|
||||
// * 'Emails[0]' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Emails[1]' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Emails[2]' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Name' expected type 'string', got unconvertible type 'int'
|
||||
}
|
||||
|
||||
func ExampleDecode_metadata() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"email": "foo@bar.com",
|
||||
}
|
||||
|
||||
// For metadata, we make a more advanced DecoderConfig so we can
|
||||
// more finely configure the decoder that is used. In this case, we
|
||||
// just tell the decoder we want to track metadata.
|
||||
var md Metadata
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := decoder.Decode(input); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Unused keys: %#v", md.Unused)
|
||||
// Output:
|
||||
// Unused keys: []string{"email"}
|
||||
}
|
||||
|
||||
func ExampleDecode_weaklyTypedInput() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON, generated by a weakly typed language
|
||||
// such as PHP.
|
||||
input := map[string]interface{}{
|
||||
"name": 123, // number => string
|
||||
"age": "42", // string => number
|
||||
"emails": map[string]interface{}{}, // empty map => empty array
|
||||
}
|
||||
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
|
||||
}
|
||||
|
||||
func ExampleDecode_tags() {
|
||||
// Note that the mapstructure tags defined in the struct type
|
||||
// can indicate which fields the values are mapped to.
|
||||
type Person struct {
|
||||
Name string `mapstructure:"person_name"`
|
||||
Age int `mapstructure:"person_age"`
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"person_name": "Mitchell",
|
||||
"person_age": 91,
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output:
|
||||
// mapstructure.Person{Name:"Mitchell", Age:91}
|
||||
}
|
||||
|
||||
func ExampleDecode_embeddedStruct() {
|
||||
// Squashing multiple embedded structs is allowed using the squash tag.
|
||||
// This is demonstrated by creating a composite struct of multiple types
|
||||
// and decoding into it. In this case, a person can carry with it both
|
||||
// a Family and a Location, as well as their own FirstName.
|
||||
type Family struct {
|
||||
LastName string
|
||||
}
|
||||
type Location struct {
|
||||
City string
|
||||
}
|
||||
type Person struct {
|
||||
Family `mapstructure:",squash"`
|
||||
Location `mapstructure:",squash"`
|
||||
FirstName string
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"FirstName": "Mitchell",
|
||||
"LastName": "Hashimoto",
|
||||
"City": "San Francisco",
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s, %s", result.FirstName, result.LastName, result.City)
|
||||
// Output:
|
||||
// Mitchell Hashimoto, San Francisco
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
go_import_path: gopkg.in/yaml.v2
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original copyright and license:
|
||||
|
||||
apic.go
|
||||
emitterc.go
|
||||
parserc.go
|
||||
readerc.go
|
||||
scannerc.go
|
||||
writerc.go
|
||||
yamlh.go
|
||||
yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue