Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -1,6 +0,0 @@
*.sublime-project
*.sublime-workspace
*.un~
*.swp
.idea/
*.iml

View File

@@ -1,8 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- tip

View File

@@ -1,96 +0,0 @@
## Changelog
### [1.7.3](https://github.com/magiconair/properties/tags/v1.7.3) - 10 Jul 2017
* [Issue #17](https://github.com/magiconair/properties/issues/17): Add [SetValue()](http://godoc.org/github.com/magiconair/properties#Properties.SetValue) method to set values generically
* [Issue #22](https://github.com/magiconair/properties/issues/22): Add [LoadMap()](http://godoc.org/github.com/magiconair/properties#LoadMap) function to load properties from a string map
### [1.7.2](https://github.com/magiconair/properties/tags/v1.7.2) - 20 Mar 2017
* [Issue #15](https://github.com/magiconair/properties/issues/15): Drop gocheck dependency
* [PR #21](https://github.com/magiconair/properties/pull/21): Add [Map()](http://godoc.org/github.com/magiconair/properties#Properties.Map) and [FilterFunc()](http://godoc.org/github.com/magiconair/properties#Properties.FilterFunc)
### [1.7.1](https://github.com/magiconair/properties/tags/v1.7.1) - 13 Jan 2017
* [PR #16](https://github.com/magiconair/properties/pull/16): Keep gofmt happy
* [PR #18](https://github.com/magiconair/properties/pull/18): Fix Delete() function
### [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#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#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

View File

@@ -1,6 +1,6 @@
goproperties - properties file decoder for Go
Copyright (c) 2013-2014 - Frank Schroeder
Copyright (c) 2013-2018 - Frank Schroeder
All rights reserved.

View File

@@ -1,100 +0,0 @@
Overview [![Build Status](https://travis-ci.org/magiconair/properties.svg?branch=master)](https://travis-ci.org/magiconair/properties)
========
#### Current version: 1.7.3
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.
Read the full documentation on [GoDoc](https://godoc.org/github.com/magiconair/properties) [![GoDoc](https://godoc.org/github.com/magiconair/properties?status.png)](https://godoc.org/github.com/magiconair/properties)
Getting Started
---------------
```go
import (
"flag"
"github.com/magiconair/properties"
)
func main() {
// init from a file
p := properties.MustLoadFile("${HOME}/config.properties", properties.UTF8)
// or multiple files
p = properties.MustLoadFiles([]string{
"${HOME}/config.properties",
"${HOME}/config-${USER}.properties",
}, properties.UTF8, true)
// or from a map
p = properties.LoadMap(map[string]string{"key": "value", "abc": "def"})
// or from a string
p = properties.MustLoadString("key=value\nabc=def")
// or from a URL
p = properties.MustLoadURL("http://host/path")
// or from multiple URLs
p = properties.MustLoadURL([]string{
"http://host/config",
"http://host/config-${USER}",
}, true)
// or from flags
p.MustFlag(flag.CommandLine)
// get values through getters
host := p.MustGetString("host")
port := p.GetInt("port", 8080)
// or through 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)
}
}
```
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

View File

@@ -1,90 +0,0 @@
// Copyright 2017 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 assert provides helper functions for testing.
package assert
import (
"fmt"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"testing"
)
// skip defines the default call depth
const skip = 2
// Equal asserts that got and want are equal as defined by
// reflect.DeepEqual. The test fails with msg if they are not equal.
func Equal(t *testing.T, got, want interface{}, msg ...string) {
if x := equal(2, got, want, msg...); x != "" {
fmt.Println(x)
t.Fail()
}
}
func equal(skip int, got, want interface{}, msg ...string) string {
if !reflect.DeepEqual(got, want) {
return fail(skip, "got %v want %v %s", got, want, strings.Join(msg, " "))
}
return ""
}
// Panic asserts that function fn() panics.
// It assumes that recover() either returns a string or
// an error and fails if the message does not match
// the regular expression in 'matches'.
func Panic(t *testing.T, fn func(), matches string) {
if x := doesPanic(2, fn, matches); x != "" {
fmt.Println(x)
t.Fail()
}
}
func doesPanic(skip int, fn func(), expr string) (err string) {
defer func() {
r := recover()
if r == nil {
err = fail(skip, "did not panic")
return
}
var v string
switch r.(type) {
case error:
v = r.(error).Error()
case string:
v = r.(string)
}
err = matches(skip, v, expr)
}()
fn()
return ""
}
// Matches asserts that a value matches a given regular expression.
func Matches(t *testing.T, value, expr string) {
if x := matches(2, value, expr); x != "" {
fmt.Println(x)
t.Fail()
}
}
func matches(skip int, value, expr string) string {
ok, err := regexp.MatchString(expr, value)
if err != nil {
return fail(skip, "invalid pattern %q. %s", expr, err)
}
if !ok {
return fail(skip, "got %s which does not match %s", value, expr)
}
return ""
}
func fail(skip int, format string, args ...interface{}) string {
_, file, line, _ := runtime.Caller(skip)
return fmt.Sprintf("\t%s:%d: %s\n", filepath.Base(file), line, fmt.Sprintf(format, args...))
}

View File

@@ -1,55 +0,0 @@
// Copyright 2017 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 assert
import "testing"
func TestEqualEquals(t *testing.T) {
if got, want := equal(2, "a", "a"), ""; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestEqualFails(t *testing.T) {
if got, want := equal(2, "a", "b"), "\tassert_test.go:16: got a want b \n"; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPanicPanics(t *testing.T) {
if got, want := doesPanic(2, func() { panic("foo") }, ""), ""; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPanicPanicsAndMatches(t *testing.T) {
if got, want := doesPanic(2, func() { panic("foo") }, "foo"), ""; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPanicPanicsAndDoesNotMatch(t *testing.T) {
if got, want := doesPanic(2, func() { panic("foo") }, "bar"), "\tassert.go:62: got foo which does not match bar\n"; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPanicPanicsAndDoesNotPanic(t *testing.T) {
if got, want := doesPanic(2, func() {}, "bar"), "\tassert.go:65: did not panic\n"; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestMatchesMatches(t *testing.T) {
if got, want := matches(2, "aaa", "a"), ""; got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestMatchesDoesNotMatch(t *testing.T) {
if got, want := matches(2, "aaa", "b"), "\tassert_test.go:52: got aaa which does not match b\n"; got != want {
t.Fatalf("got %q want %q", got, want)
}
}

View File

@@ -1,24 +0,0 @@
// 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++ {
if _, err := Load([]byte(input), ISO_8859_1); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,299 +0,0 @@
// Copyright 2017 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
}

View File

@@ -1,93 +0,0 @@
// Copyright 2017 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
}

View File

@@ -1,76 +0,0 @@
// Copyright 2017 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.MustSet("s", "t")
p.MustSet("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")
if err := f.Parse([]string{"-c", "4"}); err != nil {
t.Fatal(err)
}
p := NewProperties()
p.MustSet("b", "5")
p.MustSet("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.MustSet("x", "7")
p.MustSet("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
}

View File

@@ -196,9 +196,8 @@ func lexBeforeKey(l *lexer) stateFn {
return lexComment
case isWhitespace(r):
l.acceptRun(whitespace)
l.ignore()
return lexKey
return lexBeforeKey
default:
l.backup()

View File

@@ -218,7 +218,7 @@ func must(p *Properties, err error) *Properties {
// 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))
return expand(name, []string{}, "${", "}", make(map[string]string))
}
// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.

View File

@@ -1,231 +0,0 @@
// Copyright 2017 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"
"testing"
"github.com/magiconair/properties/assert"
)
func TestLoadFailsWithNotExistingFile(t *testing.T) {
_, err := LoadFile("doesnotexist.properties", ISO_8859_1)
assert.Equal(t, err != nil, true, "")
assert.Matches(t, err.Error(), "open.*no such file or directory")
}
func TestLoadFilesFailsOnNotExistingFile(t *testing.T) {
_, err := LoadFile("doesnotexist.properties", ISO_8859_1)
assert.Equal(t, err != nil, true, "")
assert.Matches(t, err.Error(), "open.*no such file or directory")
}
func TestLoadFilesDoesNotFailOnNotExistingFileAndIgnoreMissing(t *testing.T) {
p, err := LoadFiles([]string{"doesnotexist.properties"}, ISO_8859_1, true)
assert.Equal(t, err, nil)
assert.Equal(t, p.Len(), 0)
}
func TestLoadString(t *testing.T) {
x := "key=äüö"
p1 := MustLoadString(x)
p2 := must(Load([]byte(x), UTF8))
assert.Equal(t, p1, p2)
}
func TestLoadMap(t *testing.T) {
// LoadMap does not guarantee the same import order
// of keys every time since map access is randomized.
// Therefore, we need to compare the generated maps.
m := map[string]string{"key": "value", "abc": "def"}
assert.Equal(t, LoadMap(m).Map(), m)
}
func TestLoadFile(t *testing.T) {
tf := make(tempFiles, 0)
defer tf.removeAll()
filename := tf.makeFile("key=value")
p := MustLoadFile(filename, ISO_8859_1)
assert.Equal(t, p.Len(), 1)
assertKeyValues(t, "", p, "key", "value")
}
func TestLoadFiles(t *testing.T) {
tf := make(tempFiles, 0)
defer tf.removeAll()
filename := tf.makeFile("key=value")
filename2 := tf.makeFile("key2=value2")
p := MustLoadFiles([]string{filename, filename2}, ISO_8859_1, false)
assertKeyValues(t, "", p, "key", "value", "key2", "value2")
}
func TestLoadExpandedFile(t *testing.T) {
tf := make(tempFiles, 0)
defer tf.removeAll()
if err := os.Setenv("_VARX", "some-value"); err != nil {
t.Fatal(err)
}
filename := tf.makeFilePrefix(os.Getenv("_VARX"), "key=value")
filename = strings.Replace(filename, os.Getenv("_VARX"), "${_VARX}", -1)
p := MustLoadFile(filename, ISO_8859_1)
assertKeyValues(t, "", p, "key", "value")
}
func TestLoadFilesAndIgnoreMissing(t *testing.T) {
tf := make(tempFiles, 0)
defer tf.removeAll()
filename := tf.makeFile("key=value")
filename2 := tf.makeFile("key2=value2")
p := MustLoadFiles([]string{filename, filename + "foo", filename2, filename2 + "foo"}, ISO_8859_1, true)
assertKeyValues(t, "", p, "key", "value", "key2", "value2")
}
func TestLoadURL(t *testing.T) {
srv := testServer()
defer srv.Close()
p := MustLoadURL(srv.URL + "/a")
assertKeyValues(t, "", p, "key", "value")
}
func TestLoadURLs(t *testing.T) {
srv := testServer()
defer srv.Close()
p := MustLoadURLs([]string{srv.URL + "/a", srv.URL + "/b"}, false)
assertKeyValues(t, "", p, "key", "value", "key2", "value2")
}
func TestLoadURLsAndFailMissing(t *testing.T) {
srv := testServer()
defer srv.Close()
p, err := LoadURLs([]string{srv.URL + "/a", srv.URL + "/c"}, false)
assert.Equal(t, p, (*Properties)(nil))
assert.Matches(t, err.Error(), ".*returned 404.*")
}
func TestLoadURLsAndIgnoreMissing(t *testing.T) {
srv := testServer()
defer srv.Close()
p := MustLoadURLs([]string{srv.URL + "/a", srv.URL + "/b", srv.URL + "/c"}, true)
assertKeyValues(t, "", p, "key", "value", "key2", "value2")
}
func TestLoadURLEncoding(t *testing.T) {
srv := testServer()
defer srv.Close()
uris := []string{"/none", "/utf8", "/plain", "/latin1", "/iso88591"}
for i, uri := range uris {
p := MustLoadURL(srv.URL + uri)
assert.Equal(t, p.GetString("key", ""), "äöü", fmt.Sprintf("%d", i))
}
}
func TestLoadURLFailInvalidEncoding(t *testing.T) {
srv := testServer()
defer srv.Close()
p, err := LoadURL(srv.URL + "/json")
assert.Equal(t, p, (*Properties)(nil))
assert.Matches(t, err.Error(), ".*invalid content type.*")
}
func TestLoadAll(t *testing.T) {
tf := make(tempFiles, 0)
defer tf.removeAll()
filename := tf.makeFile("key=value")
filename2 := tf.makeFile("key2=value3")
filename3 := tf.makeFile("key=value4")
srv := testServer()
defer srv.Close()
p := MustLoadAll([]string{filename, filename2, srv.URL + "/a", srv.URL + "/b", filename3}, UTF8, false)
assertKeyValues(t, "", p, "key", "value4", "key2", "value2")
}
type tempFiles []string
func (tf *tempFiles) removeAll() {
for _, path := range *tf {
err := os.Remove(path)
if err != nil {
fmt.Printf("os.Remove: %v", err)
}
}
}
func (tf *tempFiles) makeFile(data string) string {
return tf.makeFilePrefix("properties", data)
}
func (tf *tempFiles) makeFilePrefix(prefix, data string) string {
f, err := ioutil.TempFile("", prefix)
if err != nil {
panic("ioutil.TempFile: " + err.Error())
}
// remember the temp file so that we can remove it later
*tf = append(*tf, f.Name())
n, err := fmt.Fprint(f, data)
if err != nil {
panic("fmt.Fprintln: " + err.Error())
}
if n != len(data) {
panic(fmt.Sprintf("Data size mismatch. expected=%d wrote=%d\n", len(data), n))
}
err = f.Close()
if err != nil {
panic("f.Close: " + err.Error())
}
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)
if _, err := w.Write(data); err != nil {
panic(err)
}
}
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)
}
}))
}

View File

@@ -19,6 +19,8 @@ import (
"unicode/utf8"
)
const maxExpansionDepth = 64
// 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.
@@ -92,7 +94,7 @@ func (p *Properties) Get(key string) (value string, ok bool) {
return "", false
}
expanded, err := p.expand(v)
expanded, err := p.expand(key, v)
// we guarantee that the expanded value is free of
// circular references and malformed expressions
@@ -511,6 +513,9 @@ func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
if p.DisableExpansion {
prev, ok = p.Get(key)
p.m[key] = value
if !ok {
p.k = append(p.k, key)
}
return prev, ok, nil
}
@@ -522,7 +527,7 @@ func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
p.m[key] = value
// now check for a circular reference
_, err = p.expand(value)
_, err = p.expand(key, value)
if err != nil {
// revert to the previous state
@@ -693,56 +698,65 @@ outer:
// 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 {
for key, value := range p.m {
if _, err := p.expand(key, value); err != nil {
return err
}
}
return nil
}
func (p *Properties) expand(input string) (string, error) {
func (p *Properties) expand(key, 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)
return expand(input, []string{key}, 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
func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) {
if len(keys) > maxExpansionDepth {
return "", fmt.Errorf("expansion too deep")
}
keyStart := start + len(prefix)
keyLen := strings.Index(s[keyStart:], postfix)
if keyLen == -1 {
return "", fmt.Errorf("malformed expression")
for {
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)
for _, k := range keys {
if key == k {
return "", fmt.Errorf("circular reference")
}
}
val, ok := values[key]
if !ok {
val = os.Getenv(key)
}
new_val, err := expand(val, append(keys, key), prefix, postfix, values)
if err != nil {
return "", err
}
s = s[:start] + new_val + s[end+1:]
}
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)
return s, nil
}
// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.

View File

@@ -1,934 +0,0 @@
// Copyright 2017 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"
"reflect"
"strings"
"testing"
"time"
"github.com/magiconair/properties/assert"
)
var verbose = flag.Bool("verbose", false, "Verbose output")
func init() {
ErrorHandler = PanicHandler
}
// ----------------------------------------------------------------------------
// 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 TestBasic(t *testing.T) {
testWhitespaceAndDelimiterCombinations(t, "key", "")
testWhitespaceAndDelimiterCombinations(t, "key", "value")
testWhitespaceAndDelimiterCombinations(t, "key", "value ")
}
func TestComplex(t *testing.T) {
for _, test := range complexTests {
testKeyValue(t, test[0], test[1:]...)
}
}
func TestErrors(t *testing.T) {
for _, test := range errorTests {
_, err := Load([]byte(test.input), ISO_8859_1)
assert.Equal(t, err != nil, true, "want error")
assert.Equal(t, strings.Contains(err.Error(), test.msg), true)
}
}
func TestDisableExpansion(t *testing.T) {
input := "key=value\nkey2=${key}"
p := mustParse(t, input)
p.DisableExpansion = true
assert.Equal(t, p.MustGet("key"), "value")
assert.Equal(t, p.MustGet("key2"), "${key}")
// with expansion disabled we can introduce circular references
p.MustSet("keyA", "${keyB}")
p.MustSet("keyB", "${keyA}")
assert.Equal(t, p.MustGet("keyA"), "${keyB}")
assert.Equal(t, p.MustGet("keyB"), "${keyA}")
}
func TestMustGet(t *testing.T) {
input := "key = value\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGet("key"), "value")
assert.Panic(t, func() { p.MustGet("invalid") }, "unknown property: invalid")
}
func TestGetBool(t *testing.T) {
for _, test := range boolTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetBool(test.key, test.def), test.value)
}
}
func TestMustGetBool(t *testing.T) {
input := "key = true\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetBool("key"), true)
assert.Panic(t, func() { p.MustGetBool("invalid") }, "unknown property: invalid")
}
func TestGetDuration(t *testing.T) {
for _, test := range durationTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetDuration(test.key, test.def), test.value)
}
}
func TestMustGetDuration(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetDuration("key"), time.Duration(123))
assert.Panic(t, func() { p.MustGetDuration("key2") }, "strconv.ParseInt: parsing.*")
assert.Panic(t, func() { p.MustGetDuration("invalid") }, "unknown property: invalid")
}
func TestGetParsedDuration(t *testing.T) {
for _, test := range parsedDurationTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetParsedDuration(test.key, test.def), test.value)
}
}
func TestMustGetParsedDuration(t *testing.T) {
input := "key = 123ms\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetParsedDuration("key"), 123*time.Millisecond)
assert.Panic(t, func() { p.MustGetParsedDuration("key2") }, "time: invalid duration ghi")
assert.Panic(t, func() { p.MustGetParsedDuration("invalid") }, "unknown property: invalid")
}
func TestGetFloat64(t *testing.T) {
for _, test := range floatTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetFloat64(test.key, test.def), test.value)
}
}
func TestMustGetFloat64(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetFloat64("key"), float64(123))
assert.Panic(t, func() { p.MustGetFloat64("key2") }, "strconv.ParseFloat: parsing.*")
assert.Panic(t, func() { p.MustGetFloat64("invalid") }, "unknown property: invalid")
}
func TestGetInt(t *testing.T) {
for _, test := range int64Tests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetInt(test.key, int(test.def)), int(test.value))
}
}
func TestMustGetInt(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetInt("key"), int(123))
assert.Panic(t, func() { p.MustGetInt("key2") }, "strconv.ParseInt: parsing.*")
assert.Panic(t, func() { p.MustGetInt("invalid") }, "unknown property: invalid")
}
func TestGetInt64(t *testing.T) {
for _, test := range int64Tests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetInt64(test.key, test.def), test.value)
}
}
func TestMustGetInt64(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetInt64("key"), int64(123))
assert.Panic(t, func() { p.MustGetInt64("key2") }, "strconv.ParseInt: parsing.*")
assert.Panic(t, func() { p.MustGetInt64("invalid") }, "unknown property: invalid")
}
func TestGetUint(t *testing.T) {
for _, test := range uint64Tests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetUint(test.key, uint(test.def)), uint(test.value))
}
}
func TestMustGetUint(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetUint("key"), uint(123))
assert.Panic(t, func() { p.MustGetUint64("key2") }, "strconv.ParseUint: parsing.*")
assert.Panic(t, func() { p.MustGetUint64("invalid") }, "unknown property: invalid")
}
func TestGetUint64(t *testing.T) {
for _, test := range uint64Tests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetUint64(test.key, test.def), test.value)
}
}
func TestMustGetUint64(t *testing.T) {
input := "key = 123\nkey2 = ghi"
p := mustParse(t, input)
assert.Equal(t, p.MustGetUint64("key"), uint64(123))
assert.Panic(t, func() { p.MustGetUint64("key2") }, "strconv.ParseUint: parsing.*")
assert.Panic(t, func() { p.MustGetUint64("invalid") }, "unknown property: invalid")
}
func TestGetString(t *testing.T) {
for _, test := range stringTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), 1)
assert.Equal(t, p.GetString(test.key, test.def), test.value)
}
}
func TestMustGetString(t *testing.T) {
input := `key = value`
p := mustParse(t, input)
assert.Equal(t, p.MustGetString("key"), "value")
assert.Panic(t, func() { p.MustGetString("invalid") }, "unknown property: invalid")
}
func TestComment(t *testing.T) {
for _, test := range commentTests {
p := mustParse(t, test.input)
assert.Equal(t, p.MustGetString(test.key), test.value)
assert.Equal(t, p.GetComments(test.key), test.comments)
if test.comments != nil {
assert.Equal(t, p.GetComment(test.key), test.comments[len(test.comments)-1])
} else {
assert.Equal(t, p.GetComment(test.key), "")
}
// test setting comments
if len(test.comments) > 0 {
// set single comment
p.ClearComments()
assert.Equal(t, len(p.c), 0)
p.SetComment(test.key, test.comments[0])
assert.Equal(t, p.GetComment(test.key), test.comments[0])
// set multiple comments
p.ClearComments()
assert.Equal(t, len(p.c), 0)
p.SetComments(test.key, test.comments)
assert.Equal(t, p.GetComments(test.key), test.comments)
// clear comments for a key
p.SetComments(test.key, nil)
assert.Equal(t, p.GetComment(test.key), "")
assert.Equal(t, p.GetComments(test.key), ([]string)(nil))
}
}
}
func TestFilter(t *testing.T) {
for _, test := range filterTests {
p := mustParse(t, test.input)
pp, err := p.Filter(test.pattern)
if err != nil {
assert.Matches(t, err.Error(), test.err)
continue
}
assert.Equal(t, pp != nil, true, "want properties")
assert.Equal(t, pp.Len(), len(test.keys))
for _, key := range test.keys {
v1, ok1 := p.Get(key)
v2, ok2 := pp.Get(key)
assert.Equal(t, ok1, true)
assert.Equal(t, ok2, true)
assert.Equal(t, v1, v2)
}
}
}
func TestFilterPrefix(t *testing.T) {
for _, test := range filterPrefixTests {
p := mustParse(t, test.input)
pp := p.FilterPrefix(test.prefix)
assert.Equal(t, pp != nil, true, "want properties")
assert.Equal(t, pp.Len(), len(test.keys))
for _, key := range test.keys {
v1, ok1 := p.Get(key)
v2, ok2 := pp.Get(key)
assert.Equal(t, ok1, true)
assert.Equal(t, ok2, true)
assert.Equal(t, v1, v2)
}
}
}
func TestFilterStripPrefix(t *testing.T) {
for _, test := range filterStripPrefixTests {
p := mustParse(t, test.input)
pp := p.FilterPrefix(test.prefix)
assert.Equal(t, pp != nil, true, "want properties")
assert.Equal(t, pp.Len(), len(test.keys))
for _, key := range test.keys {
v1, ok1 := p.Get(key)
v2, ok2 := pp.Get(key)
assert.Equal(t, ok1, true)
assert.Equal(t, ok2, true)
assert.Equal(t, v1, v2)
}
}
}
func TestKeys(t *testing.T) {
for _, test := range keysTests {
p := mustParse(t, test.input)
assert.Equal(t, p.Len(), len(test.keys))
assert.Equal(t, len(p.Keys()), len(test.keys))
assert.Equal(t, p.Keys(), test.keys)
}
}
func TestSet(t *testing.T) {
for _, test := range setTests {
p := mustParse(t, test.input)
prev, ok, err := p.Set(test.key, test.value)
if test.err != "" {
assert.Matches(t, err.Error(), test.err)
continue
}
assert.Equal(t, err, nil)
assert.Equal(t, ok, test.ok)
if ok {
assert.Equal(t, prev, test.prev)
}
assert.Equal(t, p.Keys(), test.keys)
}
}
func TestSetValue(t *testing.T) {
tests := []interface{}{
true, false,
int8(123), int16(123), int32(123), int64(123), int(123),
uint8(123), uint16(123), uint32(123), uint64(123), uint(123),
float32(1.23), float64(1.23),
"abc",
}
for _, v := range tests {
p := NewProperties()
err := p.SetValue("x", v)
assert.Equal(t, err, nil)
assert.Equal(t, p.GetString("x", ""), fmt.Sprintf("%v", v))
}
}
func TestMustSet(t *testing.T) {
input := "key=${key}"
p := mustParse(t, input)
assert.Panic(t, func() { p.MustSet("key", "${key}") }, "circular reference .*")
}
func TestWrite(t *testing.T) {
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)
}
assert.Equal(t, err, nil)
s := string(buf.Bytes())
assert.Equal(t, n, len(test.output), fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
assert.Equal(t, s, test.output, fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
}
}
func TestWriteComment(t *testing.T) {
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)
}
assert.Equal(t, err, nil)
s := string(buf.Bytes())
assert.Equal(t, n, len(test.output), fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
assert.Equal(t, s, test.output, fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
}
}
func TestCustomExpansionExpression(t *testing.T) {
testKeyValuePrePostfix(t, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
}
func TestPanicOn32BitIntOverflow(t *testing.T) {
is32Bit = true
var min, max int64 = math.MinInt32 - 1, math.MaxInt32 + 1
input := fmt.Sprintf("min=%d\nmax=%d", min, max)
p := mustParse(t, input)
assert.Equal(t, p.MustGetInt64("min"), min)
assert.Equal(t, p.MustGetInt64("max"), max)
assert.Panic(t, func() { p.MustGetInt("min") }, ".* out of range")
assert.Panic(t, func() { p.MustGetInt("max") }, ".* out of range")
}
func TestPanicOn32BitUintOverflow(t *testing.T) {
is32Bit = true
var max uint64 = math.MaxUint32 + 1
input := fmt.Sprintf("max=%d", max)
p := mustParse(t, input)
assert.Equal(t, p.MustGetUint64("max"), max)
assert.Panic(t, func() { p.MustGetUint("max") }, ".* out of range")
}
func TestDeleteKey(t *testing.T) {
input := "#comments should also be gone\nkey=to-be-deleted\nsecond=key"
p := mustParse(t, input)
assert.Equal(t, len(p.m), 2)
assert.Equal(t, len(p.c), 1)
assert.Equal(t, len(p.k), 2)
p.Delete("key")
assert.Equal(t, len(p.m), 1)
assert.Equal(t, len(p.c), 0)
assert.Equal(t, len(p.k), 1)
assert.Equal(t, p.k[0], "second")
assert.Equal(t, p.m["second"], "key")
}
func TestDeleteUnknownKey(t *testing.T) {
input := "#comments should also be gone\nkey=to-be-deleted"
p := mustParse(t, input)
assert.Equal(t, len(p.m), 1)
assert.Equal(t, len(p.c), 1)
assert.Equal(t, len(p.k), 1)
p.Delete("wrong-key")
assert.Equal(t, len(p.m), 1)
assert.Equal(t, len(p.c), 1)
assert.Equal(t, len(p.k), 1)
}
func TestMerge(t *testing.T) {
input1 := "#comment\nkey=value\nkey2=value2"
input2 := "#another comment\nkey=another value\nkey3=value3"
p1 := mustParse(t, input1)
p2 := mustParse(t, input2)
p1.Merge(p2)
assert.Equal(t, len(p1.m), 3)
assert.Equal(t, len(p1.c), 1)
assert.Equal(t, len(p1.k), 3)
assert.Equal(t, p1.MustGet("key"), "another value")
assert.Equal(t, p1.GetComment("key"), "another comment")
}
func TestMap(t *testing.T) {
input := "key=value\nabc=def"
p := mustParse(t, input)
m := map[string]string{"key": "value", "abc": "def"}
assert.Equal(t, p.Map(), m)
}
func TestFilterFunc(t *testing.T) {
input := "key=value\nabc=def"
p := mustParse(t, input)
pp := p.FilterFunc(func(k, v string) bool {
return k != "abc"
})
m := map[string]string{"key": "value"}
assert.Equal(t, pp.Map(), m)
}
// ----------------------------------------------------------------------------
// tests all combinations of delimiters, leading and/or trailing whitespace and newlines.
func testWhitespaceAndDelimiterCombinations(t *testing.T, 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(t, 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(t *testing.T, input string, keyvalues ...string) {
testKeyValuePrePostfix(t, "${", "}", 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(t *testing.T, prefix, postfix, input string, keyvalues ...string) {
p, err := Load([]byte(input), ISO_8859_1)
assert.Equal(t, err, nil)
p.Prefix = prefix
p.Postfix = postfix
assertKeyValues(t, 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(t *testing.T, input string, p *Properties, keyvalues ...string) {
assert.Equal(t, p != nil, true, "want properties")
assert.Equal(t, 2*p.Len(), len(keyvalues), "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)
if !ok {
t.Errorf("No key %q found (input=%q)", key, input)
}
if got, want := v, value; !reflect.DeepEqual(got, want) {
t.Errorf("Value %q does not match %q (input=%q)", v, value, input)
}
}
}
func mustParse(t *testing.T, s string) *Properties {
p, err := parse(s)
if err != nil {
t.Fatalf("parse failed with %s", err)
}
return p
}
// prints to stderr if the -verbose flag was given.
func printf(format string, args ...interface{}) {
if *verbose {
fmt.Fprintf(os.Stderr, format, args...)
}
}