github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/starkit/set_state.go (about) 1 package starkit 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "go.starlark.net/starlark" 8 ) 9 10 // SetState works like SetState in React. It can take a value or a function. 11 // 12 // That function should transform old state into new state. It can have the signature `func(T) T` or `func(T) (T, error)`. 13 // 14 // For example, an plugin that accumulated strings might use 15 // SetState() like this: 16 // 17 // err := starkit.SetState(t, func(strings []string) { 18 // return append([]string{newString}, strings...) 19 // }) 20 // 21 // This would be so much easier with generics :grimace: 22 // 23 // SetState will return an error if it can't match the type 24 // of anything in the state store. 25 func SetState(t *starlark.Thread, valOrFn interface{}) error { 26 typ := reflect.TypeOf(valOrFn) 27 model, ok := t.Local(modelKey).(Model) 28 if !ok { 29 return fmt.Errorf("Internal error: Starlark not initialized correctly: starkit.Model not found") 30 } 31 32 if typ.Kind() != reflect.Func { 33 return setStateVal(model, typ, valOrFn) 34 } else { 35 return setStateFn(model, typ, valOrFn) 36 } 37 } 38 39 func setStateVal(model Model, typ reflect.Type, val interface{}) error { 40 // If there's already a value with this type in the state store, overwrite it. 41 _, ok := model.state[typ] 42 if !ok { 43 return fmt.Errorf("Internal error: Type not found in state store: %T", val) 44 } 45 model.state[typ] = val 46 return nil 47 } 48 49 func setStateFn(model Model, typ reflect.Type, fn interface{}) error { 50 // Validate the function signature. 51 if typ.NumIn() != 1 || 52 typ.NumOut() < 1 || typ.NumOut() > 2 || 53 typ.In(0) != typ.Out(0) || 54 (typ.NumOut() == 2 && typ.Out(1) != reflect.TypeOf((*error)(nil)).Elem()) { 55 return fmt.Errorf("Internal error: invalid SetState call: signature must be `func(T) T` or `func(t) (T, error)`") 56 } 57 58 inTyp := typ.In(0) 59 // Overwrite the value in the state store. 60 existing, ok := model.state[inTyp] 61 if !ok { 62 return fmt.Errorf("Internal error: Type not found in state store: %s", inTyp) 63 } 64 65 outs := reflect.ValueOf(fn).Call([]reflect.Value{reflect.ValueOf(existing)}) 66 67 if typ.NumOut() == 2 && !outs[1].IsNil() { 68 return outs[1].Interface().(error) 69 } 70 71 // We know this is valid because of the type validation check above. 72 model.state[inTyp] = outs[0].Interface() 73 74 return nil 75 }