github.com/goki/ki@v1.1.17/kit/enums.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 kit
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"log"
    11  	"reflect"
    12  	"strings"
    13  
    14  	"github.com/goki/ki/bitflag"
    15  )
    16  
    17  // design notes: for methods that return string, not passing error b/c you can
    18  // easily check for null string, and registering errors in log for setter
    19  // methods, returning error and also logging so it is safe to ignore err if
    20  // you don't care
    21  
    22  // Bit flags are setup just using the ordinal count iota, and the only diff is
    23  // the methods which do 1 << flag when operating on them
    24  // see bitflag package
    25  
    26  // EnumRegistry is a map from an enum-style const int type name to a
    27  // corresponding reflect.Type and conversion methods generated by (modified)
    28  // stringer that convert to / from strings.
    29  //
    30  // Each such type must be explicitly registered by calling AddEnum
    31  // in an expression that also initializes a new global variable
    32  // that is then useful whenever you need to specify that type:
    33  //
    34  //	var KiT_MyEnum = kit.Enums.AddEnum(MyEnumN, bitFlag true/false,
    35  //	   TypeNameProps (or nil))
    36  //
    37  // where MyEnum is the name of the type, MyEnumN is the enum value
    38  // representing the number of defined enums (always good practice to define
    39  // this value, for ease of extension by others), and TypeNameProps is nil or a
    40  // map[string]interface{} of properties, OR:
    41  //
    42  //	var KiT_MyEnum = kit.Enums.AddEnumAltLower(MyEnumN, bitFlag true/false,
    43  //	   TypeNameProps, "Prefix")
    44  //
    45  // which automatically registers alternative names as lower-case versions of
    46  // const names with given prefix removed -- often what is used in e.g., json
    47  // or xml kinds of formats.
    48  //
    49  // The resulting type name is registered using a *short* package-qualified
    50  // version of the type name, with just the last directory name . Type.
    51  // This is the usual name used in programming Go.  All properties are
    52  // registered using this same short name.
    53  //
    54  // special properties:
    55  //
    56  // * "N": max value of enum defined -- number of enum entries (assuming
    57  // ordinal, which is all that is currently supported here)
    58  //
    59  // * "BitFlag": true -- each value represents a bit in a set of bit flags, so
    60  // the string rep of a value contains an or-list of names for each bit set,
    61  // separated by "|".  Use the bitflag package to set and clear bits while
    62  // keeping the definition of the flags as a standard ordinal integer value --
    63  // much more flexible than pre-compiling the bitmasks.  Usually should be an
    64  // int64 type.
    65  //
    66  // * "AltStrings": map[int64]string -- provides an alternative string mapping for
    67  // the enum values
    68  //
    69  // * "ParType": reflect.Type -- parent type that this extends.
    70  //
    71  // Also recommend defining JSON I/O functions for each registered enum -- much
    72  // safer to save enums as strings than using their raw numerical values, which
    73  // can change over time:
    74  //
    75  //	func (ev TestFlags) MarshalJSON() ([]byte, error) { return kit.EnumMarshalJSON(ev) }
    76  //	func (ev *TestFlags) UnmarshalJSON() ([]byte, error) { return kit.EnumUnmarshalJSON(ev) }
    77  //
    78  // And any value that will be used as a key in a map must define Text versions (which don't use quotes)
    79  //
    80  //	func (ev TestFlags) MarshalText() ([]byte, error) { return kit.EnumMarshalText(ev) }
    81  //	func (ev *TestFlags) UnmarshalText() ([]byte, error) { return kit.EnumUnmarshalText(ev) }
    82  type EnumRegistry struct {
    83  	// Enums is a map from the *short* package-qualified name to reflect.Type
    84  	Enums map[string]reflect.Type
    85  
    86  	// Props contains properties that can be associated with each enum type
    87  	// e.g., "BitFlag": true, "AltStrings" : map[int64]string, or other custom settings.
    88  	// The key here is the short package-qualified name
    89  	Props map[string]map[string]any
    90  
    91  	// Vals contains cached EnumValue representations of the enum values.
    92  	// Used by Values method.
    93  	Vals map[string][]EnumValue
    94  }
    95  
    96  // Enums is master registry of enum types -- can also create your own package-specific ones
    97  var Enums EnumRegistry
    98  
    99  const (
   100  	// BitFlag is used for AddEnum to indicate that this is a bit flag enum
   101  	BitFlag = true
   102  
   103  	// NotBitFlag is used for AddEnum to indicate that this is NOT a bit flag enum
   104  	NotBitFlag = false
   105  )
   106  
   107  // AddEnum adds a given type to the registry -- requires the N value to set N
   108  // from and grab type info from -- if bitFlag then sets BitFlag property, and
   109  // each value represents a bit in a set of bit flags, so the string rep of a
   110  // value contains an or-list of names for each bit set, separated by | -- can
   111  // also add additional properties -- they are copied so can be re-used across enums
   112  func (tr *EnumRegistry) AddEnum(en any, bitFlag bool, props map[string]any) reflect.Type {
   113  	if tr.Enums == nil {
   114  		tr.Enums = make(map[string]reflect.Type)
   115  		tr.Props = make(map[string]map[string]any)
   116  		tr.Vals = make(map[string][]EnumValue)
   117  	}
   118  
   119  	// get the pointer-to version and elem so it is a settable type!
   120  	typ := PtrType(reflect.TypeOf(en)).Elem()
   121  	n := EnumIfaceToInt64(en)
   122  	snm := ShortTypeName(typ)
   123  	tr.Enums[snm] = typ
   124  	if props != nil {
   125  		// make a copy of props for enums -- often shared
   126  		nwprops := make(map[string]any, len(props))
   127  		for key, val := range props {
   128  			nwprops[key] = val
   129  		}
   130  		tr.Props[snm] = nwprops
   131  	}
   132  	tp := tr.Properties(snm)
   133  	tp["N"] = n
   134  	if bitFlag {
   135  		tp := tr.Properties(snm)
   136  		tp["BitFlag"] = true
   137  		EnumBitDepthCheck(typ, n)
   138  	}
   139  	// fmt.Printf("added enum: %v with n: %v\n", tn, n)
   140  	return typ
   141  }
   142  
   143  // EnumBitDepthCheck checks if given type can handle given number of bit flags
   144  func EnumBitDepthCheck(typ reflect.Type, n int64) error {
   145  	cp := typ.Size() * 8
   146  	if n > int64(cp) {
   147  		err := fmt.Errorf("kit.EnumBitDepthCheck ERROR: enum: %v is a bitflag of kind: %v with capacity of: %d bits, but more flags were defined: %d", ShortTypeName(typ), typ.Kind(), cp, n)
   148  		log.Println(err)
   149  		return err
   150  	}
   151  	return nil
   152  }
   153  
   154  // AddEnumAltLower adds a given type to the registry -- requires the N value
   155  // to set N from and grab type info from -- automatically initializes
   156  // AltStrings alternative string map based on the name with given prefix
   157  // removed (e.g., a type name-based prefix) and lower-cased -- also requires
   158  // the number of enums -- assumes starts at 0
   159  func (tr *EnumRegistry) AddEnumAltLower(en any, bitFlag bool, props map[string]any, prefix string) reflect.Type {
   160  	typ := tr.AddEnum(en, bitFlag, props)
   161  	n := EnumIfaceToInt64(en)
   162  	snm := ShortTypeName(typ)
   163  	alts := make(map[int64]string)
   164  	tp := tr.Properties(snm)
   165  	for i := int64(0); i < n; i++ {
   166  		str := EnumInt64ToString(i, typ)
   167  		str = strings.ToLower(strings.TrimPrefix(str, prefix))
   168  		alts[i] = str
   169  	}
   170  	tp["AltStrings"] = alts
   171  	return typ
   172  }
   173  
   174  // AddEnumExt adds a given type to the registry that extends an existing parTyp enum.
   175  // Requires the N value to set N from and grab type info from.
   176  // if bitFlag then sets BitFlag property, and each value represents a bit in a set of bit
   177  // flags, so the string rep of a value contains an or-list of names for each bit set,
   178  // separated by | -- can also add additional properties -- they are copied so
   179  // can be re-used across enums
   180  func (tr *EnumRegistry) AddEnumExt(parTyp reflect.Type, en any, bitFlag bool, props map[string]any) reflect.Type {
   181  	typ := tr.AddEnum(en, bitFlag, props)
   182  	snm := ShortTypeName(typ)
   183  	if parTyp == typ {
   184  		log.Printf("kit.Enums.AddEnumExt: parent type: %v is same as type being defined -- must be different!\n", parTyp.String())
   185  	} else {
   186  		tr.SetProp(snm, "ParType", parTyp)
   187  	}
   188  	return typ
   189  }
   190  
   191  // AddEnumExtAltLower adds a given type to the registry that extends an existing parTyp enum.
   192  // Requires the N value to set N from and grab type info from.
   193  // Automatically initializes AltStrings alternative string map based on the name with
   194  // given prefix removed (e.g., a type name-based prefix) and lower-cased.
   195  // Also requires the number of enums -- assumes starts at end of parent.
   196  func (tr *EnumRegistry) AddEnumExtAltLower(parTyp reflect.Type, en any, bitFlag bool, props map[string]any, prefix string) reflect.Type {
   197  	typ := tr.AddEnumExt(parTyp, en, bitFlag, props)
   198  	n := EnumIfaceToInt64(en)
   199  	snm := ShortTypeName(typ)
   200  	alts := make(map[int64]string)
   201  	tp := tr.Properties(snm)
   202  	pnm := ShortTypeName(parTyp)
   203  	pp := tr.Properties(pnm)
   204  	pn, _ := ToInt(pp["N"])
   205  	if palti, ok := pp["AltStrings"]; ok {
   206  		palt := palti.(map[int64]string)
   207  		for k, v := range palt {
   208  			alts[k] = v
   209  		}
   210  	}
   211  	for i := int64(pn); i < n; i++ {
   212  		str := EnumInt64ToString(i, typ)
   213  		str = strings.ToLower(strings.TrimPrefix(str, prefix))
   214  		alts[i] = str
   215  	}
   216  	tp["AltStrings"] = alts
   217  	return typ
   218  }
   219  
   220  // Enum finds an enum type based on its *short* package-qualified type name
   221  // returns nil if not found.
   222  func (tr *EnumRegistry) Enum(name string) reflect.Type {
   223  	return tr.Enums[name]
   224  }
   225  
   226  // TypeRegistered returns true if the given type is registered as an enum type.
   227  func (tr *EnumRegistry) TypeRegistered(typ reflect.Type) bool {
   228  	enumName := ShortTypeName(typ)
   229  	_, ok := tr.Enums[enumName]
   230  	return ok
   231  }
   232  
   233  // Props returns properties for this type based on short package-qualified name.
   234  // Makes props map if not already made.
   235  func (tr *EnumRegistry) Properties(enumName string) map[string]any {
   236  	tp, ok := tr.Props[enumName]
   237  	if !ok {
   238  		tp = make(map[string]any)
   239  		tr.Props[enumName] = tp
   240  	}
   241  	return tp
   242  }
   243  
   244  // Prop safely finds an enum type property from short package-qualified name
   245  // and property key.  Returns nil if not found.
   246  func (tr *EnumRegistry) Prop(enumName, propKey string) any {
   247  	tp, ok := tr.Props[enumName]
   248  	if !ok {
   249  		// fmt.Printf("no props for enum type: %v\n", enumName)
   250  		return nil
   251  	}
   252  	p, ok := tp[propKey]
   253  	if !ok {
   254  		// fmt.Printf("no props for key: %v\n", propKey)
   255  		return nil
   256  	}
   257  	return p
   258  }
   259  
   260  // SetProp safely sets given property for given enum name
   261  func (tr *EnumRegistry) SetProp(enumName, propKey string, val any) {
   262  	tp := tr.Properties(enumName)
   263  	tp[propKey] = val
   264  }
   265  
   266  // AltStrings returns optional alternative string map for enums -- e.g.,
   267  // lower-case, without prefixes etc -- can put multiple such alt strings in
   268  // the one string with your own separator, in a predefined order, if
   269  // necessary, and just call strings.Split on those and get the one you want.
   270  // Uses short package-qualified name. Returns nil if not set.
   271  func (tr *EnumRegistry) AltStrings(enumName string) map[int64]string {
   272  	ps := tr.Prop(enumName, "AltStrings")
   273  	if ps == nil {
   274  		return nil
   275  	}
   276  	m, ok := ps.(map[int64]string)
   277  	if !ok {
   278  		log.Printf("kit.EnumRegistry AltStrings error: AltStrings property must be a map[int64]string type, is not -- is instead: %T\n", m)
   279  		return nil
   280  	}
   281  	return m
   282  }
   283  
   284  // ParType returns optional parent type that given type extends -- nil if not set.
   285  func (tr *EnumRegistry) ParType(enumName string) reflect.Type {
   286  	pti := tr.Prop(enumName, "ParType")
   287  	if pti == nil {
   288  		return nil
   289  	}
   290  	pt, ok := pti.(reflect.Type)
   291  	if !ok {
   292  		log.Printf("kit.EnumRegistry ParType error: ParType property must be a reflect.Type, is not -- is instead: %T\n", pt)
   293  		return nil
   294  	}
   295  	return pt
   296  }
   297  
   298  // NVals returns the number of defined enum values for given enum interface
   299  func (tr *EnumRegistry) NVals(eval any) int64 {
   300  	typ := reflect.TypeOf(eval)
   301  	nm := ShortTypeName(typ)
   302  	n := tr.Prop(nm, "N")
   303  	if n != nil {
   304  		return n.(int64)
   305  	}
   306  	log.Printf("kit.EnumRegistry: no N registered for type: %v\n", nm)
   307  	return 0
   308  }
   309  
   310  // IsBitFlag checks if this enum is for bit flags instead of mutually-exclusive int
   311  // values -- checks BitFlag property -- if true string rep of a value contains
   312  // an or-list of names for each bit set, separated by |
   313  func (tr *EnumRegistry) IsBitFlag(typ reflect.Type) bool {
   314  	b, _ := ToBool(tr.Prop(ShortTypeName(typ), "BitFlag"))
   315  	return b
   316  }
   317  
   318  ////////////////////////////////////////////////////////////////////////////////////////
   319  //   To / From Int64 for generic interface{} and reflect.Value
   320  
   321  // EnumIfaceToInt64 converts an enum interface{} into an int64 using reflect
   322  // -- just use int64(eval) when you have the enum value in hand -- this is
   323  // when you just have a generic interface{}
   324  func EnumIfaceToInt64(eval any) int64 {
   325  	ev := NonPtrValue(reflect.ValueOf(eval))
   326  	var ival int64
   327  	reflect.ValueOf(&ival).Elem().Set(ev.Convert(reflect.TypeOf(ival)))
   328  	return ival
   329  }
   330  
   331  // SetEnumIfaceFromInt64 sets enum interface{} value from int64 value -- must
   332  // pass a pointer to the enum and also needs raw type of the enum as well --
   333  // can't get it from the interface{} reliably
   334  func SetEnumIfaceFromInt64(eval any, ival int64, typ reflect.Type) error {
   335  	if reflect.TypeOf(eval).Kind() != reflect.Ptr {
   336  		err := fmt.Errorf("kit.SetEnumFromInt64: must pass a pointer to the enum: Type: %v, Kind: %v\n", reflect.TypeOf(eval).Name(), reflect.TypeOf(eval).Kind())
   337  		log.Printf("%v", err)
   338  		return err
   339  	}
   340  	reflect.ValueOf(eval).Elem().Set(reflect.ValueOf(ival).Convert(typ))
   341  	return nil
   342  }
   343  
   344  // SetEnumValueFromInt64 sets enum value from int64 value, using a
   345  // reflect.Value representation of the enum -- does more checking and can get
   346  // type from value compared to Iface version
   347  func SetEnumValueFromInt64(eval reflect.Value, ival int64) error {
   348  	if eval.Kind() != reflect.Ptr {
   349  		err := fmt.Errorf("kit.SetEnumValueFromInt64: must pass a pointer value to the enum: Type: %v, Kind: %v\n", eval.Type().String(), eval.Kind())
   350  		log.Printf("%v", err)
   351  		return err
   352  	}
   353  	npt := NonPtrType(eval.Type())
   354  	eval.Elem().Set(reflect.ValueOf(ival).Convert(npt))
   355  	return nil
   356  }
   357  
   358  // EnumIfaceFromInt64 returns an interface{} value which is an enum value of
   359  // given type (not a pointer to it), set to given integer value
   360  func EnumIfaceFromInt64(ival int64, typ reflect.Type) any {
   361  	evn := reflect.New(typ)
   362  	SetEnumValueFromInt64(evn, ival)
   363  	return evn.Elem().Interface()
   364  }
   365  
   366  ////////////////////////////////////////////////////////////////////////////////////////
   367  //   To / From String for generic interface{} and reflect.Value
   368  
   369  // EnumIfaceToString converts an enum interface{} value to its corresponding
   370  // string value, using fmt.Stringer interface directly -- same effect as
   371  // calling fmt.Sprintf("%v") but this is slightly faster
   372  func EnumIfaceToString(eval any) string {
   373  	strer, ok := eval.(fmt.Stringer) // will fail if not impl
   374  	if !ok {
   375  		log.Printf("kit.EnumIfaceToString: fmt.Stringer interface not supported by type %v\n", reflect.TypeOf(eval).Name())
   376  		return ""
   377  	}
   378  	return strer.String()
   379  }
   380  
   381  // EnumInt64ToString first converts an int64 to enum of given type, and then
   382  // converts that to a string value
   383  func EnumInt64ToString(ival int64, typ reflect.Type) string {
   384  	ev := EnumIfaceFromInt64(ival, typ)
   385  	return EnumIfaceToString(ev)
   386  }
   387  
   388  // EnumIfaceFromString returns an interface{} value which is an enum value of
   389  // given type (not a pointer to it), set to given string value -- requires
   390  // reflect type of enum
   391  func EnumIfaceFromString(str string, typ reflect.Type) any {
   392  	evn := reflect.New(typ)
   393  	SetEnumValueFromString(evn, str)
   394  	return evn.Elem().Interface()
   395  }
   396  
   397  // EnumIfaceToAltString converts an enum interface{} value to its
   398  // corresponding alternative string value from the enum registry
   399  func (tr *EnumRegistry) EnumIfaceToAltString(eval any) string {
   400  	if reflect.TypeOf(eval).Kind() == reflect.Ptr {
   401  		eval = reflect.ValueOf(eval).Elem() // deref the pointer
   402  	}
   403  	et := reflect.TypeOf(eval)
   404  	tn := ShortTypeName(et)
   405  	alts := tr.AltStrings(tn)
   406  	if alts == nil {
   407  		log.Printf("kit.EnumToAltString: no alternative string map for type %v\n", tn)
   408  		return ""
   409  	}
   410  	// convert to int64 for lookup
   411  	ival := EnumIfaceToInt64(eval)
   412  	return alts[ival]
   413  }
   414  
   415  // EnumInt64ToAltString converts an int64 value to the enum of given type, and
   416  // then into corresponding alternative string value
   417  func (tr *EnumRegistry) EnumInt64ToAltString(ival int64, typnm string) string {
   418  	alts := tr.AltStrings(typnm)
   419  	if alts == nil {
   420  		log.Printf("kit.EnumInt64ToAltString: no alternative string map for type %v\n", typnm)
   421  		return ""
   422  	}
   423  	return alts[ival]
   424  }
   425  
   426  // SetEnumValueFromString sets enum value from string using reflect.Value
   427  // IMPORTANT: requires the modified stringer go generate utility
   428  // that generates a FromString method
   429  func SetEnumValueFromString(eval reflect.Value, str string) error {
   430  	etp := eval.Type()
   431  	if etp.Kind() != reflect.Ptr {
   432  		err := fmt.Errorf("kit.SetEnumValueFromString -- you must pass a pointer enum, not type: %v kind %v\n", etp, etp.Kind())
   433  		// log.Printf("%v", err)
   434  		return err
   435  	}
   436  	et := etp.Elem()
   437  	methnm := "FromString"
   438  	meth := eval.MethodByName(methnm)
   439  	if ValueIsZero(meth) || meth.IsNil() {
   440  		err := fmt.Errorf("kit.SetEnumValueFromString: stringer-generated FromString() method not found: %v for type: %v %T\n", methnm, et.Name(), eval.Interface())
   441  		log.Printf("%v", err)
   442  		return err
   443  	}
   444  	sv := reflect.ValueOf(str)
   445  	args := make([]reflect.Value, 1)
   446  	args[0] = sv
   447  	meth.Call(args)
   448  	// fmt.Printf("return from FromString method: %v\n", rv[0].Interface())
   449  	return nil
   450  }
   451  
   452  // SetEnumIfaceFromString sets enum value from string -- must pass a *pointer*
   453  // to the enum item. IMPORTANT: requires the modified stringer go generate
   454  // utility that generates a FromString method
   455  func SetEnumIfaceFromString(eptr any, str string) error {
   456  	return SetEnumValueFromString(reflect.ValueOf(eptr), str)
   457  }
   458  
   459  // SetEnumValueFromAltString sets value from alternative string using a
   460  // reflect.Value -- must pass a *pointer* value to the enum item.
   461  func (tr *EnumRegistry) SetEnumValueFromAltString(eval reflect.Value, str string) error {
   462  	etp := eval.Type()
   463  	if etp.Kind() != reflect.Ptr {
   464  		err := fmt.Errorf("kit.SetEnumValueFromString -- you must pass a pointer enum, not type: %v kind %v\n", etp, etp.Kind())
   465  		log.Printf("%v", err)
   466  		return err
   467  	}
   468  	et := etp.Elem()
   469  	tn := ShortTypeName(et)
   470  	alts := tr.AltStrings(tn)
   471  	if alts == nil {
   472  		err := fmt.Errorf("kit.SetEnumValueFromAltString: no alternative string map for type %v\n", tn)
   473  		// log.Printf("%v", err)
   474  		return err
   475  	}
   476  	for i, v := range alts {
   477  		if v == str {
   478  			return SetEnumValueFromInt64(eval, int64(i))
   479  		}
   480  	}
   481  	err := fmt.Errorf("kit.SetEnumValueFromAltString: string: %v not found in alt list of strings for type%v\n", str, tn)
   482  	// log.Printf("%v", err)
   483  	return err
   484  }
   485  
   486  // SetEnumIfaceFromAltString sets from alternative string list using an interface{}
   487  // to the enum -- must pass a *pointer* to the enum item.
   488  func (tr *EnumRegistry) SetEnumIfaceFromAltString(eptr any, str string) error {
   489  	return tr.SetEnumValueFromAltString(reflect.ValueOf(eptr), str)
   490  }
   491  
   492  // SetEnumValueFromStringAltFirst first attempts to set an enum from an
   493  // alternative string, and if that fails, then it tries to set from the
   494  // regular string representation func (tr *EnumRegistry)
   495  func (tr *EnumRegistry) SetEnumValueFromStringAltFirst(eval reflect.Value, str string) error {
   496  	err := tr.SetEnumValueFromAltString(eval, str)
   497  	if err != nil {
   498  		return SetEnumValueFromString(eval, str)
   499  	}
   500  	return err
   501  }
   502  
   503  // SetEnumIfaceFromStringAltFirst first attempts to set an enum from an
   504  // alternative string, and if that fails, then it tries to set from the
   505  // regular string representation func (tr *EnumRegistry)
   506  func (tr *EnumRegistry) SetEnumIfaceFromStringAltFirst(eptr any, str string) error {
   507  	err := tr.SetEnumIfaceFromAltString(eptr, str)
   508  	if err != nil {
   509  		return SetEnumIfaceFromString(eptr, str)
   510  	}
   511  	return err
   512  }
   513  
   514  ///////////////////////////////////////////////////////////////////////////////
   515  //  BitFlags
   516  
   517  // BitFlagsToString converts an int64 of bit flags into a string
   518  // representation of the bits that are set -- en is the number of defined
   519  // bits, and also provides the type name for looking up strings
   520  func BitFlagsToString(bflg int64, en any) string {
   521  	et := PtrType(reflect.TypeOf(en)).Elem()
   522  	n := int(EnumIfaceToInt64(en))
   523  	str := ""
   524  	for i := 0; i < n; i++ {
   525  		if bitflag.Has(bflg, i) {
   526  			evs := EnumInt64ToString(int64(i), et)
   527  			if str == "" {
   528  				str = evs
   529  			} else {
   530  				str += "|" + evs
   531  			}
   532  		}
   533  	}
   534  	return str
   535  }
   536  
   537  // BitFlagsFromString sets an int64 of bit flags from a string representation
   538  // of the bits that are set -- en is the number of defined bits, and also
   539  // provides the type name for looking up strings
   540  func BitFlagsFromString(bflg *int64, str string, en any) error {
   541  	et := PtrType(reflect.TypeOf(en)).Elem()
   542  	n := int(EnumIfaceToInt64(en))
   543  	return BitFlagsTypeFromString(bflg, str, et, n)
   544  }
   545  
   546  // BitFlagsTypeFromString sets an int64 of bit flags from a string representation
   547  // of the bits that are set -- gets enum type and n of defined elements directly
   548  func BitFlagsTypeFromString(bflg *int64, str string, et reflect.Type, n int) error {
   549  	flgs := strings.Split(str, "|")
   550  	evv := reflect.New(et)
   551  	var err error
   552  	for _, flg := range flgs {
   553  		err = SetEnumValueFromString(evv, flg)
   554  		if err == nil {
   555  			evi := EnumIfaceToInt64(evv.Interface())
   556  			bitflag.Set(bflg, int(evi))
   557  		}
   558  	}
   559  	return err
   560  }
   561  
   562  // BitFlagsFromStringAltFirst sets an int64 of bit flags from a string
   563  // representation of the bits that are set, using alt-strings first -- gets
   564  // enum type and n of defined elements directly
   565  func (tr *EnumRegistry) BitFlagsFromStringAltFirst(bflg *int64, str string, et reflect.Type, n int) error {
   566  	flgs := strings.Split(str, "|")
   567  	evv := reflect.New(et)
   568  	var err error
   569  	for _, flg := range flgs {
   570  		err = tr.SetEnumValueFromStringAltFirst(evv, flg)
   571  		if err == nil {
   572  			evi := EnumIfaceToInt64(evv.Interface())
   573  			bitflag.Set(bflg, int(evi))
   574  		}
   575  	}
   576  	return err
   577  }
   578  
   579  // SetAnyEnumValueFromString looks up enum type on registry, and if it is
   580  // registered as a bitflag, sets bits from string, otherwise tries to set from
   581  // alt strings if those exist, and finally tries direct set from string --
   582  // must pass a *pointer* value to the enum item.
   583  func (tr *EnumRegistry) SetAnyEnumValueFromString(eval reflect.Value, str string) error {
   584  	etp := eval.Type()
   585  	if etp.Kind() != reflect.Ptr {
   586  		err := fmt.Errorf("kit.SetAnyEnumValueFromString -- you must pass a pointer enum, not type: %v kind %v\n", etp, etp.Kind())
   587  		log.Printf("%v", err)
   588  		return err
   589  	}
   590  	et := etp.Elem()
   591  	if tr.IsBitFlag(et) {
   592  		var bf int64
   593  		err := tr.BitFlagsFromStringAltFirst(&bf, str, et, int(tr.NVals(eval.Elem().Interface())))
   594  		if err != nil {
   595  			return err
   596  		}
   597  		return SetEnumValueFromInt64(eval, bf)
   598  	}
   599  	return tr.SetEnumValueFromStringAltFirst(eval, str)
   600  }
   601  
   602  // SetAnyEnumIfaceFromString looks up enum type on registry, and if it is
   603  // registered as a bitflag, sets bits from string, otherwise tries to set from
   604  // alt strings if those exist, and finally tries direct set from string --
   605  // must pass a *pointer* value to the enum item.
   606  func (tr *EnumRegistry) SetAnyEnumIfaceFromString(eptr any, str string) error {
   607  	return tr.SetAnyEnumValueFromString(reflect.ValueOf(eptr), str)
   608  }
   609  
   610  // Describer interface provides a description
   611  // for an item, typically to be displayed
   612  // on hover as a tooltip in a GUI
   613  type Describer interface {
   614  	// Desc returns a description for an item
   615  	Desc() string
   616  }
   617  
   618  ///////////////////////////////////////////////////////////////////////////////
   619  //  EnumValue
   620  
   621  // EnumValue represents enum values, in common int64 terms, e.g., for GUI
   622  type EnumValue struct {
   623  
   624  	// name for this value
   625  	Name string `desc:"name for this value"`
   626  
   627  	// integer value
   628  	Value int64 `desc:"integer value"`
   629  
   630  	// the enum type that this value belongs to
   631  	Type reflect.Type `desc:"the enum type that this value belongs to"`
   632  
   633  	// the comment description of the enum value
   634  	Desc string `desc:"the comment description of the enum value"`
   635  }
   636  
   637  // Set sets the values of the EnumValue struct
   638  func (ev *EnumValue) Set(name string, val int64, typ reflect.Type, desc string) {
   639  	ev.Name = name
   640  	ev.Value = val
   641  	ev.Type = typ
   642  	ev.Desc = desc
   643  }
   644  
   645  // String satisfies fmt.Stringer and provides a string representation of enum: just the name
   646  func (ev EnumValue) String() string {
   647  	return ev.Name
   648  }
   649  
   650  // Values returns an EnumValue slice for all the values of an enum type -- if
   651  // alt is true and alt names exist, then those are used
   652  func (tr *EnumRegistry) Values(enumName string, alt bool) []EnumValue {
   653  	et, ok := tr.Enums[enumName]
   654  	if !ok {
   655  		return nil
   656  	}
   657  	vals, ok := tr.Vals[enumName]
   658  	if ok {
   659  		return vals
   660  	}
   661  	alts := tr.AltStrings(enumName)
   662  	pt := tr.ParType(enumName)
   663  	n := tr.Prop(enumName, "N").(int64)
   664  	vals = make([]EnumValue, n)
   665  	st := 0
   666  	if pt != nil {
   667  		ptv := tr.TypeValues(pt, alt)
   668  		copy(vals, ptv)
   669  		st = len(ptv)
   670  	}
   671  	for i := int64(st); i < n; i++ {
   672  		str := EnumInt64ToString(i, et) // todo: what happens when no string for given values?
   673  		if alt && alts != nil {
   674  			str = alts[i]
   675  		}
   676  		desc := ""
   677  		if d, ok := reflect.ValueOf(i).Convert(et).Interface().(Describer); ok {
   678  			desc = d.Desc()
   679  		}
   680  		vals[i].Set(str, i, et, desc)
   681  	}
   682  	tr.Vals[enumName] = vals
   683  	return vals
   684  }
   685  
   686  // TypeValues returns an EnumValue slice for all the values of an enum type --
   687  // if alt is true and alt names exist, then those are used
   688  func (tr *EnumRegistry) TypeValues(et reflect.Type, alt bool) []EnumValue {
   689  	return tr.Values(ShortTypeName(et), alt)
   690  }
   691  
   692  // AllTagged returns a list of all registered enum types that include a given
   693  // property key value -- does not check for the value of that value -- just
   694  // its existence
   695  func (tr *EnumRegistry) AllTagged(key string) []reflect.Type {
   696  	tl := make([]reflect.Type, 0)
   697  	for _, typ := range tr.Enums {
   698  		tp := tr.Prop(ShortTypeName(typ), key)
   699  		if tp == nil {
   700  			continue
   701  		}
   702  		tl = append(tl, typ)
   703  	}
   704  	return tl
   705  }
   706  
   707  ///////////////////////////////////////////////////////////////////////////////
   708  //  JSON, Text Marshal
   709  
   710  func EnumMarshalJSON(eval any) ([]byte, error) {
   711  	et := reflect.TypeOf(eval)
   712  	b := make([]byte, 0, 50)
   713  	b = append(b, []byte("\"")...)
   714  	if Enums.IsBitFlag(et) {
   715  		b = append(b, []byte(BitFlagsToString(EnumIfaceToInt64(eval), eval))...)
   716  	} else {
   717  		b = append(b, []byte(EnumIfaceToString(eval))...)
   718  	}
   719  	b = append(b, []byte("\"")...)
   720  	return b, nil
   721  }
   722  
   723  func EnumUnmarshalJSON(eval any, b []byte) error {
   724  	et := reflect.TypeOf(eval)
   725  	noq := string(bytes.Trim(b, "\""))
   726  	if Enums.IsBitFlag(et) {
   727  		bf := int64(0)
   728  		err := BitFlagsTypeFromString(&bf, noq, et, int(Enums.NVals(eval)))
   729  		if err == nil {
   730  			return SetEnumIfaceFromInt64(eval, bf, et)
   731  		}
   732  		return err
   733  	}
   734  	return SetEnumIfaceFromString(eval, noq)
   735  }
   736  
   737  func EnumMarshalText(eval any) ([]byte, error) {
   738  	et := reflect.TypeOf(eval)
   739  	b := make([]byte, 0, 50)
   740  	if Enums.IsBitFlag(et) {
   741  		b = append(b, []byte(BitFlagsToString(EnumIfaceToInt64(eval), eval))...)
   742  	} else {
   743  		b = append(b, []byte(EnumIfaceToString(eval))...)
   744  	}
   745  	return b, nil
   746  }
   747  
   748  func EnumUnmarshalText(eval any, b []byte) error {
   749  	et := reflect.TypeOf(eval)
   750  	noq := string(b)
   751  	if Enums.IsBitFlag(et) {
   752  		bf := int64(0)
   753  		err := BitFlagsTypeFromString(&bf, noq, et, int(Enums.NVals(eval)))
   754  		if err == nil {
   755  			return SetEnumIfaceFromInt64(eval, bf, et)
   756  		}
   757  		return err
   758  	}
   759  	return SetEnumIfaceFromString(eval, noq)
   760  }
   761  
   762  /////////////////////////////////////////////////////////////
   763  // Following is for testing..
   764  
   765  // TestFlags are for testing -- need the generated string code, so putting in here
   766  type TestFlags int32
   767  
   768  const (
   769  	TestFlagsNil TestFlags = iota
   770  	TestFlag1
   771  	TestFlag2
   772  	TestFlagsN
   773  )
   774  
   775  //go:generate stringer -type=TestFlags
   776  
   777  var KiT_TestFlags = Enums.AddEnumAltLower(TestFlagsN, NotBitFlag, nil, "Test")
   778  
   779  func (ev TestFlags) MarshalJSON() ([]byte, error)  { return EnumMarshalJSON(ev) }
   780  func (ev *TestFlags) UnmarshalJSON(b []byte) error { return EnumUnmarshalJSON(ev, b) }