-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselect.go
More file actions
166 lines (145 loc) · 4.71 KB
/
Copy pathselect.go
File metadata and controls
166 lines (145 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package huhx
import (
"fmt"
"charm.land/huh/v2"
)
// Select wraps *huh.Select[T] for headless drive. Options provided via
// Options(...) are captured at construction time; options provided via
// OptionsFunc(...) are re-evaluated lazily inside set() so closures that
// depend on earlier fields' values resolve correctly.
type Select[T comparable] struct {
inner *huh.Select[T]
k string
value *T
accessor huh.Accessor[T]
validate func(T) error
optional bool
options []huh.Option[T]
optionsFunc func() []huh.Option[T]
}
// NewSelect returns a new Select wrapping huh.NewSelect[T]().
func NewSelect[T comparable]() *Select[T] {
return &Select[T]{inner: huh.NewSelect[T]()}
}
// Key sets the field key used for answer lookup.
func (s *Select[T]) Key(k string) *Select[T] {
s.k = k
s.inner.Key(k)
return s
}
// Title sets the field title.
func (s *Select[T]) Title(t string) *Select[T] {
s.inner.Title(t)
return s
}
// Description sets the field description.
func (s *Select[T]) Description(d string) *Select[T] {
s.inner.Description(d)
return s
}
// Options sets the available options statically. The wrapper retains a
// copy so the non-interactive runner can match answers without going
// through huh internals. Calling Options clears any previously set
// OptionsFunc.
func (s *Select[T]) Options(opts ...huh.Option[T]) *Select[T] {
s.options = opts
s.optionsFunc = nil
s.inner.Options(opts...)
return s
}
// OptionsFunc sets a dynamic options provider. The provider is forwarded
// to huh for interactive mode and is re-evaluated by the non-interactive
// runner at injection time so closures that depend on earlier fields'
// values resolve correctly. The dependent field must live in a later
// group than its source field (same rule as huh's interactive bindings).
// Calling OptionsFunc clears any previously set static Options.
func (s *Select[T]) OptionsFunc(f func() []huh.Option[T], bindings any) *Select[T] {
s.optionsFunc = f
s.options = nil
s.inner.OptionsFunc(f, bindings)
return s
}
// Value binds a destination pointer.
func (s *Select[T]) Value(v *T) *Select[T] {
s.value = v
s.accessor = huh.NewPointerAccessor(v)
s.inner.Value(v)
return s
}
// Accessor binds a custom accessor for reading/writing the field value.
func (s *Select[T]) Accessor(a huh.Accessor[T]) *Select[T] {
s.accessor = a
s.inner.Accessor(a)
return s
}
// TitleFunc sets a dynamic title with bindings.
func (s *Select[T]) TitleFunc(f func() string, bindings any) *Select[T] {
s.inner.TitleFunc(f, bindings)
return s
}
// DescriptionFunc sets a dynamic description with bindings.
func (s *Select[T]) DescriptionFunc(f func() string, bindings any) *Select[T] {
s.inner.DescriptionFunc(f, bindings)
return s
}
// Filtering toggles filter input for options.
func (s *Select[T]) Filtering(filtering bool) *Select[T] {
s.inner.Filtering(filtering)
return s
}
// Inline sets inline rendering.
func (s *Select[T]) Inline(inline bool) *Select[T] {
s.inner.Inline(inline)
return s
}
// Height sets the visible option list height.
func (s *Select[T]) Height(height int) *Select[T] {
s.inner.Height(height)
return s
}
// Validate sets the validator on both the wrapper and the inner huh field.
func (s *Select[T]) Validate(fn func(T) error) *Select[T] {
s.validate = fn
s.inner.Validate(fn)
return s
}
// Optional marks the field as not required in non-interactive mode.
func (s *Select[T]) Optional() *Select[T] {
s.optional = true
return s
}
func (s *Select[T]) key() string { return s.k }
func (s *Select[T]) huhField() huh.Field { return s.inner }
func (s *Select[T]) required() bool { return !s.optional }
// currentOptions returns the option slice to match against. If
// OptionsFunc was set the closure is invoked now so it can read any
// earlier-field values that have already been written by the runner.
func (s *Select[T]) currentOptions() []huh.Option[T] {
if s.optionsFunc != nil {
return s.optionsFunc()
}
return s.options
}
// set resolves the answer string against the field's options. An option
// matches if its Key equals the answer or if fmt.Sprintf("%v", o.Value)
// equals the answer. The %v fallback works cleanly for primitive T
// (string, int, etc.) but may be ambiguous for struct types or types
// with a custom String() method — set Key explicitly for those.
func (s *Select[T]) set(value string) error {
for _, o := range s.currentOptions() {
if o.Key == value || fmt.Sprintf("%v", o.Value) == value {
if s.accessor != nil {
s.accessor.Set(o.Value)
} else if s.value != nil {
*s.value = o.Value
}
if s.validate != nil {
if err := s.validate(o.Value); err != nil {
return err
}
}
return nil
}
}
return fmt.Errorf("%q is not a valid option", value)
}