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