···379379// findRequestedCommand uses the raw arguments and the command tree to determine what
380380// (if any) subcommand is being requested and return that command along with the arguments
381381// that were meant for it.
382382-func findRequestedCommand(cmd *Command, args []string) (*Command, []string) {
383383- // The next non-flag argument (if any) is the first immediate subcommand
384384- // e.g. in 'go mod tidy' we're looking for 'mod'.
385385- nextSubCommand, ok := firstNonFlagArg(cmd, args)
386386- if !ok {
387387- // No non-flag arguments, so we must already be either at the root command
388388- // or the correct subcommand
389389- return cmd, args
390390- }
391391-392392- // Lookup this immediate subcommand by name and if we find it, recursively call
393393- // this function so we eventually end up at the end of the command tree with
394394- // the right arguments
395395- next := findSubCommand(cmd, nextSubCommand)
396396- if next != nil {
397397- return findRequestedCommand(next, argsMinusFirstX(args, nextSubCommand))
398398- }
399399-400400- // Found it
401401- return cmd, args
402402-}
403403-404404-// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like
405405-// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]).
406382//
407407-// The input slice is not mutated so that repeated Execute calls on the same
408408-// Command see the original rawArgs.
409409-func argsMinusFirstX(args []string, x string) []string {
410410- // Note: this is borrowed from Cobra but ours is a lot simpler because we don't support
411411- // persistent flags
412412- for i, arg := range args {
413413- if arg == x {
414414- result := make([]string, 0, len(args)-1)
415415- result = append(result, args[:i]...)
416416- result = append(result, args[i+1:]...)
383383+// On the first descent into a subcommand it snapshots args into a working
384384+// slice that we own; subsequent levels then mutate that slice in place via
385385+// [slices.Delete]. The original cmd.rawArgs is never touched, so re-Execute
386386+// on the same Command still sees pristine input.
387387+func findRequestedCommand(cmd *Command, args []string) (*Command, []string) {
388388+ owned := false
417389418418- return result
390390+ for {
391391+ // The next non-flag argument (if any) is the immediate subcommand
392392+ // e.g. in 'go mod tidy' we're looking for 'mod'.
393393+ idx, ok := firstNonFlagArg(cmd, args)
394394+ if !ok {
395395+ return cmd, args
419396 }
420420- }
421397422422- return args
398398+ next := findSubCommand(cmd, args[idx])
399399+ if next == nil {
400400+ return cmd, args
401401+ }
402402+403403+ if !owned {
404404+ working := make([]string, len(args))
405405+ copy(working, args)
406406+ args = working
407407+ owned = true
408408+ }
409409+410410+ args = slices.Delete(args, idx, idx+1)
411411+ cmd = next
412412+ }
423413}
424414425415// findSubCommand searches the immediate subcommands of cmd by name, looking for next.
···435425 return nil
436426}
437427438438-// firstNonFlagArg walks args and returns the first positional (non-flag)
439439-// argument along with a boolean indicating whether one was found.
428428+// firstNonFlagArg walks args and returns the index of the first positional
429429+// (non-flag) argument along with a boolean indicating whether one was found.
440430//
441431// It consumes flag-value pairs (e.g. '--flag value' or '-f value') so they
442432// aren't mistaken for positional arguments, and stops at '--'.
443443-func firstNonFlagArg(cmd *Command, args []string) (arg string, ok bool) {
433433+func firstNonFlagArg(cmd *Command, args []string) (idx int, ok bool) {
444434 for i := 0; i < len(args); i++ {
445435 a := args[i]
446436 switch {
447437 case a == "--":
448438 // "--" terminates the flags
449449- return "", false
439439+ return -1, false
450440 case strings.HasPrefix(a, "--") && !strings.Contains(a, "=") && !cmd.hasFlag(a[2:]):
451441 // If '--flag value' then skip value
452442 fallthrough
453443 case strings.HasPrefix(a, "-") && !strings.Contains(a, "=") && len(a) == 2 && !cmd.hasShortFlag(a[1:]):
454444 // '-f value' skip the value too. If there isn't one, we're done.
455445 if i+1 >= len(args) {
456456- return "", false
446446+ return -1, false
457447 }
458448459449 i++
···461451 continue
462452 case a != "" && !strings.HasPrefix(a, "-"):
463453 // First valid positional arg
464464- return a, true
454454+ return i, true
465455 }
466456 }
467457468468- return "", false
458458+ return -1, false
469459}
470460471461// showHelp is the default for a command's helpFunc.
+168-195
internal/arg/arg.go
···16161717 "go.followtheprocess.codes/cli/arg"
1818 "go.followtheprocess.codes/cli/internal/format"
1919+ "go.followtheprocess.codes/cli/internal/kind"
1920 "go.followtheprocess.codes/cli/internal/parse"
2021)
2122···23242425// Arg represents a single command line argument.
2526type Arg[T arg.Argable] struct {
2626- value *T // The actual stored value
2727- config Config[T] // Additional configuration
2828- name string // Name of the argument as it appears on the command line
2929- usage string // One line description of the argument.
2727+ value *T // The actual stored value
2828+ config Config[T] // Additional configuration
2929+ name string // Name of the argument as it appears on the command line
3030+ usage string // One line description of the argument.
3131+ typeStr string // Cached result of Type()
3232+ kind kind.Kind // Cached concrete kind of T, set in New so hot paths skip any() boxing
3033}
31343235// New constructs and returns a new [Arg].
···3942 p = new(T)
4043 }
41444545+ k, typeStr := typeInfo[T]()
4646+4247 argument := Arg[T]{
4343- value: p,
4444- name: name,
4545- usage: usage,
4646- config: config,
4848+ value: p,
4949+ name: name,
5050+ usage: usage,
5151+ config: config,
5252+ typeStr: typeStr,
5353+ kind: k,
4754 }
48554956 return argument, nil
···61686269// Default returns the default value of the argument as a string
6370// or "" if the argument is required.
6464-//
6565-//nolint:cyclop // No other way of doing this
6671func (a Arg[T]) Default() string {
6772 if a.config.DefaultValue == nil {
6873 // DefaultValue is nil, therefore this is a required arg
6974 return ""
7075 }
71767272- switch typ := any(*a.config.DefaultValue).(type) {
7373- case int:
7474- return format.Int(typ)
7575- case int8:
7676- return format.Int(typ)
7777- case int16:
7878- return format.Int(typ)
7979- case int32:
8080- return format.Int(typ)
8181- case int64:
8282- return format.Int(typ)
8383- case uint:
8484- return format.Uint(typ)
8585- case uint8:
8686- return format.Uint(typ)
8787- case uint16:
8888- return format.Uint(typ)
8989- case uint32:
9090- return format.Uint(typ)
9191- case uint64:
9292- return format.Uint(typ)
9393- case uintptr:
9494- return format.Uint(typ)
9595- case float32:
9696- return format.Float32(typ)
9797- case float64:
9898- return format.Float64(typ)
9999- case string:
100100- return typ
101101- case *url.URL:
102102- return typ.String()
103103- case bool:
104104- return strconv.FormatBool(typ)
105105- case []byte:
106106- return hex.EncodeToString(typ)
107107- case time.Time:
108108- return typ.Format(time.RFC3339)
109109- case time.Duration:
110110- return typ.String()
111111- case net.IP:
112112- return typ.String()
113113- default:
114114- return fmt.Sprintf("Arg.String: unsupported arg type: %T", typ)
115115- }
7777+ return formatValue(a.kind, a.config.DefaultValue)
11678}
1177911880// String returns the string representation of the current value of the arg.
119119-//
120120-//nolint:cyclop // No other way of doing this realistically
12181func (a Arg[T]) String() string {
12282 if a.value == nil {
12383 return format.Nil
12484 }
12585126126- switch typ := any(*a.value).(type) {
127127- case int:
128128- return format.Int(typ)
129129- case int8:
130130- return format.Int(typ)
131131- case int16:
132132- return format.Int(typ)
133133- case int32:
134134- return format.Int(typ)
135135- case int64:
136136- return format.Int(typ)
137137- case uint:
138138- return format.Uint(typ)
139139- case uint8:
140140- return format.Uint(typ)
141141- case uint16:
142142- return format.Uint(typ)
143143- case uint32:
144144- return format.Uint(typ)
145145- case uint64:
146146- return format.Uint(typ)
147147- case uintptr:
148148- return format.Uint(typ)
149149- case float32:
150150- return format.Float32(typ)
151151- case float64:
152152- return format.Float64(typ)
153153- case string:
154154- return typ
155155- case *url.URL:
156156- return typ.String()
157157- case bool:
158158- return strconv.FormatBool(typ)
159159- case []byte:
160160- return hex.EncodeToString(typ)
161161- case time.Time:
162162- return typ.Format(time.RFC3339)
163163- case time.Duration:
164164- return typ.String()
165165- case net.IP:
166166- return typ.String()
167167- default:
168168- return fmt.Sprintf("Arg.String: unsupported arg type: %T", typ)
169169- }
8686+ return formatValue(a.kind, a.value)
17087}
1718817289// Type returns a string representation of the type of the Arg.
173173-//
174174-//nolint:cyclop // No other way of doing this realistically
17590func (a Arg[T]) Type() string {
17691 if a.value == nil {
17792 return format.Nil
17893 }
17994180180- switch typ := any(*a.value).(type) {
181181- case int:
182182- return format.TypeInt
183183- case int8:
184184- return format.TypeInt8
185185- case int16:
186186- return format.TypeInt16
187187- case int32:
188188- return format.TypeInt32
189189- case int64:
190190- return format.TypeInt64
191191- case uint:
192192- return format.TypeUint
193193- case uint8:
194194- return format.TypeUint8
195195- case uint16:
196196- return format.TypeUint16
197197- case uint32:
198198- return format.TypeUint32
199199- case uint64:
200200- return format.TypeUint64
201201- case uintptr:
202202- return format.TypeUintptr
203203- case float32:
204204- return format.TypeFloat32
205205- case float64:
206206- return format.TypeFloat64
207207- case string:
208208- return format.TypeString
209209- case *url.URL:
210210- return format.TypeURL
211211- case bool:
212212- return format.TypeBool
213213- case []byte:
214214- return format.TypeBytesHex
215215- case time.Time:
216216- return format.TypeTime
217217- case time.Duration:
218218- return format.TypeDuration
219219- case net.IP:
220220- return format.TypeIP
221221- default:
222222- return fmt.Sprintf("%T", typ)
223223- }
9595+ return a.typeStr
22496}
2259722698// Set sets an [Arg] value by parsing it's string value.
22799//
228228-//nolint:gocognit,maintidx // No other way of doing this realistically
100100+//nolint:gocognit,maintidx,cyclop // No other way of doing this realistically
229101func (a Arg[T]) Set(str string) error {
230102 if a.value == nil {
231103 return fmt.Errorf("cannot set value %s, arg.value was nil", str)
232104 }
233105234234- switch typ := any(*a.value).(type) {
235235- case int:
106106+ switch a.kind {
107107+ case kind.Int:
236108 val, err := parse.Int(str)
237109 if err != nil {
238238- return parse.Error(parse.KindArgument, a.name, str, typ, err)
110110+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
239111 }
240112241113 *a.value = *parse.Cast[T](&val)
242114243115 return nil
244244- case int8:
116116+ case kind.Int8:
245117 val, err := parse.Int8(str)
246118 if err != nil {
247247- return parse.Error(parse.KindArgument, a.name, str, typ, err)
119119+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
248120 }
249121250122 *a.value = *parse.Cast[T](&val)
251123252124 return nil
253253- case int16:
125125+ case kind.Int16:
254126 val, err := parse.Int16(str)
255127 if err != nil {
256256- return parse.Error(parse.KindArgument, a.name, str, typ, err)
128128+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
257129 }
258130259131 *a.value = *parse.Cast[T](&val)
260132261133 return nil
262262- case int32:
134134+ case kind.Int32:
263135 val, err := parse.Int32(str)
264136 if err != nil {
265265- return parse.Error(parse.KindArgument, a.name, str, typ, err)
137137+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
266138 }
267139268140 *a.value = *parse.Cast[T](&val)
269141270142 return nil
271271- case int64:
143143+ case kind.Int64:
272144 val, err := parse.Int64(str)
273145 if err != nil {
274274- return parse.Error(parse.KindArgument, a.name, str, typ, err)
146146+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
275147 }
276148277149 *a.value = *parse.Cast[T](&val)
278150279151 return nil
280280- case uint:
152152+ case kind.Uint:
281153 val, err := parse.Uint(str)
282154 if err != nil {
283283- return parse.Error(parse.KindArgument, a.name, str, typ, err)
155155+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
284156 }
285157286158 *a.value = *parse.Cast[T](&val)
287159288160 return nil
289289- case uint8:
161161+ case kind.Uint8:
290162 val, err := parse.Uint8(str)
291163 if err != nil {
292292- return parse.Error(parse.KindArgument, a.name, str, typ, err)
164164+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
293165 }
294166295167 *a.value = *parse.Cast[T](&val)
296168297169 return nil
298298- case uint16:
170170+ case kind.Uint16:
299171 val, err := parse.Uint16(str)
300172 if err != nil {
301301- return parse.Error(parse.KindArgument, a.name, str, typ, err)
173173+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
302174 }
303175304176 *a.value = *parse.Cast[T](&val)
305177306178 return nil
307307- case uint32:
179179+ case kind.Uint32:
308180 val, err := parse.Uint32(str)
309181 if err != nil {
310310- return parse.Error(parse.KindArgument, a.name, str, typ, err)
182182+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
311183 }
312184313185 *a.value = *parse.Cast[T](&val)
314186315187 return nil
316316- case uint64:
188188+ case kind.Uint64, kind.Uintptr:
317189 val, err := parse.Uint64(str)
318190 if err != nil {
319319- return parse.Error(parse.KindArgument, a.name, str, typ, err)
191191+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
320192 }
321193322194 *a.value = *parse.Cast[T](&val)
323195324196 return nil
325325- case uintptr:
326326- val, err := parse.Uint64(str)
327327- if err != nil {
328328- return parse.Error(parse.KindArgument, a.name, str, typ, err)
329329- }
330330-331331- *a.value = *parse.Cast[T](&val)
332332-333333- return nil
334334- case float32:
197197+ case kind.Float32:
335198 val, err := parse.Float32(str)
336199 if err != nil {
337337- return parse.Error(parse.KindArgument, a.name, str, typ, err)
200200+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
338201 }
339202340203 *a.value = *parse.Cast[T](&val)
341204342205 return nil
343343- case float64:
206206+ case kind.Float64:
344207 val, err := parse.Float64(str)
345208 if err != nil {
346346- return parse.Error(parse.KindArgument, a.name, str, typ, err)
209209+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
347210 }
348211349212 *a.value = *parse.Cast[T](&val)
350213351214 return nil
352352- case string:
215215+ case kind.String:
353216 val := str
354217 *a.value = *parse.Cast[T](&val)
355218356219 return nil
357357- case *url.URL:
220220+ case kind.URL:
358221 val, err := url.ParseRequestURI(str)
359222 if err != nil {
360360- return parse.Error(parse.KindArgument, a.name, str, typ, err)
223223+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
361224 }
362225363226 *a.value = *parse.Cast[T](&val)
364227365228 return nil
366366- case bool:
229229+ case kind.Bool:
367230 val, err := strconv.ParseBool(str)
368231 if err != nil {
369369- return parse.Error(parse.KindArgument, a.name, str, typ, err)
232232+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
370233 }
371234372235 *a.value = *parse.Cast[T](&val)
373236374237 return nil
375375- case []byte:
238238+ case kind.BytesHex:
376239 val, err := hex.DecodeString(strings.TrimSpace(str))
377240 if err != nil {
378378- return parse.Error(parse.KindArgument, a.name, str, typ, err)
241241+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
379242 }
380243381244 *a.value = *parse.Cast[T](&val)
382245383246 return nil
384384- case time.Time:
247247+ case kind.Time:
385248 val, err := time.Parse(time.RFC3339, str)
386249 if err != nil {
387387- return parse.Error(parse.KindArgument, a.name, str, typ, err)
250250+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
388251 }
389252390253 *a.value = *parse.Cast[T](&val)
391254392255 return nil
393393- case time.Duration:
256256+ case kind.Duration:
394257 val, err := time.ParseDuration(str)
395258 if err != nil {
396396- return parse.Error(parse.KindArgument, a.name, str, typ, err)
259259+ return parse.Error(parse.KindArgument, a.name, str, *a.value, err)
397260 }
398261399262 *a.value = *parse.Cast[T](&val)
400263401264 return nil
402402- case net.IP:
265265+ case kind.IP:
403266 val := net.ParseIP(str)
404267 if val == nil {
405405- return parse.Error(parse.KindArgument, a.name, str, typ, errors.New("invalid IP address"))
268268+ return parse.Error(parse.KindArgument, a.name, str, *a.value, errors.New("invalid IP address"))
406269 }
407270408271 *a.value = *parse.Cast[T](&val)
409272410273 return nil
411274 default:
412412- return fmt.Errorf("Arg.Set: unsupported arg type: %T", typ)
275275+ return fmt.Errorf("Arg.Set: unsupported arg type: %T", *a.value)
276276+ }
277277+}
278278+279279+// typeInfo computes the type-dependent metadata (kind, type string) for an
280280+// arg of type T. It is called once per arg at construction so that hot paths
281281+// (Set, String, Type) never have to type-switch on any(*a.value), which would
282282+// box the value on every call.
283283+//
284284+//nolint:cyclop // No other way of doing this realistically
285285+func typeInfo[T arg.Argable]() (kind.Kind, string) {
286286+ var zero T
287287+288288+ switch typ := any(zero).(type) {
289289+ case int:
290290+ return kind.Int, format.TypeInt
291291+ case int8:
292292+ return kind.Int8, format.TypeInt8
293293+ case int16:
294294+ return kind.Int16, format.TypeInt16
295295+ case int32:
296296+ return kind.Int32, format.TypeInt32
297297+ case int64:
298298+ return kind.Int64, format.TypeInt64
299299+ case uint:
300300+ return kind.Uint, format.TypeUint
301301+ case uint8:
302302+ return kind.Uint8, format.TypeUint8
303303+ case uint16:
304304+ return kind.Uint16, format.TypeUint16
305305+ case uint32:
306306+ return kind.Uint32, format.TypeUint32
307307+ case uint64:
308308+ return kind.Uint64, format.TypeUint64
309309+ case uintptr:
310310+ return kind.Uintptr, format.TypeUintptr
311311+ case float32:
312312+ return kind.Float32, format.TypeFloat32
313313+ case float64:
314314+ return kind.Float64, format.TypeFloat64
315315+ case string:
316316+ return kind.String, format.TypeString
317317+ case *url.URL:
318318+ return kind.URL, format.TypeURL
319319+ case bool:
320320+ return kind.Bool, format.TypeBool
321321+ case []byte:
322322+ return kind.BytesHex, format.TypeBytesHex
323323+ case time.Time:
324324+ return kind.Time, format.TypeTime
325325+ case time.Duration:
326326+ return kind.Duration, format.TypeDuration
327327+ case net.IP:
328328+ return kind.IP, format.TypeIP
329329+ default:
330330+ return kind.Invalid, fmt.Sprintf("%T", typ)
331331+ }
332332+}
333333+334334+// formatValue renders the value pointed to by p as a string using the kind dispatch.
335335+//
336336+//nolint:cyclop // No other way of doing this realistically
337337+func formatValue[T arg.Argable](k kind.Kind, p *T) string {
338338+ switch k {
339339+ case kind.Int:
340340+ return format.Int(*parse.Cast[int, T](p))
341341+ case kind.Int8:
342342+ return format.Int(*parse.Cast[int8, T](p))
343343+ case kind.Int16:
344344+ return format.Int(*parse.Cast[int16, T](p))
345345+ case kind.Int32:
346346+ return format.Int(*parse.Cast[int32, T](p))
347347+ case kind.Int64:
348348+ return format.Int(*parse.Cast[int64, T](p))
349349+ case kind.Uint:
350350+ return format.Uint(*parse.Cast[uint, T](p))
351351+ case kind.Uint8:
352352+ return format.Uint(*parse.Cast[uint8, T](p))
353353+ case kind.Uint16:
354354+ return format.Uint(*parse.Cast[uint16, T](p))
355355+ case kind.Uint32:
356356+ return format.Uint(*parse.Cast[uint32, T](p))
357357+ case kind.Uint64:
358358+ return format.Uint(*parse.Cast[uint64, T](p))
359359+ case kind.Uintptr:
360360+ return format.Uint(*parse.Cast[uintptr, T](p))
361361+ case kind.Float32:
362362+ return format.Float32(*parse.Cast[float32, T](p))
363363+ case kind.Float64:
364364+ return format.Float64(*parse.Cast[float64, T](p))
365365+ case kind.String:
366366+ return *parse.Cast[string, T](p)
367367+ case kind.URL:
368368+ u := *parse.Cast[*url.URL, T](p)
369369+ if u == nil {
370370+ return format.Nil
371371+ }
372372+373373+ return u.String()
374374+ case kind.Bool:
375375+ return strconv.FormatBool(*parse.Cast[bool, T](p))
376376+ case kind.BytesHex:
377377+ return hex.EncodeToString(*parse.Cast[[]byte, T](p))
378378+ case kind.Time:
379379+ return parse.Cast[time.Time, T](p).Format(time.RFC3339)
380380+ case kind.Duration:
381381+ return parse.Cast[time.Duration, T](p).String()
382382+ case kind.IP:
383383+ return parse.Cast[net.IP, T](p).String()
384384+ default:
385385+ return fmt.Sprintf("Arg.String: unsupported arg type: %T", *p)
413386 }
414387}
415388
+286-260
internal/flag/flag.go
···17171818 "go.followtheprocess.codes/cli/flag"
1919 "go.followtheprocess.codes/cli/internal/format"
2020+ "go.followtheprocess.codes/cli/internal/kind"
2021 "go.followtheprocess.codes/cli/internal/parse"
2122)
2223···24252526// Flag represents a single command line flag.
2627type Flag[T flag.Flaggable] struct {
2727- value *T // The actual stored value
2828- name string // The name of the flag as appears on the command line, e.g. "force" for a --force flag
2929- usage string // one line description of the flag, e.g. "Force deletion without confirmation"
3030- envVar string // Name of an environment variable that may set this flag's value if the flag is not explicitly provided on the command line
3131- typeStr string // Cached result of Type()
3232- noArgValue string // Cached result of NoArgValue()
3333- short rune // Optional shorthand version of the flag, e.g. "f" for a -f flag
3434- isSlice bool // Cached result of IsSlice()
2828+ value *T // The actual stored value
2929+ name string // The name of the flag as appears on the command line, e.g. "force" for a --force flag
3030+ usage string // one line description of the flag, e.g. "Force deletion without confirmation"
3131+ envVar string // Name of an environment variable that may set this flag's value if the flag is not explicitly provided on the command line
3232+ typeStr string // Cached result of Type()
3333+ noArgValue string // Cached result of NoArgValue()
3434+ short rune // Optional shorthand version of the flag, e.g. "f" for a -f flag
3535+ kind kind.Kind // Cached concrete kind of T
3636+ isSlice bool // Cached result of IsSlice()
3537}
36383739// New constructs and returns a new [Flag].
···53555456 *p = config.DefaultValue
55575656- typeStr, noArgValue, isSlice := typeInfo[T]()
5858+ info := typeInfo[T]()
57595860 return &Flag[T]{
5961 value: p,
···6163 usage: usage,
6264 short: short,
6365 envVar: config.EnvVar,
6464- typeStr: typeStr,
6565- noArgValue: noArgValue,
6666- isSlice: isSlice,
6666+ typeStr: info.typeStr,
6767+ noArgValue: info.noArgValue,
6868+ kind: info.kind,
6969+ isSlice: info.isSlice,
6770 }, nil
6871}
6972···9194 // Special case a --help flag, because if we didn't, when you call --help
9295 // it would show up with a default of true because you've passed it
9396 // so it's value is true here
9494- if isZeroIsh(*f.value) || f.name == "help" {
9797+ if f.isZeroIsh() || f.name == "help" {
9598 return ""
9699 }
97100···136139 return format.Nil
137140 }
138141139139- switch typ := any(*f.value).(type) {
140140- case int:
141141- return format.Int(typ)
142142- case int8:
143143- return format.Int(typ)
144144- case int16:
145145- return format.Int(typ)
146146- case int32:
147147- return format.Int(typ)
148148- case int64:
149149- return format.Int(typ)
150150- case flag.Count:
151151- return format.Uint(typ)
152152- case uint:
153153- return format.Uint(typ)
154154- case uint8:
155155- return format.Uint(typ)
156156- case uint16:
157157- return format.Uint(typ)
158158- case uint32:
159159- return format.Uint(typ)
160160- case uint64:
161161- return format.Uint(typ)
162162- case uintptr:
163163- return format.Uint(typ)
164164- case float32:
165165- return format.Float32(typ)
166166- case float64:
167167- return format.Float64(typ)
168168- case string:
169169- return typ
170170- case bool:
171171- return strconv.FormatBool(typ)
172172- case []byte:
173173- return hex.EncodeToString(typ)
174174- case time.Time:
175175- return typ.Format(time.RFC3339)
176176- case time.Duration:
177177- return typ.String()
178178- case net.IP:
179179- return typ.String()
180180- case *url.URL:
181181- if typ == nil {
142142+ switch f.kind {
143143+ case kind.Int:
144144+ return format.Int(*parse.Cast[int, T](f.value))
145145+ case kind.Int8:
146146+ return format.Int(*parse.Cast[int8, T](f.value))
147147+ case kind.Int16:
148148+ return format.Int(*parse.Cast[int16, T](f.value))
149149+ case kind.Int32:
150150+ return format.Int(*parse.Cast[int32, T](f.value))
151151+ case kind.Int64:
152152+ return format.Int(*parse.Cast[int64, T](f.value))
153153+ case kind.Count:
154154+ return format.Uint(*parse.Cast[flag.Count, T](f.value))
155155+ case kind.Uint:
156156+ return format.Uint(*parse.Cast[uint, T](f.value))
157157+ case kind.Uint8:
158158+ return format.Uint(*parse.Cast[uint8, T](f.value))
159159+ case kind.Uint16:
160160+ return format.Uint(*parse.Cast[uint16, T](f.value))
161161+ case kind.Uint32:
162162+ return format.Uint(*parse.Cast[uint32, T](f.value))
163163+ case kind.Uint64:
164164+ return format.Uint(*parse.Cast[uint64, T](f.value))
165165+ case kind.Uintptr:
166166+ return format.Uint(*parse.Cast[uintptr, T](f.value))
167167+ case kind.Float32:
168168+ return format.Float32(*parse.Cast[float32, T](f.value))
169169+ case kind.Float64:
170170+ return format.Float64(*parse.Cast[float64, T](f.value))
171171+ case kind.String:
172172+ return *parse.Cast[string, T](f.value)
173173+ case kind.Bool:
174174+ return strconv.FormatBool(*parse.Cast[bool, T](f.value))
175175+ case kind.BytesHex:
176176+ return hex.EncodeToString(*parse.Cast[[]byte, T](f.value))
177177+ case kind.Time:
178178+ return parse.Cast[time.Time, T](f.value).Format(time.RFC3339)
179179+ case kind.Duration:
180180+ return parse.Cast[time.Duration, T](f.value).String()
181181+ case kind.IP:
182182+ return parse.Cast[net.IP, T](f.value).String()
183183+ case kind.URL:
184184+ u := *parse.Cast[*url.URL, T](f.value)
185185+ if u == nil {
182186 return format.Nil
183187 }
184188185185- return typ.String()
186186- case []int:
187187- return format.Slice(typ)
188188- case []int8:
189189- return format.Slice(typ)
190190- case []int16:
191191- return format.Slice(typ)
192192- case []int32:
193193- return format.Slice(typ)
194194- case []int64:
195195- return format.Slice(typ)
196196- case []uint:
197197- return format.Slice(typ)
198198- case []uint16:
199199- return format.Slice(typ)
200200- case []uint32:
201201- return format.Slice(typ)
202202- case []uint64:
203203- return format.Slice(typ)
204204- case []float32:
205205- return format.Slice(typ)
206206- case []float64:
207207- return format.Slice(typ)
208208- case []string:
209209- return format.Slice(typ)
189189+ return u.String()
190190+ case kind.IntSlice:
191191+ return format.Slice(*parse.Cast[[]int, T](f.value))
192192+ case kind.Int8Slice:
193193+ return format.Slice(*parse.Cast[[]int8, T](f.value))
194194+ case kind.Int16Slice:
195195+ return format.Slice(*parse.Cast[[]int16, T](f.value))
196196+ case kind.Int32Slice:
197197+ return format.Slice(*parse.Cast[[]int32, T](f.value))
198198+ case kind.Int64Slice:
199199+ return format.Slice(*parse.Cast[[]int64, T](f.value))
200200+ case kind.UintSlice:
201201+ return format.Slice(*parse.Cast[[]uint, T](f.value))
202202+ case kind.Uint16Slice:
203203+ return format.Slice(*parse.Cast[[]uint16, T](f.value))
204204+ case kind.Uint32Slice:
205205+ return format.Slice(*parse.Cast[[]uint32, T](f.value))
206206+ case kind.Uint64Slice:
207207+ return format.Slice(*parse.Cast[[]uint64, T](f.value))
208208+ case kind.Float32Slice:
209209+ return format.Slice(*parse.Cast[[]float32, T](f.value))
210210+ case kind.Float64Slice:
211211+ return format.Slice(*parse.Cast[[]float64, T](f.value))
212212+ case kind.StringSlice:
213213+ return format.Slice(*parse.Cast[[]string, T](f.value))
210214 default:
211211- return fmt.Sprintf("Flag.String: unsupported flag type: %T", typ)
215215+ return fmt.Sprintf("Flag.String: unsupported flag type: %T", *f.value)
212216 }
213217}
214218215215-// typeInfo computes the type-dependent metadata (Type string, NoArgValue,
216216-// IsSlice) for a flag of type T. It is called once per flag at construction
217217-// so that the hot path of Parse never has to type-switch on any(*f.value),
218218-// which would otherwise box the value on every call.
219219-func typeInfo[T flag.Flaggable]() (typeStr, noArgValue string, isSlice bool) { //nolint:cyclop // No other way of doing this realistically
219219+// info bundles the cacheable, type-dependent metadata for a Flag of a given T.
220220+type info struct {
221221+ typeStr string
222222+ noArgValue string
223223+ kind kind.Kind
224224+ isSlice bool
225225+}
226226+227227+// typeInfo computes the type-dependent metadata (kind, type string, no-arg
228228+// value, isSlice) for a flag of type T. It is called once per flag at
229229+// construction so that the hot path of Parse never has to type-switch on
230230+// any(*f.value), which would otherwise box the value on every call.
231231+func typeInfo[T flag.Flaggable]() info { //nolint:cyclop // No other way of doing this realistically
220232 var zero T
221233222234 switch typ := any(zero).(type) {
223235 case int:
224224- return format.TypeInt, "", false
236236+ return info{kind: kind.Int, typeStr: format.TypeInt}
225237 case int8:
226226- return format.TypeInt8, "", false
238238+ return info{kind: kind.Int8, typeStr: format.TypeInt8}
227239 case int16:
228228- return format.TypeInt16, "", false
240240+ return info{kind: kind.Int16, typeStr: format.TypeInt16}
229241 case int32:
230230- return format.TypeInt32, "", false
242242+ return info{kind: kind.Int32, typeStr: format.TypeInt32}
231243 case int64:
232232- return format.TypeInt64, "", false
244244+ return info{kind: kind.Int64, typeStr: format.TypeInt64}
233245 case flag.Count:
234234- return format.TypeCount, "1", false
246246+ return info{kind: kind.Count, typeStr: format.TypeCount, noArgValue: "1"}
235247 case uint:
236236- return format.TypeUint, "", false
248248+ return info{kind: kind.Uint, typeStr: format.TypeUint}
237249 case uint8:
238238- return format.TypeUint8, "", false
250250+ return info{kind: kind.Uint8, typeStr: format.TypeUint8}
239251 case uint16:
240240- return format.TypeUint16, "", false
252252+ return info{kind: kind.Uint16, typeStr: format.TypeUint16}
241253 case uint32:
242242- return format.TypeUint32, "", false
254254+ return info{kind: kind.Uint32, typeStr: format.TypeUint32}
243255 case uint64:
244244- return format.TypeUint64, "", false
256256+ return info{kind: kind.Uint64, typeStr: format.TypeUint64}
245257 case uintptr:
246246- return format.TypeUintptr, "", false
258258+ return info{kind: kind.Uintptr, typeStr: format.TypeUintptr}
247259 case float32:
248248- return format.TypeFloat32, "", false
260260+ return info{kind: kind.Float32, typeStr: format.TypeFloat32}
249261 case float64:
250250- return format.TypeFloat64, "", false
262262+ return info{kind: kind.Float64, typeStr: format.TypeFloat64}
251263 case string:
252252- return format.TypeString, "", false
264264+ return info{kind: kind.String, typeStr: format.TypeString}
253265 case bool:
254254- return format.TypeBool, format.True, false
266266+ return info{kind: kind.Bool, typeStr: format.TypeBool, noArgValue: format.True}
255267 case []byte:
256256- return format.TypeBytesHex, "", false
268268+ return info{kind: kind.BytesHex, typeStr: format.TypeBytesHex}
257269 case time.Time:
258258- return format.TypeTime, "", false
270270+ return info{kind: kind.Time, typeStr: format.TypeTime}
259271 case time.Duration:
260260- return format.TypeDuration, "", false
272272+ return info{kind: kind.Duration, typeStr: format.TypeDuration}
261273 case net.IP:
262262- return format.TypeIP, "", false
274274+ return info{kind: kind.IP, typeStr: format.TypeIP}
263275 case *url.URL:
264264- return format.TypeURL, "", false
276276+ return info{kind: kind.URL, typeStr: format.TypeURL}
265277 case []int:
266266- return format.TypeIntSlice, "", true
278278+ return info{kind: kind.IntSlice, typeStr: format.TypeIntSlice, isSlice: true}
267279 case []int8:
268268- return format.TypeInt8Slice, "", true
280280+ return info{kind: kind.Int8Slice, typeStr: format.TypeInt8Slice, isSlice: true}
269281 case []int16:
270270- return format.TypeInt16Slice, "", true
282282+ return info{kind: kind.Int16Slice, typeStr: format.TypeInt16Slice, isSlice: true}
271283 case []int32:
272272- return format.TypeInt32Slice, "", true
284284+ return info{kind: kind.Int32Slice, typeStr: format.TypeInt32Slice, isSlice: true}
273285 case []int64:
274274- return format.TypeInt64Slice, "", true
286286+ return info{kind: kind.Int64Slice, typeStr: format.TypeInt64Slice, isSlice: true}
275287 case []uint:
276276- return format.TypeUintSlice, "", true
288288+ return info{kind: kind.UintSlice, typeStr: format.TypeUintSlice, isSlice: true}
277289 case []uint16:
278278- return format.TypeUint16Slice, "", true
290290+ return info{kind: kind.Uint16Slice, typeStr: format.TypeUint16Slice, isSlice: true}
279291 case []uint32:
280280- return format.TypeUint32Slice, "", true
292292+ return info{kind: kind.Uint32Slice, typeStr: format.TypeUint32Slice, isSlice: true}
281293 case []uint64:
282282- return format.TypeUint64Slice, "", true
294294+ return info{kind: kind.Uint64Slice, typeStr: format.TypeUint64Slice, isSlice: true}
283295 case []float32:
284284- return format.TypeFloat32Slice, "", true
296296+ return info{kind: kind.Float32Slice, typeStr: format.TypeFloat32Slice, isSlice: true}
285297 case []float64:
286286- return format.TypeFloat64Slice, "", true
298298+ return info{kind: kind.Float64Slice, typeStr: format.TypeFloat64Slice, isSlice: true}
287299 case []string:
288288- return format.TypeStringSlice, "", true
300300+ return info{kind: kind.StringSlice, typeStr: format.TypeStringSlice, isSlice: true}
289301 default:
290290- return fmt.Sprintf("%T", typ), "", false
302302+ return info{kind: kind.Invalid, typeStr: fmt.Sprintf("%T", typ)}
291303 }
292304}
293305294306// Set sets a [Flag] value based on string input, i.e. parsing from the command line.
295307//
296296-//nolint:gocognit,maintidx // No other way of doing this realistically
308308+//nolint:gocognit,maintidx,cyclop // No other way of doing this realistically
297309func (f *Flag[T]) Set(str string) error {
298310 if f.value == nil {
299311 return fmt.Errorf("cannot set value %s, flag.value was nil", str)
300312 }
301313302302- switch typ := any(*f.value).(type) {
303303- case int:
314314+ switch f.kind {
315315+ case kind.Int:
304316 val, err := parse.Int(str)
305317 if err != nil {
306306- return parse.Error(parse.KindFlag, f.name, str, typ, err)
318318+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
307319 }
308320309321 *f.value = *parse.Cast[T](&val)
310322311323 return nil
312312- case int8:
324324+ case kind.Int8:
313325 val, err := parse.Int8(str)
314326 if err != nil {
315315- return parse.Error(parse.KindFlag, f.name, str, typ, err)
327327+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
316328 }
317329318330 *f.value = *parse.Cast[T](&val)
319331320332 return nil
321321- case int16:
333333+ case kind.Int16:
322334 val, err := parse.Int16(str)
323335 if err != nil {
324324- return parse.Error(parse.KindFlag, f.name, str, typ, err)
336336+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
325337 }
326338327339 *f.value = *parse.Cast[T](&val)
328340329341 return nil
330330- case int32:
342342+ case kind.Int32:
331343 val, err := parse.Int32(str)
332344 if err != nil {
333333- return parse.Error(parse.KindFlag, f.name, str, typ, err)
345345+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
334346 }
335347336348 *f.value = *parse.Cast[T](&val)
337349338350 return nil
339339- case int64:
351351+ case kind.Int64:
340352 val, err := parse.Int64(str)
341353 if err != nil {
342342- return parse.Error(parse.KindFlag, f.name, str, typ, err)
354354+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
343355 }
344356345357 *f.value = *parse.Cast[T](&val)
346358347359 return nil
348348- case flag.Count:
360360+ case kind.Count:
349361 // Add the count and store it back, we still parse the given str rather
350362 // than just +1 every time as this allows people to do e.g. --verbosity=3
351363 // as well as -vvv
352364 val, err := parse.Uint(str)
353365 if err != nil {
354354- return parse.Error(parse.KindFlag, f.name, str, typ, err)
366366+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
355367 }
356368357357- newValue := typ + flag.Count(val)
369369+ newValue := *parse.Cast[flag.Count, T](f.value) + flag.Count(val)
358370 *f.value = *parse.Cast[T](&newValue)
359371360372 return nil
361361- case uint:
373373+ case kind.Uint:
362374 val, err := parse.Uint(str)
363375 if err != nil {
364364- return parse.Error(parse.KindFlag, f.name, str, typ, err)
376376+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
365377 }
366378367379 *f.value = *parse.Cast[T](&val)
368380369381 return nil
370370- case uint8:
382382+ case kind.Uint8:
371383 val, err := parse.Uint8(str)
372384 if err != nil {
373373- return parse.Error(parse.KindFlag, f.name, str, typ, err)
385385+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
374386 }
375387376388 *f.value = *parse.Cast[T](&val)
377389378390 return nil
379379- case uint16:
391391+ case kind.Uint16:
380392 val, err := parse.Uint16(str)
381393 if err != nil {
382382- return parse.Error(parse.KindFlag, f.name, str, typ, err)
394394+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
383395 }
384396385397 *f.value = *parse.Cast[T](&val)
386398387399 return nil
388388- case uint32:
400400+ case kind.Uint32:
389401 val, err := parse.Uint32(str)
390402 if err != nil {
391391- return parse.Error(parse.KindFlag, f.name, str, typ, err)
403403+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
392404 }
393405394406 *f.value = *parse.Cast[T](&val)
395407396408 return nil
397397- case uint64:
409409+ case kind.Uint64, kind.Uintptr:
398410 val, err := parse.Uint64(str)
399411 if err != nil {
400400- return parse.Error(parse.KindFlag, f.name, str, typ, err)
412412+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
401413 }
402414403415 *f.value = *parse.Cast[T](&val)
404416405417 return nil
406406- case uintptr:
407407- val, err := parse.Uint64(str)
408408- if err != nil {
409409- return parse.Error(parse.KindFlag, f.name, str, typ, err)
410410- }
411411-412412- *f.value = *parse.Cast[T](&val)
413413-414414- return nil
415415- case float32:
418418+ case kind.Float32:
416419 val, err := parse.Float32(str)
417420 if err != nil {
418418- return parse.Error(parse.KindFlag, f.name, str, typ, err)
421421+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
419422 }
420423421424 *f.value = *parse.Cast[T](&val)
422425423426 return nil
424424- case float64:
427427+ case kind.Float64:
425428 val, err := parse.Float64(str)
426429 if err != nil {
427427- return parse.Error(parse.KindFlag, f.name, str, typ, err)
430430+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
428431 }
429432430433 *f.value = *parse.Cast[T](&val)
431434432435 return nil
433433- case string:
436436+ case kind.String:
434437 val := str
435438 *f.value = *parse.Cast[T](&val)
436439437440 return nil
438438- case bool:
441441+ case kind.Bool:
439442 val, err := strconv.ParseBool(str)
440443 if err != nil {
441441- return parse.Error(parse.KindFlag, f.name, str, typ, err)
444444+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
442445 }
443446444447 *f.value = *parse.Cast[T](&val)
445448446449 return nil
447447- case []byte:
450450+ case kind.BytesHex:
448451 val, err := hex.DecodeString(strings.TrimSpace(str))
449452 if err != nil {
450450- return parse.Error(parse.KindFlag, f.name, str, typ, err)
453453+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
451454 }
452455453456 *f.value = *parse.Cast[T](&val)
454457455458 return nil
456456- case time.Time:
459459+ case kind.Time:
457460 val, err := time.Parse(time.RFC3339, str)
458461 if err != nil {
459459- return parse.Error(parse.KindFlag, f.name, str, typ, err)
462462+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
460463 }
461464462465 *f.value = *parse.Cast[T](&val)
463466464467 return nil
465465- case time.Duration:
468468+ case kind.Duration:
466469 val, err := time.ParseDuration(str)
467470 if err != nil {
468468- return parse.Error(parse.KindFlag, f.name, str, typ, err)
471471+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
469472 }
470473471474 *f.value = *parse.Cast[T](&val)
472475473476 return nil
474474- case net.IP:
477477+ case kind.IP:
475478 val := net.ParseIP(str)
476479 if val == nil {
477477- return parse.Error(parse.KindFlag, f.name, str, typ, errors.New("invalid IP address"))
480480+ return parse.Error(parse.KindFlag, f.name, str, *f.value, errors.New("invalid IP address"))
478481 }
479482480483 *f.value = *parse.Cast[T](&val)
481484482485 return nil
483483- case *url.URL:
486486+ case kind.URL:
484487 val, err := url.ParseRequestURI(str)
485488 if err != nil {
486486- return parse.Error(parse.KindFlag, f.name, str, typ, err)
489489+ return parse.Error(parse.KindFlag, f.name, str, *f.value, err)
487490 }
488491489492 *f.value = *parse.Cast[T](&val)
490493491494 return nil
492492- case []int:
495495+ case kind.IntSlice:
493496 // Like Count, a slice flag is a read/write op
494497 newValue, err := parse.Int(str)
495498 if err != nil {
496496- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
499499+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
497500 }
498501499499- typ = append(typ, newValue)
502502+ typ := append(*parse.Cast[[]int, T](f.value), newValue)
500503 *f.value = *parse.Cast[T](&typ)
501504502505 return nil
503503- case []int8:
506506+ case kind.Int8Slice:
504507 newValue, err := parse.Int8(str)
505508 if err != nil {
506506- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
509509+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
507510 }
508511509509- typ = append(typ, newValue)
512512+ typ := append(*parse.Cast[[]int8, T](f.value), newValue)
510513 *f.value = *parse.Cast[T](&typ)
511514512515 return nil
513513- case []int16:
516516+ case kind.Int16Slice:
514517 newValue, err := parse.Int16(str)
515518 if err != nil {
516516- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
519519+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
517520 }
518521519519- typ = append(typ, newValue)
522522+ typ := append(*parse.Cast[[]int16, T](f.value), newValue)
520523 *f.value = *parse.Cast[T](&typ)
521524522525 return nil
523523- case []int32:
526526+ case kind.Int32Slice:
524527 newValue, err := parse.Int32(str)
525528 if err != nil {
526526- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
529529+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
527530 }
528531529529- typ = append(typ, newValue)
532532+ typ := append(*parse.Cast[[]int32, T](f.value), newValue)
530533 *f.value = *parse.Cast[T](&typ)
531534532535 return nil
533533- case []int64:
536536+ case kind.Int64Slice:
534537 newValue, err := parse.Int64(str)
535538 if err != nil {
536536- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
539539+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
537540 }
538541539539- typ = append(typ, newValue)
542542+ typ := append(*parse.Cast[[]int64, T](f.value), newValue)
540543 *f.value = *parse.Cast[T](&typ)
541544542545 return nil
543543- case []uint:
546546+ case kind.UintSlice:
544547 newValue, err := parse.Uint(str)
545548 if err != nil {
546546- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
549549+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
547550 }
548551549549- typ = append(typ, newValue)
552552+ typ := append(*parse.Cast[[]uint, T](f.value), newValue)
550553 *f.value = *parse.Cast[T](&typ)
551554552555 return nil
553553- case []uint16:
556556+ case kind.Uint16Slice:
554557 newValue, err := parse.Uint16(str)
555558 if err != nil {
556556- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
559559+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
557560 }
558561559559- typ = append(typ, newValue)
562562+ typ := append(*parse.Cast[[]uint16, T](f.value), newValue)
560563 *f.value = *parse.Cast[T](&typ)
561564562565 return nil
563563- case []uint32:
566566+ case kind.Uint32Slice:
564567 newValue, err := parse.Uint32(str)
565568 if err != nil {
566566- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
569569+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
567570 }
568571569569- typ = append(typ, newValue)
572572+ typ := append(*parse.Cast[[]uint32, T](f.value), newValue)
570573 *f.value = *parse.Cast[T](&typ)
571574572575 return nil
573573- case []uint64:
576576+ case kind.Uint64Slice:
574577 newValue, err := parse.Uint64(str)
575578 if err != nil {
576576- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
579579+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
577580 }
578581579579- typ = append(typ, newValue)
582582+ typ := append(*parse.Cast[[]uint64, T](f.value), newValue)
580583 *f.value = *parse.Cast[T](&typ)
581584582585 return nil
583583- case []float32:
586586+ case kind.Float32Slice:
584587 newValue, err := parse.Float32(str)
585588 if err != nil {
586586- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
589589+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
587590 }
588591589589- typ = append(typ, newValue)
592592+ typ := append(*parse.Cast[[]float32, T](f.value), newValue)
590593 *f.value = *parse.Cast[T](&typ)
591594592595 return nil
593593- case []float64:
596596+ case kind.Float64Slice:
594597 newValue, err := parse.Float64(str)
595598 if err != nil {
596596- return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err)
599599+ return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err)
597600 }
598601599599- typ = append(typ, newValue)
602602+ typ := append(*parse.Cast[[]float64, T](f.value), newValue)
600603 *f.value = *parse.Cast[T](&typ)
601604602605 return nil
603603- case []string:
606606+ case kind.StringSlice:
607607+ typ := *parse.Cast[[]string, T](f.value)
604608 typ = append(typ, str)
605609 *f.value = *parse.Cast[T](&typ)
606610607611 return nil
608612 default:
609609- return fmt.Errorf("Flag.Set: unsupported flag type: %T", typ)
613613+ return fmt.Errorf("Flag.Set: unsupported flag type: %T", *f.value)
610614 }
611615}
612616···674678 return nil
675679}
676680677677-// isZeroIsh reports whether value is the zero value (ish) for it's type.
681681+// isZeroIsh reports whether the flag's value is the zero value (ish) for it's type.
678682//
679679-// "ish" means that empty slices will return true from isZeroIsh despite their official
680680-// zero value being nil. The primary use of isZeroIsh is to determine whether or not
681681-// a default value is worth displaying to the user in the help text, and an empty slice
682682-// is probably not.
683683-func isZeroIsh[T flag.Flaggable](value T) bool { //nolint:cyclop // Not much else we can do here
684684- // Note: all the slice values ([]T) are in their own separate branches because if you
685685- // combine them, the resulting value in the body of the case block is 'any' and
686686- // you cannot do len(any)
687687- switch typ := any(value).(type) {
688688- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64:
689689- return typ == 0
690690- case flag.Count:
691691- return typ == flag.Count(0)
692692- case string:
693693- return typ == ""
694694- case bool:
695695- return !typ
696696- case []byte:
697697- return len(typ) == 0
698698- case net.IP:
699699- return len(typ) == 0
700700- case *url.URL:
701701- return typ == nil
702702- case []int:
703703- return len(typ) == 0
704704- case []int8:
705705- return len(typ) == 0
706706- case []int16:
707707- return len(typ) == 0
708708- case []int32:
709709- return len(typ) == 0
710710- case []int64:
711711- return len(typ) == 0
712712- case []uint:
713713- return len(typ) == 0
714714- case []uint16:
715715- return len(typ) == 0
716716- case []uint32:
717717- return len(typ) == 0
718718- case []uint64:
719719- return len(typ) == 0
720720- case []float32:
721721- return len(typ) == 0
722722- case []float64:
723723- return len(typ) == 0
724724- case []string:
725725- return len(typ) == 0
726726- case time.Time:
683683+// "ish" means that empty slices will return true despite their official zero
684684+// value being nil. The primary use is to determine whether a default value is
685685+// worth displaying to the user in the help text — an empty slice is probably
686686+// not.
687687+//
688688+//nolint:cyclop // Not much else we can do here
689689+func (f *Flag[T]) isZeroIsh() bool {
690690+ switch f.kind {
691691+ case kind.Int:
692692+ return *parse.Cast[int, T](f.value) == 0
693693+ case kind.Int8:
694694+ return *parse.Cast[int8, T](f.value) == 0
695695+ case kind.Int16:
696696+ return *parse.Cast[int16, T](f.value) == 0
697697+ case kind.Int32:
698698+ return *parse.Cast[int32, T](f.value) == 0
699699+ case kind.Int64:
700700+ return *parse.Cast[int64, T](f.value) == 0
701701+ case kind.Uint:
702702+ return *parse.Cast[uint, T](f.value) == 0
703703+ case kind.Uint8:
704704+ return *parse.Cast[uint8, T](f.value) == 0
705705+ case kind.Uint16:
706706+ return *parse.Cast[uint16, T](f.value) == 0
707707+ case kind.Uint32:
708708+ return *parse.Cast[uint32, T](f.value) == 0
709709+ case kind.Uint64:
710710+ return *parse.Cast[uint64, T](f.value) == 0
711711+ case kind.Uintptr:
712712+ return *parse.Cast[uintptr, T](f.value) == 0
713713+ case kind.Float32:
714714+ return *parse.Cast[float32, T](f.value) == 0
715715+ case kind.Float64:
716716+ return *parse.Cast[float64, T](f.value) == 0
717717+ case kind.Count:
718718+ return *parse.Cast[flag.Count, T](f.value) == 0
719719+ case kind.String:
720720+ return *parse.Cast[string, T](f.value) == ""
721721+ case kind.Bool:
722722+ return !*parse.Cast[bool, T](f.value)
723723+ case kind.BytesHex:
724724+ return len(*parse.Cast[[]byte, T](f.value)) == 0
725725+ case kind.IP:
726726+ return len(*parse.Cast[net.IP, T](f.value)) == 0
727727+ case kind.URL:
728728+ return *parse.Cast[*url.URL, T](f.value) == nil
729729+ case kind.IntSlice:
730730+ return len(*parse.Cast[[]int, T](f.value)) == 0
731731+ case kind.Int8Slice:
732732+ return len(*parse.Cast[[]int8, T](f.value)) == 0
733733+ case kind.Int16Slice:
734734+ return len(*parse.Cast[[]int16, T](f.value)) == 0
735735+ case kind.Int32Slice:
736736+ return len(*parse.Cast[[]int32, T](f.value)) == 0
737737+ case kind.Int64Slice:
738738+ return len(*parse.Cast[[]int64, T](f.value)) == 0
739739+ case kind.UintSlice:
740740+ return len(*parse.Cast[[]uint, T](f.value)) == 0
741741+ case kind.Uint16Slice:
742742+ return len(*parse.Cast[[]uint16, T](f.value)) == 0
743743+ case kind.Uint32Slice:
744744+ return len(*parse.Cast[[]uint32, T](f.value)) == 0
745745+ case kind.Uint64Slice:
746746+ return len(*parse.Cast[[]uint64, T](f.value)) == 0
747747+ case kind.Float32Slice:
748748+ return len(*parse.Cast[[]float32, T](f.value)) == 0
749749+ case kind.Float64Slice:
750750+ return len(*parse.Cast[[]float64, T](f.value)) == 0
751751+ case kind.StringSlice:
752752+ return len(*parse.Cast[[]string, T](f.value)) == 0
753753+ case kind.Time:
727754 var zero time.Time
728728- return typ.Equal(zero)
729729- case time.Duration:
730730- var zero time.Duration
731731- return typ == zero
755755+ return parse.Cast[time.Time, T](f.value).Equal(zero)
756756+ case kind.Duration:
757757+ return *parse.Cast[time.Duration, T](f.value) == 0
732758 default:
733759 return false
734760 }
+7
internal/flag/flag_test.go
···10431043 wantErr: true,
10441044 errMsg: `invalid shorthand for flag "delete": invalid character, must be a single ASCII letter, got "本"`,
10451045 },
10461046+ {
10471047+ name: "short is whitespace",
10481048+ flagName: "delete",
10491049+ short: ' ',
10501050+ wantErr: true,
10511051+ errMsg: `invalid shorthand for flag "delete": cannot contain whitespace`,
10521052+ },
10461053 }
1047105410481055 for _, tt := range tests {
-7
internal/flag/set.go
···290290291291 // name will either be the entire string or the name before the "="
292292 name, value, containsEquals := strings.Cut(name, "=")
293293- if err := validateFlagName(name); err != nil {
294294- return nil, fmt.Errorf("invalid flag name %q: %w", name, err)
295295- }
296293297294 flag, exists := s.flags[name]
298295 if !exists {
···367364// parseSingleShortFlag parses a single short flag entry.
368365func (s *Set) parseSingleShortFlag(shorthands string, rest []string) (string, []string, error) {
369366 char, _ := utf8.DecodeRuneInString(shorthands)
370370-371371- if err := validateFlagShort(char); err != nil {
372372- return "", nil, fmt.Errorf("invalid flag shorthand %q: %w", string(char), err)
373373- }
374367375368 flag, exists := s.shorthands[char]
376369 if !exists {
-89
internal/flag/set_test.go
···163163 errMsg: `invalid flag name "": must not be empty`,
164164 },
165165 {
166166- name: "bad syntax long extra hyphen",
167167- newSet: func(t *testing.T) *flag.Set {
168168- return flag.NewSet()
169169- },
170170- args: []string{"---"},
171171- wantErr: true,
172172- errMsg: `invalid flag name "-": trailing hyphen`,
173173- },
174174- {
175175- name: "bad syntax long leading whitespace",
176176- newSet: func(t *testing.T) *flag.Set {
177177- return flag.NewSet()
178178- },
179179- args: []string{"-- delete"},
180180- wantErr: true,
181181- errMsg: `invalid flag name " delete": cannot contain whitespace`,
182182- },
183183- {
184184- name: "bad syntax short leading whitespace",
185185- newSet: func(t *testing.T) *flag.Set {
186186- return flag.NewSet()
187187- },
188188- args: []string{"- d"},
189189- wantErr: true,
190190- errMsg: `invalid flag shorthand " ": cannot contain whitespace`,
191191- },
192192- {
193193- name: "bad syntax long trailing whitespace",
194194- newSet: func(t *testing.T) *flag.Set {
195195- return flag.NewSet()
196196- },
197197- args: []string{"--delete "},
198198- wantErr: true,
199199- errMsg: `invalid flag name "delete ": cannot contain whitespace`,
200200- },
201201- {
202202- name: "bad syntax short trailing whitespace",
203203- newSet: func(t *testing.T) *flag.Set {
204204- f, err := flag.New(new(bool), "delete", 'd', "Delete something", flag.Config[bool]{})
205205- test.Ok(t, err)
206206-207207- set := flag.NewSet()
208208-209209- err = flag.AddToSet(set, f)
210210- test.Ok(t, err)
211211-212212- return set
213213- },
214214- args: []string{"-d "},
215215- wantErr: true,
216216- errMsg: `invalid flag shorthand " ": cannot contain whitespace`,
217217- },
218218- {
219166 name: "bad syntax short more than 1 char equals",
220167 newSet: func(t *testing.T) *flag.Set {
221168 return flag.NewSet()
···223170 args: []string{"-dfv=something"},
224171 wantErr: true,
225172 errMsg: `unrecognised shorthand flag: "d" in -dfv=something`,
226226- },
227227- {
228228- name: "bad syntax short non utf8",
229229- newSet: func(t *testing.T) *flag.Set {
230230- return flag.NewSet()
231231- },
232232- args: []string{"-Ê"},
233233- wantErr: true,
234234- errMsg: `invalid flag shorthand "Ê": invalid character, must be a single ASCII letter, got "Ê"`,
235235- },
236236- {
237237- name: "bad syntax short non utf8 equals",
238238- newSet: func(t *testing.T) *flag.Set {
239239- return flag.NewSet()
240240- },
241241- args: []string{"-Ê=something"},
242242- wantErr: true,
243243- errMsg: `invalid flag shorthand "Ê": invalid character, must be a single ASCII letter, got "Ê"`,
244244- },
245245- {
246246- name: "bad syntax short multiple non utf8",
247247- newSet: func(t *testing.T) *flag.Set {
248248- return flag.NewSet()
249249- },
250250- args: []string{"-本¼語"},
251251- wantErr: true,
252252- errMsg: `invalid flag shorthand "本": invalid character, must be a single ASCII letter, got "本"`,
253253- },
254254- {
255255- name: "bad syntax long internal whitespace",
256256- newSet: func(t *testing.T) *flag.Set {
257257- return flag.NewSet()
258258- },
259259- args: []string{"--de lete"},
260260- wantErr: true,
261261- errMsg: `invalid flag name "de lete": cannot contain whitespace`,
262173 },
263174 {
264175 name: "valid long",
+48
internal/kind/kind.go
···11+// Package kind defines a compact type tag identifying the underlying
22+// concrete type of a Flag or Arg value.
33+//
44+// It exists so that hot paths do not have to do type switching which
55+// boosts performance and cuts allocations.
66+package kind
77+88+// Kind identifies the underlying concrete type of a Flag or Arg value.
99+type Kind uint8
1010+1111+// Concrete kinds for every type in the public flag.Flaggable / arg.Argable
1212+// constraints.
1313+const (
1414+ Invalid Kind = iota
1515+ Int
1616+ Int8
1717+ Int16
1818+ Int32
1919+ Int64
2020+ Uint
2121+ Uint8
2222+ Uint16
2323+ Uint32
2424+ Uint64
2525+ Uintptr
2626+ Float32
2727+ Float64
2828+ String
2929+ Bool
3030+ BytesHex
3131+ Count
3232+ Time
3333+ Duration
3434+ IP
3535+ URL
3636+ IntSlice
3737+ Int8Slice
3838+ Int16Slice
3939+ Int32Slice
4040+ Int64Slice
4141+ UintSlice
4242+ Uint16Slice
4343+ Uint32Slice
4444+ Uint64Slice
4545+ Float32Slice
4646+ Float64Slice
4747+ StringSlice
4848+)