mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 20:02:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			178 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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{typ, seen, addr}
 | 
						|
	}
 | 
						|
	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), !config.ShouldNotDereference) || 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 {
 | 
						|
	if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
 | 
						|
		return ErrNonPointerArgument
 | 
						|
	}
 | 
						|
	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)
 | 
						|
}
 |