| // 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() |
| } |