diff options
| author | Dave Henderson <dhenderson@gmail.com> | 2019-01-29 22:00:57 -0500 |
|---|---|---|
| committer | Dave Henderson <dhenderson@gmail.com> | 2019-01-29 22:29:22 -0500 |
| commit | 8a50b114e18069b1be388102a128869a9861555b (patch) | |
| tree | ead9d88ba23658da8515c5b494971060b6effbfd | |
| parent | 8695b4f6d86bfa3c241f65bf362e36600da97495 (diff) | |
New coll.Merge function
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
| -rw-r--r-- | Gopkg.lock | 9 | ||||
| -rw-r--r-- | coll/coll.go | 14 | ||||
| -rw-r--r-- | coll/coll_test.go | 31 | ||||
| -rw-r--r-- | docs-src/content/functions/coll.yml | 31 | ||||
| -rw-r--r-- | docs/content/functions/coll.md | 45 | ||||
| -rw-r--r-- | funcs/coll.go | 6 | ||||
| -rw-r--r-- | tests/integration/collection_test.go | 64 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/LICENSE | 28 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/doc.go | 44 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/map.go | 175 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/merge.go | 255 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/mergo.go | 97 | ||||
| -rw-r--r-- | vendor/github.com/imdario/mergo/testdata/license.yml | 4 |
13 files changed, 803 insertions, 0 deletions
@@ -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 |
