github.com/goki/ki@v1.1.11/ki/props.go (about)

     1  // Copyright (c) 2018, The GoKi Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ki
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"log"
    12  	"reflect"
    13  	"strings"
    14  
    15  	"github.com/goki/ki/kit"
    16  )
    17  
    18  // Props is the type used for holding generic properties -- the actual Go type
    19  // is a mouthful and not very gui-friendly, and we need some special json methods
    20  type Props map[string]interface{}
    21  
    22  var KiT_Props = kit.Types.AddType(&Props{}, PropsProps)
    23  
    24  var PropsProps = Props{
    25  	"basic-type": true, // registers props as a basic type avail for type selection in creating property values -- many cases call for nested properties
    26  }
    27  
    28  // Set sets props value -- safely creates map
    29  func (pr *Props) Set(key string, val interface{}) {
    30  	if *pr == nil {
    31  		*pr = make(Props)
    32  	}
    33  	(*pr)[key] = val
    34  }
    35  
    36  // Prop returns property of given key
    37  func (pr Props) Prop(key string) interface{} {
    38  	return pr[key]
    39  }
    40  
    41  // Delete deletes props value at given key
    42  func (pr Props) Delete(key string) {
    43  	delete(pr, key)
    44  }
    45  
    46  // SubProps returns a value that contains another props, or nil and false if
    47  // it doesn't exist or isn't a Props
    48  func SubProps(pr map[string]interface{}, key string) (Props, bool) {
    49  	sp, ok := pr[key]
    50  	if !ok {
    51  		return nil, false
    52  	}
    53  	spp, ok := sp.(Props)
    54  	if ok {
    55  		return spp, true
    56  	}
    57  	return nil, false
    58  }
    59  
    60  // SubTypeProps returns a value that contains another props, or nil and false if
    61  // it doesn't exist or isn't a Props -- for TypeProps, uses locking
    62  func SubTypeProps(pr map[string]interface{}, key string) (Props, bool) {
    63  	sp, ok := kit.TypeProp(pr, key)
    64  	if !ok {
    65  		return nil, false
    66  	}
    67  	spp, ok := sp.(Props)
    68  	if ok {
    69  		return spp, true
    70  	}
    71  	return nil, false
    72  }
    73  
    74  // SetPropStr is a convenience method for e.g., python wrapper that avoids need to deal
    75  // directly with props interface{} type
    76  func SetPropStr(pr Props, key, val string) {
    77  	pr[key] = val
    78  }
    79  
    80  // SetSubProps is a convenience method for e.g., python wrapper that avoids need to deal
    81  // directly with props interface{} type
    82  func SetSubProps(pr Props, key string, sp Props) {
    83  	pr[key] = sp
    84  }
    85  
    86  // special key prefix indicating type info
    87  var struTypeKey = "__type:"
    88  
    89  // special key prefix for enums
    90  var enumTypeKey = "__enum:"
    91  
    92  // BlankProp is an empty property, for when there isn't any need for the value
    93  type BlankProp struct{}
    94  
    95  // PropStruct is a struct of Name and Value, for use in a PropSlice to hold
    96  // properties that require order information (maps do not retain any order)
    97  type PropStruct struct {
    98  	Name  string
    99  	Value interface{}
   100  }
   101  
   102  // PropSlice is a slice of PropStruct, for when order is important within a
   103  // subset of properties (maps do not retain order) -- can set the value of a
   104  // property to a PropSlice to create an ordered list of property values.
   105  type PropSlice []PropStruct
   106  
   107  // ElemLabel satisfies the gi.SliceLabeler interface to provide labels for slice elements
   108  func (ps *PropSlice) ElemLabel(idx int) string {
   109  	return (*ps)[idx].Name
   110  }
   111  
   112  // SliceProps returns a value that contains a PropSlice, or nil and false if it doesn't
   113  // exist or isn't a PropSlice
   114  func SliceProps(pr map[string]interface{}, key string) (PropSlice, bool) {
   115  	sp, ok := pr[key]
   116  	if !ok {
   117  		return nil, false
   118  	}
   119  	spp, ok := sp.(PropSlice)
   120  	if ok {
   121  		return spp, true
   122  	}
   123  	return nil, false
   124  }
   125  
   126  // SliceTypeProps returns a value that contains a PropSlice, or nil and false if it doesn't
   127  // exist or isn't a PropSlice -- for TypeProps, uses locking
   128  func SliceTypeProps(pr map[string]interface{}, key string) (PropSlice, bool) {
   129  	sp, ok := kit.TypeProp(pr, key)
   130  	if !ok {
   131  		return nil, false
   132  	}
   133  	spp, ok := sp.(PropSlice)
   134  	if ok {
   135  		return spp, true
   136  	}
   137  	return nil, false
   138  }
   139  
   140  // CopyProps copies properties from source to destination map.  If deepCopy
   141  // is true, then any values that are Props or PropSlice are copied too
   142  // *dest can be nil, in which case it is created.
   143  func CopyProps(dest *map[string]interface{}, src map[string]interface{}, deepCopy bool) {
   144  	if *dest == nil {
   145  		*dest = make(Props, len(src))
   146  	}
   147  	for key, val := range src {
   148  		if deepCopy {
   149  			if pv, ok := val.(map[string]interface{}); ok {
   150  				var nval Props
   151  				nval.CopyFrom(pv, deepCopy)
   152  				(*dest)[key] = nval
   153  				continue
   154  			} else if pv, ok := val.(Props); ok {
   155  				var nval Props
   156  				nval.CopyFrom(pv, deepCopy)
   157  				(*dest)[key] = nval
   158  				continue
   159  			} else if pv, ok := val.(PropSlice); ok {
   160  				var nval PropSlice
   161  				nval.CopyFrom(pv, deepCopy)
   162  				(*dest)[key] = nval
   163  				continue
   164  			}
   165  		}
   166  		(*dest)[key] = val
   167  	}
   168  }
   169  
   170  // CopyFrom copies properties from source to receiver destination map.  If deepCopy
   171  // is true, then any values that are Props or PropSlice are copied too
   172  // *dest can be nil, in which case it is created.
   173  func (dest *Props) CopyFrom(src map[string]interface{}, deepCopy bool) {
   174  	CopyProps((*map[string]interface{})(dest), src, deepCopy)
   175  }
   176  
   177  // CopyFrom copies properties from source to destination propslice.  If deepCopy
   178  // is true, then any values that are Props or PropSlice are copied too
   179  // *dest can be nil, in which case it is created.
   180  func (dest *PropSlice) CopyFrom(src PropSlice, deepCopy bool) {
   181  	if *dest == nil {
   182  		*dest = make(PropSlice, len(src))
   183  	}
   184  	for i, val := range src {
   185  		if deepCopy {
   186  			if pv, ok := val.Value.(map[string]interface{}); ok {
   187  				var nval Props
   188  				CopyProps((*map[string]interface{})(&nval), pv, deepCopy)
   189  				(*dest)[i] = PropStruct{Name: val.Name, Value: nval}
   190  				continue
   191  			} else if pv, ok := val.Value.(Props); ok {
   192  				var nval Props
   193  				CopyProps((*map[string]interface{})(&nval), pv, deepCopy)
   194  				(*dest)[i] = PropStruct{Name: val.Name, Value: nval}
   195  				continue
   196  			} else if pv, ok := val.Value.(PropSlice); ok {
   197  				var nval PropSlice
   198  				nval.CopyFrom(pv, deepCopy)
   199  				(*dest)[i] = PropStruct{Name: val.Name, Value: nval}
   200  				continue
   201  			}
   202  		}
   203  		(*dest)[i] = src[i]
   204  	}
   205  }
   206  
   207  // MarshalJSON saves the type information for each struct used in props, as a
   208  // separate key with the __type: prefix -- this allows the Unmarshal to
   209  // create actual types
   210  func (p Props) MarshalJSON() ([]byte, error) {
   211  	nk := len(p)
   212  	b := make([]byte, 0, nk*100+20)
   213  	if nk == 0 {
   214  		b = append(b, []byte("null")...)
   215  		return b, nil
   216  	}
   217  	b = append(b, []byte("{")...)
   218  	cnt := 0
   219  	var err error
   220  	for key, val := range p {
   221  		vt := kit.NonPtrType(reflect.TypeOf(val))
   222  		vk := vt.Kind()
   223  		if vk == reflect.Struct {
   224  			knm := kit.Types.TypeName(vt)
   225  			tstr := fmt.Sprintf("\"%v%v\": \"%v\",", struTypeKey, key, knm)
   226  			b = append(b, []byte(tstr)...)
   227  		}
   228  		kstr := fmt.Sprintf("\"%v\": ", key)
   229  		b = append(b, []byte(kstr)...)
   230  
   231  		var kb []byte
   232  		kb, err = json.Marshal(val)
   233  		if err != nil {
   234  			log.Printf("error doing json.Marshall from val: %v\n%v\n", val, err)
   235  			log.Printf("output to point of error: %v\n", string(b))
   236  		} else {
   237  			if vk >= reflect.Int && vk <= reflect.Uint64 && kit.Enums.TypeRegistered(vt) {
   238  				knm := kit.Types.TypeName(vt)
   239  				kb, _ = json.Marshal(val)
   240  				estr := fmt.Sprintf("\"%v(%v)%v\"", enumTypeKey, knm, string(bytes.Trim(kb, "\"")))
   241  				b = append(b, []byte(estr)...)
   242  			} else {
   243  				b = append(b, kb...)
   244  			}
   245  		}
   246  		if cnt < nk-1 {
   247  			b = append(b, []byte(",")...)
   248  		}
   249  		cnt++
   250  	}
   251  	b = append(b, []byte("}")...)
   252  	// fmt.Printf("json out: %v\n", string(b))
   253  	return b, nil
   254  }
   255  
   256  // UnmarshalJSON parses the type information in the map to restore actual
   257  // objects -- this is super inefficient and really needs a native parser, but
   258  // props are likely to be relatively small
   259  func (p *Props) UnmarshalJSON(b []byte) error {
   260  	// fmt.Printf("json in: %v\n", string(b))
   261  	if bytes.Equal(b, []byte("null")) {
   262  		*p = nil
   263  		return nil
   264  	}
   265  
   266  	// load into a temporary map and then process
   267  	tmp := make(map[string]interface{})
   268  	err := json.Unmarshal(b, &tmp)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	*p = make(Props, len(tmp))
   274  
   275  	// create all the structure objects from the list -- have to do this first to get all
   276  	// the structs made b/c the order is random..
   277  	for key, val := range tmp {
   278  		if strings.HasPrefix(key, struTypeKey) {
   279  			pkey := strings.TrimPrefix(key, struTypeKey)
   280  			rval := tmp[pkey]
   281  			tn := val.(string)
   282  			typ := kit.Types.Type(tn)
   283  			if typ == nil {
   284  				log.Printf("ki.Props: cannot load struct of type %v -- not registered in kit.Types\n", tn)
   285  				continue
   286  			}
   287  			if IsKi(typ) { // note: not really a good idea to store ki's in maps, but..
   288  				kival := NewOfType(typ)
   289  				InitNode(kival)
   290  				if kival != nil {
   291  					// fmt.Printf("stored new ki of type %v in key: %v\n", typ.String(), pkey)
   292  					tmpb, _ := json.Marshal(rval) // string rep of this
   293  					err = kival.ReadJSON(bytes.NewReader(tmpb))
   294  					if err != nil {
   295  						log.Printf("ki.Props failed to load Ki struct of type %v with error: %v\n", typ.String(), err)
   296  					}
   297  					(*p)[pkey] = kival
   298  				}
   299  			} else {
   300  				stval := reflect.New(typ).Interface()
   301  				// fmt.Printf("stored new struct of type %v in key: %v\n", typ.String(), pkey)
   302  				tmpb, _ := json.Marshal(rval) // string rep of this
   303  				err = json.Unmarshal(tmpb, stval)
   304  				if err != nil {
   305  					log.Printf("ki.Props failed to load struct of type %v with error: %v\n", typ.String(), err)
   306  				}
   307  				(*p)[pkey] = reflect.ValueOf(stval).Elem().Interface()
   308  			}
   309  		}
   310  	}
   311  
   312  	// now can re-iterate
   313  	for key, val := range tmp {
   314  		if strings.HasPrefix(key, struTypeKey) {
   315  			continue
   316  		}
   317  		if _, ok := (*p)[key]; ok { // already created -- was a struct -- skip
   318  			continue
   319  		}
   320  		// look for sub-maps, make them props..
   321  		if _, ok := val.(map[string]interface{}); ok {
   322  			// fmt.Printf("stored new Props map in key: %v\n", key)
   323  			subp := Props{}
   324  			tmpb, _ := json.Marshal(val) // string rep of this
   325  			err = json.Unmarshal(tmpb, &subp)
   326  			if err != nil {
   327  				log.Printf("ki.Props failed to load sub-Props with error: %v\n", err)
   328  			}
   329  			(*p)[key] = subp
   330  		} else { // straight copy
   331  			if sval, ok := val.(string); ok {
   332  				if strings.HasPrefix(sval, enumTypeKey) {
   333  					tn := strings.TrimPrefix(sval, enumTypeKey)
   334  					rpi := strings.Index(tn, ")")
   335  					str := tn[rpi+1:]
   336  					tn = tn[1:rpi]
   337  					etyp := kit.Enums.Enum(tn)
   338  					if etyp != nil {
   339  						eval := kit.EnumIfaceFromString(str, etyp)
   340  						(*p)[key] = eval
   341  						// fmt.Printf("decoded enum typ %v into actual value: %v from %v\n", etyp.String(), eval, str)
   342  					} else {
   343  						(*p)[key] = str
   344  					}
   345  					continue
   346  				}
   347  			}
   348  			(*p)[key] = val
   349  		}
   350  	}
   351  
   352  	return nil
   353  }