summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2019-01-29 22:00:57 -0500
committerDave Henderson <dhenderson@gmail.com>2019-01-29 22:29:22 -0500
commit8a50b114e18069b1be388102a128869a9861555b (patch)
treeead9d88ba23658da8515c5b494971060b6effbfd
parent8695b4f6d86bfa3c241f65bf362e36600da97495 (diff)
New coll.Merge function
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
-rw-r--r--Gopkg.lock9
-rw-r--r--coll/coll.go14
-rw-r--r--coll/coll_test.go31
-rw-r--r--docs-src/content/functions/coll.yml31
-rw-r--r--docs/content/functions/coll.md45
-rw-r--r--funcs/coll.go6
-rw-r--r--tests/integration/collection_test.go64
-rw-r--r--vendor/github.com/imdario/mergo/LICENSE28
-rw-r--r--vendor/github.com/imdario/mergo/doc.go44
-rw-r--r--vendor/github.com/imdario/mergo/map.go175
-rw-r--r--vendor/github.com/imdario/mergo/merge.go255
-rw-r--r--vendor/github.com/imdario/mergo/mergo.go97
-rw-r--r--vendor/github.com/imdario/mergo/testdata/license.yml4
13 files changed, 803 insertions, 0 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 572400ab..51e0caf6 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -282,6 +282,14 @@
version = "v0.11.1"
[[projects]]
+ digest = "1:aaa38889f11896ee3644d77e17dc7764cc47f5f3d3b488268df2af2b52541c5f"
+ name = "github.com/imdario/mergo"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "7c29201646fa3de8506f701213473dd407f19646"
+ version = "v0.3.7"
+
+[[projects]]
digest = "1:406338ad39ab2e37b7f4452906442a3dbf0eb3379dd1f06aafb5c07e769a5fbb"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
@@ -520,6 +528,7 @@
"github.com/hashicorp/go-sockaddr",
"github.com/hashicorp/go-sockaddr/template",
"github.com/hashicorp/vault/api",
+ "github.com/imdario/mergo",
"github.com/pkg/errors",
"github.com/spf13/afero",
"github.com/spf13/cobra",
diff --git a/coll/coll.go b/coll/coll.go
index a9331055..40d2e9d5 100644
--- a/coll/coll.go
+++ b/coll/coll.go
@@ -5,6 +5,8 @@ import (
"reflect"
"sort"
+ "github.com/imdario/mergo"
+
"github.com/hairyhenderson/gomplate/conv"
"github.com/pkg/errors"
)
@@ -162,3 +164,15 @@ func Reverse(list interface{}) ([]interface{}, error) {
}
return l, nil
}
+
+// Merge source maps (srcs) into dst. Precedence is in left-to-right order, with
+// the left-most values taking precedence over the right-most.
+func Merge(dst map[string]interface{}, srcs ...map[string]interface{}) (map[string]interface{}, error) {
+ for _, src := range srcs {
+ err := mergo.Merge(&dst, src)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return dst, nil
+}
diff --git a/coll/coll_test.go b/coll/coll_test.go
index fff2b02f..db6d4d51 100644
--- a/coll/coll_test.go
+++ b/coll/coll_test.go
@@ -197,3 +197,34 @@ func TestReverse(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, []interface{}{4, 3, 2, 1}, out)
}
+
+func TestMerge(t *testing.T) {
+ dst := map[string]interface{}{}
+ src := map[string]interface{}{}
+ expected := map[string]interface{}{}
+
+ out, err := Merge(dst, src)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+
+ dst = map[string]interface{}{"a": 4, "c": 5}
+ src = map[string]interface{}{"a": 1, "b": 2, "c": 3}
+ expected = map[string]interface{}{
+ "a": dst["a"], "b": src["b"], "c": dst["c"],
+ }
+
+ out, err = Merge(dst, src)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+
+ dst = map[string]interface{}{"a": 4, "c": 5}
+ src = map[string]interface{}{"a": 1, "b": 2, "c": 3}
+ src2 := map[string]interface{}{"a": 1, "b": 2, "c": 3, "d": 4}
+ expected = map[string]interface{}{
+ "a": dst["a"], "b": src["b"], "c": dst["c"], "d": src2["d"],
+ }
+
+ out, err = Merge(dst, src, src2)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+}
diff --git a/docs-src/content/functions/coll.yml b/docs-src/content/functions/coll.yml
index c4d61dce..158df4b2 100644
--- a/docs-src/content/functions/coll.yml
+++ b/docs-src/content/functions/coll.yml
@@ -195,3 +195,34 @@ funcs:
- |
$ gomplate -i '{{ slice 4 3 2 1 | reverse }}'
[1 2 3 4]
+ - name: coll.Merge
+ alias: merge
+ description: |
+ Merge maps together by overriding src with dst.
+
+ In other words, the src map can be configured the "default" map, whereas the dst
+ map can be configured the "overrides".
+
+ Many source maps can be provided. Precedence is in left-to-right order.
+
+ Note that this function _changes_ the destination map.
+ pipeline: true
+ arguments:
+ - name: dst
+ required: true
+ description: the map to merge _into_
+ - name: srcs...
+ required: true
+ description: the map (or maps) to merge _from_
+ examples:
+ - |
+ $ gomplate -i '{{ $default := dict "foo" 1 "bar" 2}}
+ {{ $config := dict "foo" 8 }}
+ {{ merge $config $default }}'
+ map[bar:2 foo:8]
+ - |
+ $ gomplate -i '{{ $dst := dict "foo" 1 "bar" 2 }}
+ {{ $src1 := dict "foo" 8 "baz" 4 }}
+ {{ $src2 := dict "foo" 3 "bar" 5 }}
+ {{ coll.Merge $dst $src1 $src2 }}'
+ map[foo:1 bar:5 baz:4]
diff --git a/docs/content/functions/coll.md b/docs/content/functions/coll.md
index fca540d5..ccc50250 100644
--- a/docs/content/functions/coll.md
+++ b/docs/content/functions/coll.md
@@ -319,3 +319,48 @@ list | coll.Reverse
$ gomplate -i '{{ slice 4 3 2 1 | reverse }}'
[1 2 3 4]
```
+
+## `coll.Merge`
+
+**Alias:** `merge`
+
+Merge maps together by overriding src with dst.
+
+In other words, the src map can be configured the "default" map, whereas the dst
+map can be configured the "overrides".
+
+Many source maps can be provided. Precedence is in left-to-right order.
+
+Note that this function _changes_ the destination map.
+
+### Usage
+```go
+coll.Merge dst srcs...
+```
+
+```go
+srcs... | coll.Merge dst
+```
+
+### Arguments
+
+| name | description |
+|------|-------------|
+| `dst` | _(required)_ the map to merge _into_ |
+| `srcs...` | _(required)_ the map (or maps) to merge _from_ |
+
+### Examples
+
+```console
+$ gomplate -i '{{ $default := dict "foo" 1 "bar" 2}}
+{{ $config := dict "foo" 8 }}
+{{ merge $config $default }}'
+map[bar:2 foo:8]
+```
+```console
+$ gomplate -i '{{ $dst := dict "foo" 1 "bar" 2 }}
+{{ $src1 := dict "foo" 8 "baz" 4 }}
+{{ $src2 := dict "foo" 3 "bar" 5 }}
+{{ coll.Merge $dst $src1 $src2 }}'
+map[foo:1 bar:5 baz:4]
+```
diff --git a/funcs/coll.go b/funcs/coll.go
index c26c9e59..f104e0aa 100644
--- a/funcs/coll.go
+++ b/funcs/coll.go
@@ -30,6 +30,7 @@ func AddCollFuncs(f map[string]interface{}) {
f["prepend"] = CollNS().Prepend
f["uniq"] = CollNS().Uniq
f["reverse"] = CollNS().Reverse
+ f["merge"] = CollNS().Merge
}
// CollFuncs -
@@ -79,3 +80,8 @@ func (f *CollFuncs) Uniq(in interface{}) ([]interface{}, error) {
func (f *CollFuncs) Reverse(in interface{}) ([]interface{}, error) {
return coll.Reverse(in)
}
+
+// Merge -
+func (f *CollFuncs) Merge(dst map[string]interface{}, src ...map[string]interface{}) (map[string]interface{}, error) {
+ return coll.Merge(dst, src...)
+}
diff --git a/tests/integration/collection_test.go b/tests/integration/collection_test.go
new file mode 100644
index 00000000..32a5b880
--- /dev/null
+++ b/tests/integration/collection_test.go
@@ -0,0 +1,64 @@
+//+build integration
+
+package integration
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/gotestyourself/gotestyourself/fs"
+ "github.com/gotestyourself/gotestyourself/icmd"
+)
+
+type CollSuite struct {
+ tmpDir *fs.Dir
+}
+
+var _ = Suite(&CollSuite{})
+
+func (s *CollSuite) SetUpTest(c *C) {
+ s.tmpDir = fs.NewDir(c, "gomplate-inttests",
+ fs.WithFiles(map[string]string{
+ "defaults.yaml": `values:
+ one: 1
+ two: 2
+ three:
+ - 4
+ four:
+ a: a
+ b: b
+`,
+ "config.json": `{
+ "values": {
+ "one": "uno",
+ "three": [ 5, 6, 7 ],
+ "four": { "a": "eh?" }
+ }
+ }`,
+ }))
+}
+
+func (s *CollSuite) TearDownTest(c *C) {
+ s.tmpDir.Remove()
+}
+
+func (s *CollSuite) TestMerge(c *C) {
+ result := icmd.RunCmd(icmd.Command(GomplateBin,
+ "-d", "defaults="+s.tmpDir.Join("defaults.yaml"),
+ "-d", "config="+s.tmpDir.Join("config.json"),
+ "-i", `{{ $defaults := ds "defaults" -}}
+ {{ $config := ds "config" -}}
+ {{ $merged := coll.Merge $config $defaults -}}
+ {{ $merged | data.ToYAML }}
+`))
+ result.Assert(c, icmd.Expected{ExitCode: 0, Out: `values:
+ four:
+ a: eh?
+ b: b
+ one: uno
+ three:
+ - 5
+ - 6
+ - 7
+ two: 2
+`})
+}
diff --git a/vendor/github.com/imdario/mergo/LICENSE b/vendor/github.com/imdario/mergo/LICENSE
new file mode 100644
index 00000000..68668029
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2013 Dario Castañé. All rights reserved.
+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.
diff --git a/vendor/github.com/imdario/mergo/doc.go b/vendor/github.com/imdario/mergo/doc.go
new file mode 100644
index 00000000..6e9aa7ba
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/doc.go
@@ -0,0 +1,44 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// 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 mergo merges same-type structs and maps by setting default values in zero-value fields.
+
+Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
+
+Usage
+
+From my own work-in-progress project:
+
+ type networkConfig struct {
+ Protocol string
+ Address string
+ ServerType string `json: "server_type"`
+ Port uint16
+ }
+
+ type FssnConfig struct {
+ Network networkConfig
+ }
+
+ var fssnDefault = FssnConfig {
+ networkConfig {
+ "tcp",
+ "127.0.0.1",
+ "http",
+ 31560,
+ },
+ }
+
+ // Inside a function [...]
+
+ if err := mergo.Merge(&config, fssnDefault); err != nil {
+ log.Fatal(err)
+ }
+
+ // More code [...]
+
+*/
+package mergo
diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go
new file mode 100644
index 00000000..3f5afa83
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/map.go
@@ -0,0 +1,175 @@
+// Copyright 2014 Dario Castañé. All rights reserved.
+// 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.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+ "fmt"
+ "reflect"
+ "unicode"
+ "unicode/utf8"
+)
+
+func changeInitialCase(s string, mapper func(rune) rune) string {
+ if s == "" {
+ return s
+ }
+ r, n := utf8.DecodeRuneInString(s)
+ return string(mapper(r)) + s[n:]
+}
+
+func isExported(field reflect.StructField) bool {
+ r, _ := utf8.DecodeRuneInString(field.Name)
+ return r >= 'A' && r <= 'Z'
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
+ overwrite := config.Overwrite
+ if dst.CanAddr() {
+ addr := dst.UnsafeAddr()
+ h := 17 * addr
+ seen := visited[h]
+ typ := dst.Type()
+ for p := seen; p != nil; p = p.next {
+ if p.ptr == addr && p.typ == typ {
+ return nil
+ }
+ }
+ // Remember, remember...
+ visited[h] = &visit{addr, typ, seen}
+ }
+ zeroValue := reflect.Value{}
+ switch dst.Kind() {
+ case reflect.Map:
+ dstMap := dst.Interface().(map[string]interface{})
+ for i, n := 0, src.NumField(); i < n; i++ {
+ srcType := src.Type()
+ field := srcType.Field(i)
+ if !isExported(field) {
+ continue
+ }
+ fieldName := field.Name
+ fieldName = changeInitialCase(fieldName, unicode.ToLower)
+ if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
+ dstMap[fieldName] = src.Field(i).Interface()
+ }
+ }
+ case reflect.Ptr:
+ if dst.IsNil() {
+ v := reflect.New(dst.Type().Elem())
+ dst.Set(v)
+ }
+ dst = dst.Elem()
+ fallthrough
+ case reflect.Struct:
+ srcMap := src.Interface().(map[string]interface{})
+ for key := range srcMap {
+ config.overwriteWithEmptyValue = true
+ srcValue := srcMap[key]
+ fieldName := changeInitialCase(key, unicode.ToUpper)
+ dstElement := dst.FieldByName(fieldName)
+ if dstElement == zeroValue {
+ // We discard it because the field doesn't exist.
+ continue
+ }
+ srcElement := reflect.ValueOf(srcValue)
+ dstKind := dstElement.Kind()
+ srcKind := srcElement.Kind()
+ if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
+ srcElement = srcElement.Elem()
+ srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
+ } else if dstKind == reflect.Ptr {
+ // Can this work? I guess it can't.
+ if srcKind != reflect.Ptr && srcElement.CanAddr() {
+ srcPtr := srcElement.Addr()
+ srcElement = reflect.ValueOf(srcPtr)
+ srcKind = reflect.Ptr
+ }
+ }
+
+ if !srcElement.IsValid() {
+ continue
+ }
+ if srcKind == dstKind {
+ if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+ return
+ }
+ } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
+ if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+ return
+ }
+ } else if srcKind == reflect.Map {
+ if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
+ return
+ }
+ } else {
+ return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
+ }
+ }
+ }
+ return
+}
+
+// Map sets fields' values in dst from src.
+// src can be a map with string keys or a struct. dst must be the opposite:
+// if src is a map, dst must be a valid pointer to struct. If src is a struct,
+// dst must be map[string]interface{}.
+// It won't merge unexported (private) fields and will do recursively
+// any exported field.
+// If dst is a map, keys will be src fields' names in lower camel case.
+// Missing key in src that doesn't match a field in dst will be skipped. This
+// doesn't apply if dst is a map.
+// This is separated method from Merge because it is cleaner and it keeps sane
+// semantics: merging equal types, mapping different (restricted) types.
+func Map(dst, src interface{}, opts ...func(*Config)) error {
+ return _map(dst, src, opts...)
+}
+
+// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
+// non-empty src attribute values.
+// Deprecated: Use Map(…) with WithOverride
+func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
+ return _map(dst, src, append(opts, WithOverride)...)
+}
+
+func _map(dst, src interface{}, opts ...func(*Config)) error {
+ var (
+ vDst, vSrc reflect.Value
+ err error
+ )
+ config := &Config{}
+
+ for _, opt := range opts {
+ opt(config)
+ }
+
+ if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+ return err
+ }
+ // To be friction-less, we redirect equal-type arguments
+ // to deepMerge. Only because arguments can be anything.
+ if vSrc.Kind() == vDst.Kind() {
+ return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+ }
+ switch vSrc.Kind() {
+ case reflect.Struct:
+ if vDst.Kind() != reflect.Map {
+ return ErrExpectedMapAsDestination
+ }
+ case reflect.Map:
+ if vDst.Kind() != reflect.Struct {
+ return ErrExpectedStructAsDestination
+ }
+ default:
+ return ErrNotSupported
+ }
+ return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+}
diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go
new file mode 100644
index 00000000..f8de6c54
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/merge.go
@@ -0,0 +1,255 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// 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.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+ "fmt"
+ "reflect"
+)
+
+func hasExportedField(dst reflect.Value) (exported bool) {
+ for i, n := 0, dst.NumField(); i < n; i++ {
+ field := dst.Type().Field(i)
+ if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
+ exported = exported || hasExportedField(dst.Field(i))
+ } else {
+ exported = exported || len(field.PkgPath) == 0
+ }
+ }
+ return
+}
+
+type Config struct {
+ Overwrite bool
+ AppendSlice bool
+ Transformers Transformers
+ overwriteWithEmptyValue bool
+}
+
+type Transformers interface {
+ Transformer(reflect.Type) func(dst, src reflect.Value) error
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
+ overwrite := config.Overwrite
+ overwriteWithEmptySrc := config.overwriteWithEmptyValue
+ config.overwriteWithEmptyValue = false
+
+ if !src.IsValid() {
+ return
+ }
+ if dst.CanAddr() {
+ addr := dst.UnsafeAddr()
+ h := 17 * addr
+ seen := visited[h]
+ typ := dst.Type()
+ for p := seen; p != nil; p = p.next {
+ if p.ptr == addr && p.typ == typ {
+ return nil
+ }
+ }
+ // Remember, remember...
+ visited[h] = &visit{addr, typ, seen}
+ }
+
+ if config.Transformers != nil && !isEmptyValue(dst) {
+ if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
+ err = fn(dst, src)
+ return
+ }
+ }
+
+ switch dst.Kind() {
+ case reflect.Struct:
+ if hasExportedField(dst) {
+ for i, n := 0, dst.NumField(); i < n; i++ {
+ if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
+ return
+ }
+ }
+ } else {
+ if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
+ dst.Set(src)
+ }
+ }
+ case reflect.Map:
+ if dst.IsNil() && !src.IsNil() {
+ dst.Set(reflect.MakeMap(dst.Type()))
+ }
+ for _, key := range src.MapKeys() {
+ srcElement := src.MapIndex(key)
+ if !srcElement.IsValid() {
+ continue
+ }
+ dstElement := dst.MapIndex(key)
+ switch srcElement.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
+ if srcElement.IsNil() {
+ continue
+ }
+ fallthrough
+ default:
+ if !srcElement.CanInterface() {
+ continue
+ }
+ switch reflect.TypeOf(srcElement.Interface()).Kind() {
+ case reflect.Struct:
+ fallthrough
+ case reflect.Ptr:
+ fallthrough
+ case reflect.Map:
+ srcMapElm := srcElement
+ dstMapElm := dstElement
+ if srcMapElm.CanInterface() {
+ srcMapElm = reflect.ValueOf(srcMapElm.Interface())
+ if dstMapElm.IsValid() {
+ dstMapElm = reflect.ValueOf(dstMapElm.Interface())
+ }
+ }
+ if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
+ return
+ }
+ case reflect.Slice:
+ srcSlice := reflect.ValueOf(srcElement.Interface())
+
+ var dstSlice reflect.Value
+ if !dstElement.IsValid() || dstElement.IsNil() {
+ dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
+ } else {
+ dstSlice = reflect.ValueOf(dstElement.Interface())
+ }
+
+ if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
+ dstSlice = srcSlice
+ } else if config.AppendSlice {
+ if srcSlice.Type() != dstSlice.Type() {
+ return fmt.Errorf("cannot append two slice with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
+ }
+ dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
+ }
+ dst.SetMapIndex(key, dstSlice)
+ }
+ }
+ if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
+ continue
+ }
+
+ if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) {
+ if dst.IsNil() {
+ dst.Set(reflect.MakeMap(dst.Type()))
+ }
+ dst.SetMapIndex(key, srcElement)
+ }
+ }
+ case reflect.Slice:
+ if !dst.CanSet() {
+ break
+ }
+ if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
+ dst.Set(src)
+ } else if config.AppendSlice {
+ if src.Type() != dst.Type() {
+ return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
+ }
+ dst.Set(reflect.AppendSlice(dst, src))
+ }
+ case reflect.Ptr:
+ fallthrough
+ case reflect.Interface:
+ if src.IsNil() {
+ break
+ }
+ if src.Kind() != reflect.Interface {
+ if dst.IsNil() || overwrite {
+ if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
+ dst.Set(src)
+ }
+ } else if src.Kind() == reflect.Ptr {
+ if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+ return
+ }
+ } else if dst.Elem().Type() == src.Type() {
+ if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
+ return
+ }
+ } else {
+ return ErrDifferentArgumentsTypes
+ }
+ break
+ }
+ if dst.IsNil() || overwrite {
+ if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
+ dst.Set(src)
+ }
+ } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+ return
+ }
+ default:
+ if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
+ dst.Set(src)
+ }
+ }
+ return
+}
+
+// Merge will fill any empty for value type attributes on the dst struct using corresponding
+// src attributes if they themselves are not empty. dst and src must be valid same-type structs
+// and dst must be a pointer to struct.
+// It won't merge unexported (private) fields and will do recursively any exported field.
+func Merge(dst, src interface{}, opts ...func(*Config)) error {
+ return merge(dst, src, opts...)
+}
+
+// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
+// non-empty src attribute values.
+// Deprecated: use Merge(…) with WithOverride
+func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
+ return merge(dst, src, append(opts, WithOverride)...)
+}
+
+// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
+func WithTransformers(transformers Transformers) func(*Config) {
+ return func(config *Config) {
+ config.Transformers = transformers
+ }
+}
+
+// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
+func WithOverride(config *Config) {
+ config.Overwrite = true
+}
+
+// WithAppendSlice will make merge append slices instead of overwriting it
+func WithAppendSlice(config *Config) {
+ config.AppendSlice = true
+}
+
+func merge(dst, src interface{}, opts ...func(*Config)) error {
+ var (
+ vDst, vSrc reflect.Value
+ err error
+ )
+
+ config := &Config{}
+
+ for _, opt := range opts {
+ opt(config)
+ }
+
+ if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+ return err
+ }
+ if vDst.Type() != vSrc.Type() {
+ return ErrDifferentArgumentsTypes
+ }
+ return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+}
diff --git a/vendor/github.com/imdario/mergo/mergo.go b/vendor/github.com/imdario/mergo/mergo.go
new file mode 100644
index 00000000..a82fea2f
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/mergo.go
@@ -0,0 +1,97 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// 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.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+ "errors"
+ "reflect"
+)
+
+// Errors reported by Mergo when it finds invalid arguments.
+var (
+ ErrNilArguments = errors.New("src and dst must not be nil")
+ ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
+ ErrNotSupported = errors.New("only structs and maps are supported")
+ ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
+ ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
+)
+
+// During deepMerge, must keep track of checks that are
+// in progress. The comparison algorithm assumes that all
+// checks in progress are true when it reencounters them.
+// Visited are stored in a map indexed by 17 * a1 + a2;
+type visit struct {
+ ptr uintptr
+ typ reflect.Type
+ next *visit
+}
+
+// From src/pkg/encoding/json/encode.go.
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ if v.IsNil() {
+ return true
+ }
+ return isEmptyValue(v.Elem())
+ case reflect.Func:
+ return v.IsNil()
+ case reflect.Invalid:
+ return true
+ }
+ return false
+}
+
+func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
+ if dst == nil || src == nil {
+ err = ErrNilArguments
+ return
+ }
+ vDst = reflect.ValueOf(dst).Elem()
+ if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
+ err = ErrNotSupported
+ return
+ }
+ vSrc = reflect.ValueOf(src)
+ // We check if vSrc is a pointer to dereference it.
+ if vSrc.Kind() == reflect.Ptr {
+ vSrc = vSrc.Elem()
+ }
+ return
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
+ if dst.CanAddr() {
+ addr := dst.UnsafeAddr()
+ h := 17 * addr
+ seen := visited[h]
+ typ := dst.Type()
+ for p := seen; p != nil; p = p.next {
+ if p.ptr == addr && p.typ == typ {
+ return nil
+ }
+ }
+ // Remember, remember...
+ visited[h] = &visit{addr, typ, seen}
+ }
+ return // TODO refactor
+}
diff --git a/vendor/github.com/imdario/mergo/testdata/license.yml b/vendor/github.com/imdario/mergo/testdata/license.yml
new file mode 100644
index 00000000..2f1ad008
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/testdata/license.yml
@@ -0,0 +1,4 @@
+import: ../../../../fossene/db/schema/thing.yml
+fields:
+ site: string
+ author: root