225 lines
6.4 KiB
Go
225 lines
6.4 KiB
Go
// Package builder provides a method for writing fluent immutable builders.
|
|
package builder
|
|
|
|
import (
|
|
"github.com/lann/ps"
|
|
"go/ast"
|
|
"reflect"
|
|
)
|
|
|
|
// Builder stores a set of named values.
|
|
//
|
|
// New types can be declared with underlying type Builder and used with the
|
|
// functions in this package. See example.
|
|
//
|
|
// Instances of Builder should be treated as immutable. It is up to the
|
|
// implementor to ensure mutable values set on a Builder are not mutated while
|
|
// the Builder is in use.
|
|
type Builder struct {
|
|
builderMap ps.Map
|
|
}
|
|
|
|
var (
|
|
EmptyBuilder = Builder{ps.NewMap()}
|
|
emptyBuilderValue = reflect.ValueOf(EmptyBuilder)
|
|
)
|
|
|
|
func getBuilderMap(builder interface{}) ps.Map {
|
|
b := convert(builder, Builder{}).(Builder)
|
|
|
|
if b.builderMap == nil {
|
|
return ps.NewMap()
|
|
}
|
|
|
|
return b.builderMap
|
|
}
|
|
|
|
// Set returns a copy of the given builder with a new value set for the given
|
|
// name.
|
|
//
|
|
// Set (and all other functions taking a builder in this package) will panic if
|
|
// the given builder's underlying type is not Builder.
|
|
func Set(builder interface{}, name string, v interface{}) interface{} {
|
|
b := Builder{getBuilderMap(builder).Set(name, v)}
|
|
return convert(b, builder)
|
|
}
|
|
|
|
// Delete returns a copy of the given builder with the given named value unset.
|
|
func Delete(builder interface{}, name string) interface{} {
|
|
b := Builder{getBuilderMap(builder).Delete(name)}
|
|
return convert(b, builder)
|
|
}
|
|
|
|
// Append returns a copy of the given builder with new value(s) appended to the
|
|
// named list. If the value was previously unset or set with Set (even to a e.g.
|
|
// slice values), the new value(s) will be appended to an empty list.
|
|
func Append(builder interface{}, name string, vs ...interface{}) interface{} {
|
|
return Extend(builder, name, vs)
|
|
}
|
|
|
|
// Extend behaves like Append, except it takes a single slice or array value
|
|
// which will be concatenated to the named list.
|
|
//
|
|
// Unlike a variadic call to Append - which requires a []interface{} value -
|
|
// Extend accepts slices or arrays of any type.
|
|
//
|
|
// Extend will panic if the given value is not a slice, array, or nil.
|
|
func Extend(builder interface{}, name string, vs interface{}) interface{} {
|
|
if vs == nil {
|
|
return builder
|
|
}
|
|
|
|
maybeList, ok := getBuilderMap(builder).Lookup(name)
|
|
|
|
var list ps.List
|
|
if ok {
|
|
list, ok = maybeList.(ps.List)
|
|
}
|
|
if !ok {
|
|
list = ps.NewList()
|
|
}
|
|
|
|
forEach(vs, func(v interface{}) {
|
|
list = list.Cons(v)
|
|
})
|
|
|
|
return Set(builder, name, list)
|
|
}
|
|
|
|
func listToSlice(list ps.List, arrayType reflect.Type) reflect.Value {
|
|
size := list.Size()
|
|
slice := reflect.MakeSlice(arrayType, size, size)
|
|
for i := size - 1; i >= 0; i-- {
|
|
val := reflect.ValueOf(list.Head())
|
|
slice.Index(i).Set(val)
|
|
list = list.Tail()
|
|
}
|
|
return slice
|
|
}
|
|
|
|
var anyArrayType = reflect.TypeOf([]interface{}{})
|
|
|
|
// Get retrieves a single named value from the given builder.
|
|
// If the value has not been set, it returns (nil, false). Otherwise, it will
|
|
// return (value, true).
|
|
//
|
|
// If the named value was last set with Append or Extend, the returned value
|
|
// will be a slice. If the given Builder has been registered with Register or
|
|
// RegisterType and the given name is an exported field of the registered
|
|
// struct, the returned slice will have the same type as that field. Otherwise
|
|
// the slice will have type []interface{}. It will panic if the given name is a
|
|
// registered struct's exported field and the value set on the Builder is not
|
|
// assignable to the field.
|
|
func Get(builder interface{}, name string) (interface{}, bool) {
|
|
val, ok := getBuilderMap(builder).Lookup(name)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
list, isList := val.(ps.List)
|
|
if isList {
|
|
arrayType := anyArrayType
|
|
|
|
if ast.IsExported(name) {
|
|
structType := getBuilderStructType(reflect.TypeOf(builder))
|
|
if structType != nil {
|
|
field, ok := (*structType).FieldByName(name)
|
|
if ok {
|
|
arrayType = field.Type
|
|
}
|
|
}
|
|
}
|
|
|
|
val = listToSlice(list, arrayType).Interface()
|
|
}
|
|
|
|
return val, true
|
|
}
|
|
|
|
// GetMap returns a map[string]interface{} of the values set in the given
|
|
// builder.
|
|
//
|
|
// See notes on Get regarding returned slices.
|
|
func GetMap(builder interface{}) map[string]interface{} {
|
|
m := getBuilderMap(builder)
|
|
structType := getBuilderStructType(reflect.TypeOf(builder))
|
|
|
|
ret := make(map[string]interface{}, m.Size())
|
|
|
|
m.ForEach(func(name string, val ps.Any) {
|
|
list, isList := val.(ps.List)
|
|
if isList {
|
|
arrayType := anyArrayType
|
|
|
|
if structType != nil {
|
|
field, ok := (*structType).FieldByName(name)
|
|
if ok {
|
|
arrayType = field.Type
|
|
}
|
|
}
|
|
|
|
val = listToSlice(list, arrayType).Interface()
|
|
}
|
|
|
|
ret[name] = val
|
|
})
|
|
|
|
return ret
|
|
}
|
|
|
|
// GetStruct builds a new struct from the given registered builder.
|
|
// It will return nil if the given builder's type has not been registered with
|
|
// Register or RegisterValue.
|
|
//
|
|
// All values set on the builder with names that start with an uppercase letter
|
|
// (i.e. which would be exported if they were identifiers) are assigned to the
|
|
// corresponding exported fields of the struct.
|
|
//
|
|
// GetStruct will panic if any of these "exported" values are not assignable to
|
|
// their corresponding struct fields.
|
|
func GetStruct(builder interface{}) interface{} {
|
|
structVal := newBuilderStruct(reflect.TypeOf(builder))
|
|
if structVal == nil {
|
|
return nil
|
|
}
|
|
return scanStruct(builder, structVal)
|
|
}
|
|
|
|
// GetStructLike builds a new struct from the given builder with the same type
|
|
// as the given struct.
|
|
//
|
|
// All values set on the builder with names that start with an uppercase letter
|
|
// (i.e. which would be exported if they were identifiers) are assigned to the
|
|
// corresponding exported fields of the struct.
|
|
//
|
|
// ScanStruct will panic if any of these "exported" values are not assignable to
|
|
// their corresponding struct fields.
|
|
func GetStructLike(builder interface{}, strct interface{}) interface{} {
|
|
structVal := reflect.New(reflect.TypeOf(strct)).Elem()
|
|
return scanStruct(builder, &structVal)
|
|
}
|
|
|
|
func scanStruct(builder interface{}, structVal *reflect.Value) interface{} {
|
|
getBuilderMap(builder).ForEach(func(name string, val ps.Any) {
|
|
if ast.IsExported(name) {
|
|
field := structVal.FieldByName(name)
|
|
|
|
var value reflect.Value
|
|
switch v := val.(type) {
|
|
case nil:
|
|
switch field.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
value = reflect.Zero(field.Type())
|
|
}
|
|
// nil is not valid for this Type; Set will panic
|
|
case ps.List:
|
|
value = listToSlice(v, field.Type())
|
|
default:
|
|
value = reflect.ValueOf(val)
|
|
}
|
|
field.Set(value)
|
|
}
|
|
})
|
|
|
|
return structVal.Interface()
|
|
}
|