package input import ( "fmt" "strconv" "golang.org/x/exp/slices" ) // ListBinder describes an interface that simple lists can implement to be compatible with list based UI components. type ListBinder interface { ListKeyValuePairs() []ListKeyValuePair ListDeleteKey(key string) bool // Removes the entry with the given key, and returns true if it got deleted successfully. ListAddKeyValuePair(key, value string) error // Adds the key value pair to the list. ListLen() int // Returns the length of the list. } type ListKeyValuePair struct { Key, Value string } // ListBindGenericSlice implements ListBinder for slices that are of some arbitrary type. // For this to work, you need to supply a function that returns key value pairs for every slice entry. type ListBindGenericSlice[T any] struct { Slice *[]T } func (l ListBindGenericSlice[T]) ListKeyValuePairs() []ListKeyValuePair { if l.Slice == nil { return nil } result := make([]ListKeyValuePair, 0, len(*l.Slice)) for i, entry := range *l.Slice { var str string switch v := any(entry).(type) { case string: str = v case int: str = strconv.FormatInt(int64(v), 10) case int8: str = strconv.FormatInt(int64(v), 10) case int16: str = strconv.FormatInt(int64(v), 10) case int32: str = strconv.FormatInt(int64(v), 10) case int64: str = strconv.FormatInt(int64(v), 10) case uint: str = strconv.FormatUint(uint64(v), 10) case uint8: str = strconv.FormatUint(uint64(v), 10) case uint16: str = strconv.FormatUint(uint64(v), 10) case uint32: str = strconv.FormatUint(uint64(v), 10) case uint64: str = strconv.FormatUint(uint64(v), 10) case float32: str = strconv.FormatFloat(float64(v), 'f', -1, 32) // TODO: Format number in current user's locale case float64: str = strconv.FormatFloat(float64(v), 'f', -1, 64) } result = append(result, ListKeyValuePair{Key: strconv.Itoa(i), Value: str}) } return result } func (l ListBindGenericSlice[T]) ListDeleteKey(key string) bool { if l.Slice == nil { return false } index, err := strconv.Atoi(key) if err != nil { return false } if index < 0 || index >= len(*l.Slice) { return false } *l.Slice = slices.Delete(*l.Slice, index, index+1) return true } func (l ListBindGenericSlice[T]) ListAddKeyValuePair(key, value string) error { var val T switch v := any(&val).(type) { case *string: *v = value case *int: val, err := strconv.ParseInt(value, 10, 0) if err != nil { return err } *v = int(val) case *int8: val, err := strconv.ParseInt(value, 10, 8) if err != nil { return err } *v = int8(val) case *int16: val, err := strconv.ParseInt(value, 10, 16) if err != nil { return err } *v = int16(val) case *int32: val, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } *v = int32(val) case *int64: val, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } *v = int64(val) case *uint: val, err := strconv.ParseUint(value, 10, 0) if err != nil { return err } *v = uint(val) case *uint8: val, err := strconv.ParseUint(value, 10, 8) if err != nil { return err } *v = uint8(val) case *uint16: val, err := strconv.ParseUint(value, 10, 16) if err != nil { return err } *v = uint16(val) case *uint32: val, err := strconv.ParseUint(value, 10, 32) if err != nil { return err } *v = uint32(val) case *uint64: val, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } *v = uint64(val) case *float32: val, err := strconv.ParseFloat(value, 32) if err != nil { return err } *v = float32(val) case *float64: val, err := strconv.ParseFloat(value, 64) if err != nil { return err } *v = float64(val) default: return fmt.Errorf("bound type %T is not supported", v) } index, err := strconv.Atoi(key) if err != nil { return fmt.Errorf("failed to parse index: %w", err) } if index < 0 || index > len(*l.Slice) { return fmt.Errorf("index %d out of bounds", index) } *l.Slice = slices.Insert(*l.Slice, index, val) return nil } func (l ListBindGenericSlice[T]) ListLen() int { return len(*l.Slice) }