Simple, intuitive CLI framework for Go pkg.go.dev/go.followtheprocess.codes/cli
go cli
0

Configure Feed

Select the types of activity you want to include in your feed.

More performance wins (#212)

authored by

Tom Fleet and committed by
GitHub
(Apr 26, 2026, 2:55 PM +0100) fedfe754 ed047d4d

+543 -595
+34 -44
command.go
··· 379 379 // findRequestedCommand uses the raw arguments and the command tree to determine what 380 380 // (if any) subcommand is being requested and return that command along with the arguments 381 381 // that were meant for it. 382 - func findRequestedCommand(cmd *Command, args []string) (*Command, []string) { 383 - // The next non-flag argument (if any) is the first immediate subcommand 384 - // e.g. in 'go mod tidy' we're looking for 'mod'. 385 - nextSubCommand, ok := firstNonFlagArg(cmd, args) 386 - if !ok { 387 - // No non-flag arguments, so we must already be either at the root command 388 - // or the correct subcommand 389 - return cmd, args 390 - } 391 - 392 - // Lookup this immediate subcommand by name and if we find it, recursively call 393 - // this function so we eventually end up at the end of the command tree with 394 - // the right arguments 395 - next := findSubCommand(cmd, nextSubCommand) 396 - if next != nil { 397 - return findRequestedCommand(next, argsMinusFirstX(args, nextSubCommand)) 398 - } 399 - 400 - // Found it 401 - return cmd, args 402 - } 403 - 404 - // argsMinusFirstX removes only the first x from args. Otherwise, commands that look like 405 - // openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]). 406 382 // 407 - // The input slice is not mutated so that repeated Execute calls on the same 408 - // Command see the original rawArgs. 409 - func argsMinusFirstX(args []string, x string) []string { 410 - // Note: this is borrowed from Cobra but ours is a lot simpler because we don't support 411 - // persistent flags 412 - for i, arg := range args { 413 - if arg == x { 414 - result := make([]string, 0, len(args)-1) 415 - result = append(result, args[:i]...) 416 - result = append(result, args[i+1:]...) 383 + // On the first descent into a subcommand it snapshots args into a working 384 + // slice that we own; subsequent levels then mutate that slice in place via 385 + // [slices.Delete]. The original cmd.rawArgs is never touched, so re-Execute 386 + // on the same Command still sees pristine input. 387 + func findRequestedCommand(cmd *Command, args []string) (*Command, []string) { 388 + owned := false 417 389 418 - return result 390 + for { 391 + // The next non-flag argument (if any) is the immediate subcommand 392 + // e.g. in 'go mod tidy' we're looking for 'mod'. 393 + idx, ok := firstNonFlagArg(cmd, args) 394 + if !ok { 395 + return cmd, args 419 396 } 420 - } 421 397 422 - return args 398 + next := findSubCommand(cmd, args[idx]) 399 + if next == nil { 400 + return cmd, args 401 + } 402 + 403 + if !owned { 404 + working := make([]string, len(args)) 405 + copy(working, args) 406 + args = working 407 + owned = true 408 + } 409 + 410 + args = slices.Delete(args, idx, idx+1) 411 + cmd = next 412 + } 423 413 } 424 414 425 415 // findSubCommand searches the immediate subcommands of cmd by name, looking for next. ··· 435 425 return nil 436 426 } 437 427 438 - // firstNonFlagArg walks args and returns the first positional (non-flag) 439 - // argument along with a boolean indicating whether one was found. 428 + // firstNonFlagArg walks args and returns the index of the first positional 429 + // (non-flag) argument along with a boolean indicating whether one was found. 440 430 // 441 431 // It consumes flag-value pairs (e.g. '--flag value' or '-f value') so they 442 432 // aren't mistaken for positional arguments, and stops at '--'. 443 - func firstNonFlagArg(cmd *Command, args []string) (arg string, ok bool) { 433 + func firstNonFlagArg(cmd *Command, args []string) (idx int, ok bool) { 444 434 for i := 0; i < len(args); i++ { 445 435 a := args[i] 446 436 switch { 447 437 case a == "--": 448 438 // "--" terminates the flags 449 - return "", false 439 + return -1, false 450 440 case strings.HasPrefix(a, "--") && !strings.Contains(a, "=") && !cmd.hasFlag(a[2:]): 451 441 // If '--flag value' then skip value 452 442 fallthrough 453 443 case strings.HasPrefix(a, "-") && !strings.Contains(a, "=") && len(a) == 2 && !cmd.hasShortFlag(a[1:]): 454 444 // '-f value' skip the value too. If there isn't one, we're done. 455 445 if i+1 >= len(args) { 456 - return "", false 446 + return -1, false 457 447 } 458 448 459 449 i++ ··· 461 451 continue 462 452 case a != "" && !strings.HasPrefix(a, "-"): 463 453 // First valid positional arg 464 - return a, true 454 + return i, true 465 455 } 466 456 } 467 457 468 - return "", false 458 + return -1, false 469 459 } 470 460 471 461 // showHelp is the default for a command's helpFunc.
+168 -195
internal/arg/arg.go
··· 16 16 17 17 "go.followtheprocess.codes/cli/arg" 18 18 "go.followtheprocess.codes/cli/internal/format" 19 + "go.followtheprocess.codes/cli/internal/kind" 19 20 "go.followtheprocess.codes/cli/internal/parse" 20 21 ) 21 22 ··· 23 24 24 25 // Arg represents a single command line argument. 25 26 type Arg[T arg.Argable] struct { 26 - value *T // The actual stored value 27 - config Config[T] // Additional configuration 28 - name string // Name of the argument as it appears on the command line 29 - usage string // One line description of the argument. 27 + value *T // The actual stored value 28 + config Config[T] // Additional configuration 29 + name string // Name of the argument as it appears on the command line 30 + usage string // One line description of the argument. 31 + typeStr string // Cached result of Type() 32 + kind kind.Kind // Cached concrete kind of T, set in New so hot paths skip any() boxing 30 33 } 31 34 32 35 // New constructs and returns a new [Arg]. ··· 39 42 p = new(T) 40 43 } 41 44 45 + k, typeStr := typeInfo[T]() 46 + 42 47 argument := Arg[T]{ 43 - value: p, 44 - name: name, 45 - usage: usage, 46 - config: config, 48 + value: p, 49 + name: name, 50 + usage: usage, 51 + config: config, 52 + typeStr: typeStr, 53 + kind: k, 47 54 } 48 55 49 56 return argument, nil ··· 61 68 62 69 // Default returns the default value of the argument as a string 63 70 // or "" if the argument is required. 64 - // 65 - //nolint:cyclop // No other way of doing this 66 71 func (a Arg[T]) Default() string { 67 72 if a.config.DefaultValue == nil { 68 73 // DefaultValue is nil, therefore this is a required arg 69 74 return "" 70 75 } 71 76 72 - switch typ := any(*a.config.DefaultValue).(type) { 73 - case int: 74 - return format.Int(typ) 75 - case int8: 76 - return format.Int(typ) 77 - case int16: 78 - return format.Int(typ) 79 - case int32: 80 - return format.Int(typ) 81 - case int64: 82 - return format.Int(typ) 83 - case uint: 84 - return format.Uint(typ) 85 - case uint8: 86 - return format.Uint(typ) 87 - case uint16: 88 - return format.Uint(typ) 89 - case uint32: 90 - return format.Uint(typ) 91 - case uint64: 92 - return format.Uint(typ) 93 - case uintptr: 94 - return format.Uint(typ) 95 - case float32: 96 - return format.Float32(typ) 97 - case float64: 98 - return format.Float64(typ) 99 - case string: 100 - return typ 101 - case *url.URL: 102 - return typ.String() 103 - case bool: 104 - return strconv.FormatBool(typ) 105 - case []byte: 106 - return hex.EncodeToString(typ) 107 - case time.Time: 108 - return typ.Format(time.RFC3339) 109 - case time.Duration: 110 - return typ.String() 111 - case net.IP: 112 - return typ.String() 113 - default: 114 - return fmt.Sprintf("Arg.String: unsupported arg type: %T", typ) 115 - } 77 + return formatValue(a.kind, a.config.DefaultValue) 116 78 } 117 79 118 80 // String returns the string representation of the current value of the arg. 119 - // 120 - //nolint:cyclop // No other way of doing this realistically 121 81 func (a Arg[T]) String() string { 122 82 if a.value == nil { 123 83 return format.Nil 124 84 } 125 85 126 - switch typ := any(*a.value).(type) { 127 - case int: 128 - return format.Int(typ) 129 - case int8: 130 - return format.Int(typ) 131 - case int16: 132 - return format.Int(typ) 133 - case int32: 134 - return format.Int(typ) 135 - case int64: 136 - return format.Int(typ) 137 - case uint: 138 - return format.Uint(typ) 139 - case uint8: 140 - return format.Uint(typ) 141 - case uint16: 142 - return format.Uint(typ) 143 - case uint32: 144 - return format.Uint(typ) 145 - case uint64: 146 - return format.Uint(typ) 147 - case uintptr: 148 - return format.Uint(typ) 149 - case float32: 150 - return format.Float32(typ) 151 - case float64: 152 - return format.Float64(typ) 153 - case string: 154 - return typ 155 - case *url.URL: 156 - return typ.String() 157 - case bool: 158 - return strconv.FormatBool(typ) 159 - case []byte: 160 - return hex.EncodeToString(typ) 161 - case time.Time: 162 - return typ.Format(time.RFC3339) 163 - case time.Duration: 164 - return typ.String() 165 - case net.IP: 166 - return typ.String() 167 - default: 168 - return fmt.Sprintf("Arg.String: unsupported arg type: %T", typ) 169 - } 86 + return formatValue(a.kind, a.value) 170 87 } 171 88 172 89 // Type returns a string representation of the type of the Arg. 173 - // 174 - //nolint:cyclop // No other way of doing this realistically 175 90 func (a Arg[T]) Type() string { 176 91 if a.value == nil { 177 92 return format.Nil 178 93 } 179 94 180 - switch typ := any(*a.value).(type) { 181 - case int: 182 - return format.TypeInt 183 - case int8: 184 - return format.TypeInt8 185 - case int16: 186 - return format.TypeInt16 187 - case int32: 188 - return format.TypeInt32 189 - case int64: 190 - return format.TypeInt64 191 - case uint: 192 - return format.TypeUint 193 - case uint8: 194 - return format.TypeUint8 195 - case uint16: 196 - return format.TypeUint16 197 - case uint32: 198 - return format.TypeUint32 199 - case uint64: 200 - return format.TypeUint64 201 - case uintptr: 202 - return format.TypeUintptr 203 - case float32: 204 - return format.TypeFloat32 205 - case float64: 206 - return format.TypeFloat64 207 - case string: 208 - return format.TypeString 209 - case *url.URL: 210 - return format.TypeURL 211 - case bool: 212 - return format.TypeBool 213 - case []byte: 214 - return format.TypeBytesHex 215 - case time.Time: 216 - return format.TypeTime 217 - case time.Duration: 218 - return format.TypeDuration 219 - case net.IP: 220 - return format.TypeIP 221 - default: 222 - return fmt.Sprintf("%T", typ) 223 - } 95 + return a.typeStr 224 96 } 225 97 226 98 // Set sets an [Arg] value by parsing it's string value. 227 99 // 228 - //nolint:gocognit,maintidx // No other way of doing this realistically 100 + //nolint:gocognit,maintidx,cyclop // No other way of doing this realistically 229 101 func (a Arg[T]) Set(str string) error { 230 102 if a.value == nil { 231 103 return fmt.Errorf("cannot set value %s, arg.value was nil", str) 232 104 } 233 105 234 - switch typ := any(*a.value).(type) { 235 - case int: 106 + switch a.kind { 107 + case kind.Int: 236 108 val, err := parse.Int(str) 237 109 if err != nil { 238 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 110 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 239 111 } 240 112 241 113 *a.value = *parse.Cast[T](&val) 242 114 243 115 return nil 244 - case int8: 116 + case kind.Int8: 245 117 val, err := parse.Int8(str) 246 118 if err != nil { 247 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 119 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 248 120 } 249 121 250 122 *a.value = *parse.Cast[T](&val) 251 123 252 124 return nil 253 - case int16: 125 + case kind.Int16: 254 126 val, err := parse.Int16(str) 255 127 if err != nil { 256 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 128 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 257 129 } 258 130 259 131 *a.value = *parse.Cast[T](&val) 260 132 261 133 return nil 262 - case int32: 134 + case kind.Int32: 263 135 val, err := parse.Int32(str) 264 136 if err != nil { 265 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 137 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 266 138 } 267 139 268 140 *a.value = *parse.Cast[T](&val) 269 141 270 142 return nil 271 - case int64: 143 + case kind.Int64: 272 144 val, err := parse.Int64(str) 273 145 if err != nil { 274 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 146 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 275 147 } 276 148 277 149 *a.value = *parse.Cast[T](&val) 278 150 279 151 return nil 280 - case uint: 152 + case kind.Uint: 281 153 val, err := parse.Uint(str) 282 154 if err != nil { 283 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 155 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 284 156 } 285 157 286 158 *a.value = *parse.Cast[T](&val) 287 159 288 160 return nil 289 - case uint8: 161 + case kind.Uint8: 290 162 val, err := parse.Uint8(str) 291 163 if err != nil { 292 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 164 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 293 165 } 294 166 295 167 *a.value = *parse.Cast[T](&val) 296 168 297 169 return nil 298 - case uint16: 170 + case kind.Uint16: 299 171 val, err := parse.Uint16(str) 300 172 if err != nil { 301 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 173 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 302 174 } 303 175 304 176 *a.value = *parse.Cast[T](&val) 305 177 306 178 return nil 307 - case uint32: 179 + case kind.Uint32: 308 180 val, err := parse.Uint32(str) 309 181 if err != nil { 310 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 182 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 311 183 } 312 184 313 185 *a.value = *parse.Cast[T](&val) 314 186 315 187 return nil 316 - case uint64: 188 + case kind.Uint64, kind.Uintptr: 317 189 val, err := parse.Uint64(str) 318 190 if err != nil { 319 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 191 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 320 192 } 321 193 322 194 *a.value = *parse.Cast[T](&val) 323 195 324 196 return nil 325 - case uintptr: 326 - val, err := parse.Uint64(str) 327 - if err != nil { 328 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 329 - } 330 - 331 - *a.value = *parse.Cast[T](&val) 332 - 333 - return nil 334 - case float32: 197 + case kind.Float32: 335 198 val, err := parse.Float32(str) 336 199 if err != nil { 337 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 200 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 338 201 } 339 202 340 203 *a.value = *parse.Cast[T](&val) 341 204 342 205 return nil 343 - case float64: 206 + case kind.Float64: 344 207 val, err := parse.Float64(str) 345 208 if err != nil { 346 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 209 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 347 210 } 348 211 349 212 *a.value = *parse.Cast[T](&val) 350 213 351 214 return nil 352 - case string: 215 + case kind.String: 353 216 val := str 354 217 *a.value = *parse.Cast[T](&val) 355 218 356 219 return nil 357 - case *url.URL: 220 + case kind.URL: 358 221 val, err := url.ParseRequestURI(str) 359 222 if err != nil { 360 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 223 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 361 224 } 362 225 363 226 *a.value = *parse.Cast[T](&val) 364 227 365 228 return nil 366 - case bool: 229 + case kind.Bool: 367 230 val, err := strconv.ParseBool(str) 368 231 if err != nil { 369 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 232 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 370 233 } 371 234 372 235 *a.value = *parse.Cast[T](&val) 373 236 374 237 return nil 375 - case []byte: 238 + case kind.BytesHex: 376 239 val, err := hex.DecodeString(strings.TrimSpace(str)) 377 240 if err != nil { 378 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 241 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 379 242 } 380 243 381 244 *a.value = *parse.Cast[T](&val) 382 245 383 246 return nil 384 - case time.Time: 247 + case kind.Time: 385 248 val, err := time.Parse(time.RFC3339, str) 386 249 if err != nil { 387 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 250 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 388 251 } 389 252 390 253 *a.value = *parse.Cast[T](&val) 391 254 392 255 return nil 393 - case time.Duration: 256 + case kind.Duration: 394 257 val, err := time.ParseDuration(str) 395 258 if err != nil { 396 - return parse.Error(parse.KindArgument, a.name, str, typ, err) 259 + return parse.Error(parse.KindArgument, a.name, str, *a.value, err) 397 260 } 398 261 399 262 *a.value = *parse.Cast[T](&val) 400 263 401 264 return nil 402 - case net.IP: 265 + case kind.IP: 403 266 val := net.ParseIP(str) 404 267 if val == nil { 405 - return parse.Error(parse.KindArgument, a.name, str, typ, errors.New("invalid IP address")) 268 + return parse.Error(parse.KindArgument, a.name, str, *a.value, errors.New("invalid IP address")) 406 269 } 407 270 408 271 *a.value = *parse.Cast[T](&val) 409 272 410 273 return nil 411 274 default: 412 - return fmt.Errorf("Arg.Set: unsupported arg type: %T", typ) 275 + return fmt.Errorf("Arg.Set: unsupported arg type: %T", *a.value) 276 + } 277 + } 278 + 279 + // typeInfo computes the type-dependent metadata (kind, type string) for an 280 + // arg of type T. It is called once per arg at construction so that hot paths 281 + // (Set, String, Type) never have to type-switch on any(*a.value), which would 282 + // box the value on every call. 283 + // 284 + //nolint:cyclop // No other way of doing this realistically 285 + func typeInfo[T arg.Argable]() (kind.Kind, string) { 286 + var zero T 287 + 288 + switch typ := any(zero).(type) { 289 + case int: 290 + return kind.Int, format.TypeInt 291 + case int8: 292 + return kind.Int8, format.TypeInt8 293 + case int16: 294 + return kind.Int16, format.TypeInt16 295 + case int32: 296 + return kind.Int32, format.TypeInt32 297 + case int64: 298 + return kind.Int64, format.TypeInt64 299 + case uint: 300 + return kind.Uint, format.TypeUint 301 + case uint8: 302 + return kind.Uint8, format.TypeUint8 303 + case uint16: 304 + return kind.Uint16, format.TypeUint16 305 + case uint32: 306 + return kind.Uint32, format.TypeUint32 307 + case uint64: 308 + return kind.Uint64, format.TypeUint64 309 + case uintptr: 310 + return kind.Uintptr, format.TypeUintptr 311 + case float32: 312 + return kind.Float32, format.TypeFloat32 313 + case float64: 314 + return kind.Float64, format.TypeFloat64 315 + case string: 316 + return kind.String, format.TypeString 317 + case *url.URL: 318 + return kind.URL, format.TypeURL 319 + case bool: 320 + return kind.Bool, format.TypeBool 321 + case []byte: 322 + return kind.BytesHex, format.TypeBytesHex 323 + case time.Time: 324 + return kind.Time, format.TypeTime 325 + case time.Duration: 326 + return kind.Duration, format.TypeDuration 327 + case net.IP: 328 + return kind.IP, format.TypeIP 329 + default: 330 + return kind.Invalid, fmt.Sprintf("%T", typ) 331 + } 332 + } 333 + 334 + // formatValue renders the value pointed to by p as a string using the kind dispatch. 335 + // 336 + //nolint:cyclop // No other way of doing this realistically 337 + func formatValue[T arg.Argable](k kind.Kind, p *T) string { 338 + switch k { 339 + case kind.Int: 340 + return format.Int(*parse.Cast[int, T](p)) 341 + case kind.Int8: 342 + return format.Int(*parse.Cast[int8, T](p)) 343 + case kind.Int16: 344 + return format.Int(*parse.Cast[int16, T](p)) 345 + case kind.Int32: 346 + return format.Int(*parse.Cast[int32, T](p)) 347 + case kind.Int64: 348 + return format.Int(*parse.Cast[int64, T](p)) 349 + case kind.Uint: 350 + return format.Uint(*parse.Cast[uint, T](p)) 351 + case kind.Uint8: 352 + return format.Uint(*parse.Cast[uint8, T](p)) 353 + case kind.Uint16: 354 + return format.Uint(*parse.Cast[uint16, T](p)) 355 + case kind.Uint32: 356 + return format.Uint(*parse.Cast[uint32, T](p)) 357 + case kind.Uint64: 358 + return format.Uint(*parse.Cast[uint64, T](p)) 359 + case kind.Uintptr: 360 + return format.Uint(*parse.Cast[uintptr, T](p)) 361 + case kind.Float32: 362 + return format.Float32(*parse.Cast[float32, T](p)) 363 + case kind.Float64: 364 + return format.Float64(*parse.Cast[float64, T](p)) 365 + case kind.String: 366 + return *parse.Cast[string, T](p) 367 + case kind.URL: 368 + u := *parse.Cast[*url.URL, T](p) 369 + if u == nil { 370 + return format.Nil 371 + } 372 + 373 + return u.String() 374 + case kind.Bool: 375 + return strconv.FormatBool(*parse.Cast[bool, T](p)) 376 + case kind.BytesHex: 377 + return hex.EncodeToString(*parse.Cast[[]byte, T](p)) 378 + case kind.Time: 379 + return parse.Cast[time.Time, T](p).Format(time.RFC3339) 380 + case kind.Duration: 381 + return parse.Cast[time.Duration, T](p).String() 382 + case kind.IP: 383 + return parse.Cast[net.IP, T](p).String() 384 + default: 385 + return fmt.Sprintf("Arg.String: unsupported arg type: %T", *p) 413 386 } 414 387 } 415 388
+286 -260
internal/flag/flag.go
··· 17 17 18 18 "go.followtheprocess.codes/cli/flag" 19 19 "go.followtheprocess.codes/cli/internal/format" 20 + "go.followtheprocess.codes/cli/internal/kind" 20 21 "go.followtheprocess.codes/cli/internal/parse" 21 22 ) 22 23 ··· 24 25 25 26 // Flag represents a single command line flag. 26 27 type Flag[T flag.Flaggable] struct { 27 - value *T // The actual stored value 28 - name string // The name of the flag as appears on the command line, e.g. "force" for a --force flag 29 - usage string // one line description of the flag, e.g. "Force deletion without confirmation" 30 - 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 31 - typeStr string // Cached result of Type() 32 - noArgValue string // Cached result of NoArgValue() 33 - short rune // Optional shorthand version of the flag, e.g. "f" for a -f flag 34 - isSlice bool // Cached result of IsSlice() 28 + value *T // The actual stored value 29 + name string // The name of the flag as appears on the command line, e.g. "force" for a --force flag 30 + usage string // one line description of the flag, e.g. "Force deletion without confirmation" 31 + 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 32 + typeStr string // Cached result of Type() 33 + noArgValue string // Cached result of NoArgValue() 34 + short rune // Optional shorthand version of the flag, e.g. "f" for a -f flag 35 + kind kind.Kind // Cached concrete kind of T 36 + isSlice bool // Cached result of IsSlice() 35 37 } 36 38 37 39 // New constructs and returns a new [Flag]. ··· 53 55 54 56 *p = config.DefaultValue 55 57 56 - typeStr, noArgValue, isSlice := typeInfo[T]() 58 + info := typeInfo[T]() 57 59 58 60 return &Flag[T]{ 59 61 value: p, ··· 61 63 usage: usage, 62 64 short: short, 63 65 envVar: config.EnvVar, 64 - typeStr: typeStr, 65 - noArgValue: noArgValue, 66 - isSlice: isSlice, 66 + typeStr: info.typeStr, 67 + noArgValue: info.noArgValue, 68 + kind: info.kind, 69 + isSlice: info.isSlice, 67 70 }, nil 68 71 } 69 72 ··· 91 94 // Special case a --help flag, because if we didn't, when you call --help 92 95 // it would show up with a default of true because you've passed it 93 96 // so it's value is true here 94 - if isZeroIsh(*f.value) || f.name == "help" { 97 + if f.isZeroIsh() || f.name == "help" { 95 98 return "" 96 99 } 97 100 ··· 136 139 return format.Nil 137 140 } 138 141 139 - switch typ := any(*f.value).(type) { 140 - case int: 141 - return format.Int(typ) 142 - case int8: 143 - return format.Int(typ) 144 - case int16: 145 - return format.Int(typ) 146 - case int32: 147 - return format.Int(typ) 148 - case int64: 149 - return format.Int(typ) 150 - case flag.Count: 151 - return format.Uint(typ) 152 - case uint: 153 - return format.Uint(typ) 154 - case uint8: 155 - return format.Uint(typ) 156 - case uint16: 157 - return format.Uint(typ) 158 - case uint32: 159 - return format.Uint(typ) 160 - case uint64: 161 - return format.Uint(typ) 162 - case uintptr: 163 - return format.Uint(typ) 164 - case float32: 165 - return format.Float32(typ) 166 - case float64: 167 - return format.Float64(typ) 168 - case string: 169 - return typ 170 - case bool: 171 - return strconv.FormatBool(typ) 172 - case []byte: 173 - return hex.EncodeToString(typ) 174 - case time.Time: 175 - return typ.Format(time.RFC3339) 176 - case time.Duration: 177 - return typ.String() 178 - case net.IP: 179 - return typ.String() 180 - case *url.URL: 181 - if typ == nil { 142 + switch f.kind { 143 + case kind.Int: 144 + return format.Int(*parse.Cast[int, T](f.value)) 145 + case kind.Int8: 146 + return format.Int(*parse.Cast[int8, T](f.value)) 147 + case kind.Int16: 148 + return format.Int(*parse.Cast[int16, T](f.value)) 149 + case kind.Int32: 150 + return format.Int(*parse.Cast[int32, T](f.value)) 151 + case kind.Int64: 152 + return format.Int(*parse.Cast[int64, T](f.value)) 153 + case kind.Count: 154 + return format.Uint(*parse.Cast[flag.Count, T](f.value)) 155 + case kind.Uint: 156 + return format.Uint(*parse.Cast[uint, T](f.value)) 157 + case kind.Uint8: 158 + return format.Uint(*parse.Cast[uint8, T](f.value)) 159 + case kind.Uint16: 160 + return format.Uint(*parse.Cast[uint16, T](f.value)) 161 + case kind.Uint32: 162 + return format.Uint(*parse.Cast[uint32, T](f.value)) 163 + case kind.Uint64: 164 + return format.Uint(*parse.Cast[uint64, T](f.value)) 165 + case kind.Uintptr: 166 + return format.Uint(*parse.Cast[uintptr, T](f.value)) 167 + case kind.Float32: 168 + return format.Float32(*parse.Cast[float32, T](f.value)) 169 + case kind.Float64: 170 + return format.Float64(*parse.Cast[float64, T](f.value)) 171 + case kind.String: 172 + return *parse.Cast[string, T](f.value) 173 + case kind.Bool: 174 + return strconv.FormatBool(*parse.Cast[bool, T](f.value)) 175 + case kind.BytesHex: 176 + return hex.EncodeToString(*parse.Cast[[]byte, T](f.value)) 177 + case kind.Time: 178 + return parse.Cast[time.Time, T](f.value).Format(time.RFC3339) 179 + case kind.Duration: 180 + return parse.Cast[time.Duration, T](f.value).String() 181 + case kind.IP: 182 + return parse.Cast[net.IP, T](f.value).String() 183 + case kind.URL: 184 + u := *parse.Cast[*url.URL, T](f.value) 185 + if u == nil { 182 186 return format.Nil 183 187 } 184 188 185 - return typ.String() 186 - case []int: 187 - return format.Slice(typ) 188 - case []int8: 189 - return format.Slice(typ) 190 - case []int16: 191 - return format.Slice(typ) 192 - case []int32: 193 - return format.Slice(typ) 194 - case []int64: 195 - return format.Slice(typ) 196 - case []uint: 197 - return format.Slice(typ) 198 - case []uint16: 199 - return format.Slice(typ) 200 - case []uint32: 201 - return format.Slice(typ) 202 - case []uint64: 203 - return format.Slice(typ) 204 - case []float32: 205 - return format.Slice(typ) 206 - case []float64: 207 - return format.Slice(typ) 208 - case []string: 209 - return format.Slice(typ) 189 + return u.String() 190 + case kind.IntSlice: 191 + return format.Slice(*parse.Cast[[]int, T](f.value)) 192 + case kind.Int8Slice: 193 + return format.Slice(*parse.Cast[[]int8, T](f.value)) 194 + case kind.Int16Slice: 195 + return format.Slice(*parse.Cast[[]int16, T](f.value)) 196 + case kind.Int32Slice: 197 + return format.Slice(*parse.Cast[[]int32, T](f.value)) 198 + case kind.Int64Slice: 199 + return format.Slice(*parse.Cast[[]int64, T](f.value)) 200 + case kind.UintSlice: 201 + return format.Slice(*parse.Cast[[]uint, T](f.value)) 202 + case kind.Uint16Slice: 203 + return format.Slice(*parse.Cast[[]uint16, T](f.value)) 204 + case kind.Uint32Slice: 205 + return format.Slice(*parse.Cast[[]uint32, T](f.value)) 206 + case kind.Uint64Slice: 207 + return format.Slice(*parse.Cast[[]uint64, T](f.value)) 208 + case kind.Float32Slice: 209 + return format.Slice(*parse.Cast[[]float32, T](f.value)) 210 + case kind.Float64Slice: 211 + return format.Slice(*parse.Cast[[]float64, T](f.value)) 212 + case kind.StringSlice: 213 + return format.Slice(*parse.Cast[[]string, T](f.value)) 210 214 default: 211 - return fmt.Sprintf("Flag.String: unsupported flag type: %T", typ) 215 + return fmt.Sprintf("Flag.String: unsupported flag type: %T", *f.value) 212 216 } 213 217 } 214 218 215 - // typeInfo computes the type-dependent metadata (Type string, NoArgValue, 216 - // IsSlice) for a flag of type T. It is called once per flag at construction 217 - // so that the hot path of Parse never has to type-switch on any(*f.value), 218 - // which would otherwise box the value on every call. 219 - func typeInfo[T flag.Flaggable]() (typeStr, noArgValue string, isSlice bool) { //nolint:cyclop // No other way of doing this realistically 219 + // info bundles the cacheable, type-dependent metadata for a Flag of a given T. 220 + type info struct { 221 + typeStr string 222 + noArgValue string 223 + kind kind.Kind 224 + isSlice bool 225 + } 226 + 227 + // typeInfo computes the type-dependent metadata (kind, type string, no-arg 228 + // value, isSlice) for a flag of type T. It is called once per flag at 229 + // construction so that the hot path of Parse never has to type-switch on 230 + // any(*f.value), which would otherwise box the value on every call. 231 + func typeInfo[T flag.Flaggable]() info { //nolint:cyclop // No other way of doing this realistically 220 232 var zero T 221 233 222 234 switch typ := any(zero).(type) { 223 235 case int: 224 - return format.TypeInt, "", false 236 + return info{kind: kind.Int, typeStr: format.TypeInt} 225 237 case int8: 226 - return format.TypeInt8, "", false 238 + return info{kind: kind.Int8, typeStr: format.TypeInt8} 227 239 case int16: 228 - return format.TypeInt16, "", false 240 + return info{kind: kind.Int16, typeStr: format.TypeInt16} 229 241 case int32: 230 - return format.TypeInt32, "", false 242 + return info{kind: kind.Int32, typeStr: format.TypeInt32} 231 243 case int64: 232 - return format.TypeInt64, "", false 244 + return info{kind: kind.Int64, typeStr: format.TypeInt64} 233 245 case flag.Count: 234 - return format.TypeCount, "1", false 246 + return info{kind: kind.Count, typeStr: format.TypeCount, noArgValue: "1"} 235 247 case uint: 236 - return format.TypeUint, "", false 248 + return info{kind: kind.Uint, typeStr: format.TypeUint} 237 249 case uint8: 238 - return format.TypeUint8, "", false 250 + return info{kind: kind.Uint8, typeStr: format.TypeUint8} 239 251 case uint16: 240 - return format.TypeUint16, "", false 252 + return info{kind: kind.Uint16, typeStr: format.TypeUint16} 241 253 case uint32: 242 - return format.TypeUint32, "", false 254 + return info{kind: kind.Uint32, typeStr: format.TypeUint32} 243 255 case uint64: 244 - return format.TypeUint64, "", false 256 + return info{kind: kind.Uint64, typeStr: format.TypeUint64} 245 257 case uintptr: 246 - return format.TypeUintptr, "", false 258 + return info{kind: kind.Uintptr, typeStr: format.TypeUintptr} 247 259 case float32: 248 - return format.TypeFloat32, "", false 260 + return info{kind: kind.Float32, typeStr: format.TypeFloat32} 249 261 case float64: 250 - return format.TypeFloat64, "", false 262 + return info{kind: kind.Float64, typeStr: format.TypeFloat64} 251 263 case string: 252 - return format.TypeString, "", false 264 + return info{kind: kind.String, typeStr: format.TypeString} 253 265 case bool: 254 - return format.TypeBool, format.True, false 266 + return info{kind: kind.Bool, typeStr: format.TypeBool, noArgValue: format.True} 255 267 case []byte: 256 - return format.TypeBytesHex, "", false 268 + return info{kind: kind.BytesHex, typeStr: format.TypeBytesHex} 257 269 case time.Time: 258 - return format.TypeTime, "", false 270 + return info{kind: kind.Time, typeStr: format.TypeTime} 259 271 case time.Duration: 260 - return format.TypeDuration, "", false 272 + return info{kind: kind.Duration, typeStr: format.TypeDuration} 261 273 case net.IP: 262 - return format.TypeIP, "", false 274 + return info{kind: kind.IP, typeStr: format.TypeIP} 263 275 case *url.URL: 264 - return format.TypeURL, "", false 276 + return info{kind: kind.URL, typeStr: format.TypeURL} 265 277 case []int: 266 - return format.TypeIntSlice, "", true 278 + return info{kind: kind.IntSlice, typeStr: format.TypeIntSlice, isSlice: true} 267 279 case []int8: 268 - return format.TypeInt8Slice, "", true 280 + return info{kind: kind.Int8Slice, typeStr: format.TypeInt8Slice, isSlice: true} 269 281 case []int16: 270 - return format.TypeInt16Slice, "", true 282 + return info{kind: kind.Int16Slice, typeStr: format.TypeInt16Slice, isSlice: true} 271 283 case []int32: 272 - return format.TypeInt32Slice, "", true 284 + return info{kind: kind.Int32Slice, typeStr: format.TypeInt32Slice, isSlice: true} 273 285 case []int64: 274 - return format.TypeInt64Slice, "", true 286 + return info{kind: kind.Int64Slice, typeStr: format.TypeInt64Slice, isSlice: true} 275 287 case []uint: 276 - return format.TypeUintSlice, "", true 288 + return info{kind: kind.UintSlice, typeStr: format.TypeUintSlice, isSlice: true} 277 289 case []uint16: 278 - return format.TypeUint16Slice, "", true 290 + return info{kind: kind.Uint16Slice, typeStr: format.TypeUint16Slice, isSlice: true} 279 291 case []uint32: 280 - return format.TypeUint32Slice, "", true 292 + return info{kind: kind.Uint32Slice, typeStr: format.TypeUint32Slice, isSlice: true} 281 293 case []uint64: 282 - return format.TypeUint64Slice, "", true 294 + return info{kind: kind.Uint64Slice, typeStr: format.TypeUint64Slice, isSlice: true} 283 295 case []float32: 284 - return format.TypeFloat32Slice, "", true 296 + return info{kind: kind.Float32Slice, typeStr: format.TypeFloat32Slice, isSlice: true} 285 297 case []float64: 286 - return format.TypeFloat64Slice, "", true 298 + return info{kind: kind.Float64Slice, typeStr: format.TypeFloat64Slice, isSlice: true} 287 299 case []string: 288 - return format.TypeStringSlice, "", true 300 + return info{kind: kind.StringSlice, typeStr: format.TypeStringSlice, isSlice: true} 289 301 default: 290 - return fmt.Sprintf("%T", typ), "", false 302 + return info{kind: kind.Invalid, typeStr: fmt.Sprintf("%T", typ)} 291 303 } 292 304 } 293 305 294 306 // Set sets a [Flag] value based on string input, i.e. parsing from the command line. 295 307 // 296 - //nolint:gocognit,maintidx // No other way of doing this realistically 308 + //nolint:gocognit,maintidx,cyclop // No other way of doing this realistically 297 309 func (f *Flag[T]) Set(str string) error { 298 310 if f.value == nil { 299 311 return fmt.Errorf("cannot set value %s, flag.value was nil", str) 300 312 } 301 313 302 - switch typ := any(*f.value).(type) { 303 - case int: 314 + switch f.kind { 315 + case kind.Int: 304 316 val, err := parse.Int(str) 305 317 if err != nil { 306 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 318 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 307 319 } 308 320 309 321 *f.value = *parse.Cast[T](&val) 310 322 311 323 return nil 312 - case int8: 324 + case kind.Int8: 313 325 val, err := parse.Int8(str) 314 326 if err != nil { 315 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 327 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 316 328 } 317 329 318 330 *f.value = *parse.Cast[T](&val) 319 331 320 332 return nil 321 - case int16: 333 + case kind.Int16: 322 334 val, err := parse.Int16(str) 323 335 if err != nil { 324 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 336 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 325 337 } 326 338 327 339 *f.value = *parse.Cast[T](&val) 328 340 329 341 return nil 330 - case int32: 342 + case kind.Int32: 331 343 val, err := parse.Int32(str) 332 344 if err != nil { 333 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 345 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 334 346 } 335 347 336 348 *f.value = *parse.Cast[T](&val) 337 349 338 350 return nil 339 - case int64: 351 + case kind.Int64: 340 352 val, err := parse.Int64(str) 341 353 if err != nil { 342 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 354 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 343 355 } 344 356 345 357 *f.value = *parse.Cast[T](&val) 346 358 347 359 return nil 348 - case flag.Count: 360 + case kind.Count: 349 361 // Add the count and store it back, we still parse the given str rather 350 362 // than just +1 every time as this allows people to do e.g. --verbosity=3 351 363 // as well as -vvv 352 364 val, err := parse.Uint(str) 353 365 if err != nil { 354 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 366 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 355 367 } 356 368 357 - newValue := typ + flag.Count(val) 369 + newValue := *parse.Cast[flag.Count, T](f.value) + flag.Count(val) 358 370 *f.value = *parse.Cast[T](&newValue) 359 371 360 372 return nil 361 - case uint: 373 + case kind.Uint: 362 374 val, err := parse.Uint(str) 363 375 if err != nil { 364 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 376 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 365 377 } 366 378 367 379 *f.value = *parse.Cast[T](&val) 368 380 369 381 return nil 370 - case uint8: 382 + case kind.Uint8: 371 383 val, err := parse.Uint8(str) 372 384 if err != nil { 373 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 385 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 374 386 } 375 387 376 388 *f.value = *parse.Cast[T](&val) 377 389 378 390 return nil 379 - case uint16: 391 + case kind.Uint16: 380 392 val, err := parse.Uint16(str) 381 393 if err != nil { 382 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 394 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 383 395 } 384 396 385 397 *f.value = *parse.Cast[T](&val) 386 398 387 399 return nil 388 - case uint32: 400 + case kind.Uint32: 389 401 val, err := parse.Uint32(str) 390 402 if err != nil { 391 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 403 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 392 404 } 393 405 394 406 *f.value = *parse.Cast[T](&val) 395 407 396 408 return nil 397 - case uint64: 409 + case kind.Uint64, kind.Uintptr: 398 410 val, err := parse.Uint64(str) 399 411 if err != nil { 400 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 412 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 401 413 } 402 414 403 415 *f.value = *parse.Cast[T](&val) 404 416 405 417 return nil 406 - case uintptr: 407 - val, err := parse.Uint64(str) 408 - if err != nil { 409 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 410 - } 411 - 412 - *f.value = *parse.Cast[T](&val) 413 - 414 - return nil 415 - case float32: 418 + case kind.Float32: 416 419 val, err := parse.Float32(str) 417 420 if err != nil { 418 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 421 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 419 422 } 420 423 421 424 *f.value = *parse.Cast[T](&val) 422 425 423 426 return nil 424 - case float64: 427 + case kind.Float64: 425 428 val, err := parse.Float64(str) 426 429 if err != nil { 427 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 430 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 428 431 } 429 432 430 433 *f.value = *parse.Cast[T](&val) 431 434 432 435 return nil 433 - case string: 436 + case kind.String: 434 437 val := str 435 438 *f.value = *parse.Cast[T](&val) 436 439 437 440 return nil 438 - case bool: 441 + case kind.Bool: 439 442 val, err := strconv.ParseBool(str) 440 443 if err != nil { 441 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 444 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 442 445 } 443 446 444 447 *f.value = *parse.Cast[T](&val) 445 448 446 449 return nil 447 - case []byte: 450 + case kind.BytesHex: 448 451 val, err := hex.DecodeString(strings.TrimSpace(str)) 449 452 if err != nil { 450 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 453 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 451 454 } 452 455 453 456 *f.value = *parse.Cast[T](&val) 454 457 455 458 return nil 456 - case time.Time: 459 + case kind.Time: 457 460 val, err := time.Parse(time.RFC3339, str) 458 461 if err != nil { 459 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 462 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 460 463 } 461 464 462 465 *f.value = *parse.Cast[T](&val) 463 466 464 467 return nil 465 - case time.Duration: 468 + case kind.Duration: 466 469 val, err := time.ParseDuration(str) 467 470 if err != nil { 468 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 471 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 469 472 } 470 473 471 474 *f.value = *parse.Cast[T](&val) 472 475 473 476 return nil 474 - case net.IP: 477 + case kind.IP: 475 478 val := net.ParseIP(str) 476 479 if val == nil { 477 - return parse.Error(parse.KindFlag, f.name, str, typ, errors.New("invalid IP address")) 480 + return parse.Error(parse.KindFlag, f.name, str, *f.value, errors.New("invalid IP address")) 478 481 } 479 482 480 483 *f.value = *parse.Cast[T](&val) 481 484 482 485 return nil 483 - case *url.URL: 486 + case kind.URL: 484 487 val, err := url.ParseRequestURI(str) 485 488 if err != nil { 486 - return parse.Error(parse.KindFlag, f.name, str, typ, err) 489 + return parse.Error(parse.KindFlag, f.name, str, *f.value, err) 487 490 } 488 491 489 492 *f.value = *parse.Cast[T](&val) 490 493 491 494 return nil 492 - case []int: 495 + case kind.IntSlice: 493 496 // Like Count, a slice flag is a read/write op 494 497 newValue, err := parse.Int(str) 495 498 if err != nil { 496 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 499 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 497 500 } 498 501 499 - typ = append(typ, newValue) 502 + typ := append(*parse.Cast[[]int, T](f.value), newValue) 500 503 *f.value = *parse.Cast[T](&typ) 501 504 502 505 return nil 503 - case []int8: 506 + case kind.Int8Slice: 504 507 newValue, err := parse.Int8(str) 505 508 if err != nil { 506 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 509 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 507 510 } 508 511 509 - typ = append(typ, newValue) 512 + typ := append(*parse.Cast[[]int8, T](f.value), newValue) 510 513 *f.value = *parse.Cast[T](&typ) 511 514 512 515 return nil 513 - case []int16: 516 + case kind.Int16Slice: 514 517 newValue, err := parse.Int16(str) 515 518 if err != nil { 516 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 519 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 517 520 } 518 521 519 - typ = append(typ, newValue) 522 + typ := append(*parse.Cast[[]int16, T](f.value), newValue) 520 523 *f.value = *parse.Cast[T](&typ) 521 524 522 525 return nil 523 - case []int32: 526 + case kind.Int32Slice: 524 527 newValue, err := parse.Int32(str) 525 528 if err != nil { 526 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 529 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 527 530 } 528 531 529 - typ = append(typ, newValue) 532 + typ := append(*parse.Cast[[]int32, T](f.value), newValue) 530 533 *f.value = *parse.Cast[T](&typ) 531 534 532 535 return nil 533 - case []int64: 536 + case kind.Int64Slice: 534 537 newValue, err := parse.Int64(str) 535 538 if err != nil { 536 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 539 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 537 540 } 538 541 539 - typ = append(typ, newValue) 542 + typ := append(*parse.Cast[[]int64, T](f.value), newValue) 540 543 *f.value = *parse.Cast[T](&typ) 541 544 542 545 return nil 543 - case []uint: 546 + case kind.UintSlice: 544 547 newValue, err := parse.Uint(str) 545 548 if err != nil { 546 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 549 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 547 550 } 548 551 549 - typ = append(typ, newValue) 552 + typ := append(*parse.Cast[[]uint, T](f.value), newValue) 550 553 *f.value = *parse.Cast[T](&typ) 551 554 552 555 return nil 553 - case []uint16: 556 + case kind.Uint16Slice: 554 557 newValue, err := parse.Uint16(str) 555 558 if err != nil { 556 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 559 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 557 560 } 558 561 559 - typ = append(typ, newValue) 562 + typ := append(*parse.Cast[[]uint16, T](f.value), newValue) 560 563 *f.value = *parse.Cast[T](&typ) 561 564 562 565 return nil 563 - case []uint32: 566 + case kind.Uint32Slice: 564 567 newValue, err := parse.Uint32(str) 565 568 if err != nil { 566 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 569 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 567 570 } 568 571 569 - typ = append(typ, newValue) 572 + typ := append(*parse.Cast[[]uint32, T](f.value), newValue) 570 573 *f.value = *parse.Cast[T](&typ) 571 574 572 575 return nil 573 - case []uint64: 576 + case kind.Uint64Slice: 574 577 newValue, err := parse.Uint64(str) 575 578 if err != nil { 576 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 579 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 577 580 } 578 581 579 - typ = append(typ, newValue) 582 + typ := append(*parse.Cast[[]uint64, T](f.value), newValue) 580 583 *f.value = *parse.Cast[T](&typ) 581 584 582 585 return nil 583 - case []float32: 586 + case kind.Float32Slice: 584 587 newValue, err := parse.Float32(str) 585 588 if err != nil { 586 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 589 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 587 590 } 588 591 589 - typ = append(typ, newValue) 592 + typ := append(*parse.Cast[[]float32, T](f.value), newValue) 590 593 *f.value = *parse.Cast[T](&typ) 591 594 592 595 return nil 593 - case []float64: 596 + case kind.Float64Slice: 594 597 newValue, err := parse.Float64(str) 595 598 if err != nil { 596 - return parse.ErrorSlice(parse.KindFlag, f.name, str, typ, err) 599 + return parse.ErrorSlice(parse.KindFlag, f.name, str, *f.value, err) 597 600 } 598 601 599 - typ = append(typ, newValue) 602 + typ := append(*parse.Cast[[]float64, T](f.value), newValue) 600 603 *f.value = *parse.Cast[T](&typ) 601 604 602 605 return nil 603 - case []string: 606 + case kind.StringSlice: 607 + typ := *parse.Cast[[]string, T](f.value) 604 608 typ = append(typ, str) 605 609 *f.value = *parse.Cast[T](&typ) 606 610 607 611 return nil 608 612 default: 609 - return fmt.Errorf("Flag.Set: unsupported flag type: %T", typ) 613 + return fmt.Errorf("Flag.Set: unsupported flag type: %T", *f.value) 610 614 } 611 615 } 612 616 ··· 674 678 return nil 675 679 } 676 680 677 - // isZeroIsh reports whether value is the zero value (ish) for it's type. 681 + // isZeroIsh reports whether the flag's value is the zero value (ish) for it's type. 678 682 // 679 - // "ish" means that empty slices will return true from isZeroIsh despite their official 680 - // zero value being nil. The primary use of isZeroIsh is to determine whether or not 681 - // a default value is worth displaying to the user in the help text, and an empty slice 682 - // is probably not. 683 - func isZeroIsh[T flag.Flaggable](value T) bool { //nolint:cyclop // Not much else we can do here 684 - // Note: all the slice values ([]T) are in their own separate branches because if you 685 - // combine them, the resulting value in the body of the case block is 'any' and 686 - // you cannot do len(any) 687 - switch typ := any(value).(type) { 688 - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64: 689 - return typ == 0 690 - case flag.Count: 691 - return typ == flag.Count(0) 692 - case string: 693 - return typ == "" 694 - case bool: 695 - return !typ 696 - case []byte: 697 - return len(typ) == 0 698 - case net.IP: 699 - return len(typ) == 0 700 - case *url.URL: 701 - return typ == nil 702 - case []int: 703 - return len(typ) == 0 704 - case []int8: 705 - return len(typ) == 0 706 - case []int16: 707 - return len(typ) == 0 708 - case []int32: 709 - return len(typ) == 0 710 - case []int64: 711 - return len(typ) == 0 712 - case []uint: 713 - return len(typ) == 0 714 - case []uint16: 715 - return len(typ) == 0 716 - case []uint32: 717 - return len(typ) == 0 718 - case []uint64: 719 - return len(typ) == 0 720 - case []float32: 721 - return len(typ) == 0 722 - case []float64: 723 - return len(typ) == 0 724 - case []string: 725 - return len(typ) == 0 726 - case time.Time: 683 + // "ish" means that empty slices will return true despite their official zero 684 + // value being nil. The primary use is to determine whether a default value is 685 + // worth displaying to the user in the help text — an empty slice is probably 686 + // not. 687 + // 688 + //nolint:cyclop // Not much else we can do here 689 + func (f *Flag[T]) isZeroIsh() bool { 690 + switch f.kind { 691 + case kind.Int: 692 + return *parse.Cast[int, T](f.value) == 0 693 + case kind.Int8: 694 + return *parse.Cast[int8, T](f.value) == 0 695 + case kind.Int16: 696 + return *parse.Cast[int16, T](f.value) == 0 697 + case kind.Int32: 698 + return *parse.Cast[int32, T](f.value) == 0 699 + case kind.Int64: 700 + return *parse.Cast[int64, T](f.value) == 0 701 + case kind.Uint: 702 + return *parse.Cast[uint, T](f.value) == 0 703 + case kind.Uint8: 704 + return *parse.Cast[uint8, T](f.value) == 0 705 + case kind.Uint16: 706 + return *parse.Cast[uint16, T](f.value) == 0 707 + case kind.Uint32: 708 + return *parse.Cast[uint32, T](f.value) == 0 709 + case kind.Uint64: 710 + return *parse.Cast[uint64, T](f.value) == 0 711 + case kind.Uintptr: 712 + return *parse.Cast[uintptr, T](f.value) == 0 713 + case kind.Float32: 714 + return *parse.Cast[float32, T](f.value) == 0 715 + case kind.Float64: 716 + return *parse.Cast[float64, T](f.value) == 0 717 + case kind.Count: 718 + return *parse.Cast[flag.Count, T](f.value) == 0 719 + case kind.String: 720 + return *parse.Cast[string, T](f.value) == "" 721 + case kind.Bool: 722 + return !*parse.Cast[bool, T](f.value) 723 + case kind.BytesHex: 724 + return len(*parse.Cast[[]byte, T](f.value)) == 0 725 + case kind.IP: 726 + return len(*parse.Cast[net.IP, T](f.value)) == 0 727 + case kind.URL: 728 + return *parse.Cast[*url.URL, T](f.value) == nil 729 + case kind.IntSlice: 730 + return len(*parse.Cast[[]int, T](f.value)) == 0 731 + case kind.Int8Slice: 732 + return len(*parse.Cast[[]int8, T](f.value)) == 0 733 + case kind.Int16Slice: 734 + return len(*parse.Cast[[]int16, T](f.value)) == 0 735 + case kind.Int32Slice: 736 + return len(*parse.Cast[[]int32, T](f.value)) == 0 737 + case kind.Int64Slice: 738 + return len(*parse.Cast[[]int64, T](f.value)) == 0 739 + case kind.UintSlice: 740 + return len(*parse.Cast[[]uint, T](f.value)) == 0 741 + case kind.Uint16Slice: 742 + return len(*parse.Cast[[]uint16, T](f.value)) == 0 743 + case kind.Uint32Slice: 744 + return len(*parse.Cast[[]uint32, T](f.value)) == 0 745 + case kind.Uint64Slice: 746 + return len(*parse.Cast[[]uint64, T](f.value)) == 0 747 + case kind.Float32Slice: 748 + return len(*parse.Cast[[]float32, T](f.value)) == 0 749 + case kind.Float64Slice: 750 + return len(*parse.Cast[[]float64, T](f.value)) == 0 751 + case kind.StringSlice: 752 + return len(*parse.Cast[[]string, T](f.value)) == 0 753 + case kind.Time: 727 754 var zero time.Time 728 - return typ.Equal(zero) 729 - case time.Duration: 730 - var zero time.Duration 731 - return typ == zero 755 + return parse.Cast[time.Time, T](f.value).Equal(zero) 756 + case kind.Duration: 757 + return *parse.Cast[time.Duration, T](f.value) == 0 732 758 default: 733 759 return false 734 760 }
+7
internal/flag/flag_test.go
··· 1043 1043 wantErr: true, 1044 1044 errMsg: `invalid shorthand for flag "delete": invalid character, must be a single ASCII letter, got "本"`, 1045 1045 }, 1046 + { 1047 + name: "short is whitespace", 1048 + flagName: "delete", 1049 + short: ' ', 1050 + wantErr: true, 1051 + errMsg: `invalid shorthand for flag "delete": cannot contain whitespace`, 1052 + }, 1046 1053 } 1047 1054 1048 1055 for _, tt := range tests {
-7
internal/flag/set.go
··· 290 290 291 291 // name will either be the entire string or the name before the "=" 292 292 name, value, containsEquals := strings.Cut(name, "=") 293 - if err := validateFlagName(name); err != nil { 294 - return nil, fmt.Errorf("invalid flag name %q: %w", name, err) 295 - } 296 293 297 294 flag, exists := s.flags[name] 298 295 if !exists { ··· 367 364 // parseSingleShortFlag parses a single short flag entry. 368 365 func (s *Set) parseSingleShortFlag(shorthands string, rest []string) (string, []string, error) { 369 366 char, _ := utf8.DecodeRuneInString(shorthands) 370 - 371 - if err := validateFlagShort(char); err != nil { 372 - return "", nil, fmt.Errorf("invalid flag shorthand %q: %w", string(char), err) 373 - } 374 367 375 368 flag, exists := s.shorthands[char] 376 369 if !exists {
-89
internal/flag/set_test.go
··· 163 163 errMsg: `invalid flag name "": must not be empty`, 164 164 }, 165 165 { 166 - name: "bad syntax long extra hyphen", 167 - newSet: func(t *testing.T) *flag.Set { 168 - return flag.NewSet() 169 - }, 170 - args: []string{"---"}, 171 - wantErr: true, 172 - errMsg: `invalid flag name "-": trailing hyphen`, 173 - }, 174 - { 175 - name: "bad syntax long leading whitespace", 176 - newSet: func(t *testing.T) *flag.Set { 177 - return flag.NewSet() 178 - }, 179 - args: []string{"-- delete"}, 180 - wantErr: true, 181 - errMsg: `invalid flag name " delete": cannot contain whitespace`, 182 - }, 183 - { 184 - name: "bad syntax short leading whitespace", 185 - newSet: func(t *testing.T) *flag.Set { 186 - return flag.NewSet() 187 - }, 188 - args: []string{"- d"}, 189 - wantErr: true, 190 - errMsg: `invalid flag shorthand " ": cannot contain whitespace`, 191 - }, 192 - { 193 - name: "bad syntax long trailing whitespace", 194 - newSet: func(t *testing.T) *flag.Set { 195 - return flag.NewSet() 196 - }, 197 - args: []string{"--delete "}, 198 - wantErr: true, 199 - errMsg: `invalid flag name "delete ": cannot contain whitespace`, 200 - }, 201 - { 202 - name: "bad syntax short trailing whitespace", 203 - newSet: func(t *testing.T) *flag.Set { 204 - f, err := flag.New(new(bool), "delete", 'd', "Delete something", flag.Config[bool]{}) 205 - test.Ok(t, err) 206 - 207 - set := flag.NewSet() 208 - 209 - err = flag.AddToSet(set, f) 210 - test.Ok(t, err) 211 - 212 - return set 213 - }, 214 - args: []string{"-d "}, 215 - wantErr: true, 216 - errMsg: `invalid flag shorthand " ": cannot contain whitespace`, 217 - }, 218 - { 219 166 name: "bad syntax short more than 1 char equals", 220 167 newSet: func(t *testing.T) *flag.Set { 221 168 return flag.NewSet() ··· 223 170 args: []string{"-dfv=something"}, 224 171 wantErr: true, 225 172 errMsg: `unrecognised shorthand flag: "d" in -dfv=something`, 226 - }, 227 - { 228 - name: "bad syntax short non utf8", 229 - newSet: func(t *testing.T) *flag.Set { 230 - return flag.NewSet() 231 - }, 232 - args: []string{"-Ê"}, 233 - wantErr: true, 234 - errMsg: `invalid flag shorthand "Ê": invalid character, must be a single ASCII letter, got "Ê"`, 235 - }, 236 - { 237 - name: "bad syntax short non utf8 equals", 238 - newSet: func(t *testing.T) *flag.Set { 239 - return flag.NewSet() 240 - }, 241 - args: []string{"-Ê=something"}, 242 - wantErr: true, 243 - errMsg: `invalid flag shorthand "Ê": invalid character, must be a single ASCII letter, got "Ê"`, 244 - }, 245 - { 246 - name: "bad syntax short multiple non utf8", 247 - newSet: func(t *testing.T) *flag.Set { 248 - return flag.NewSet() 249 - }, 250 - args: []string{"-本¼語"}, 251 - wantErr: true, 252 - errMsg: `invalid flag shorthand "本": invalid character, must be a single ASCII letter, got "本"`, 253 - }, 254 - { 255 - name: "bad syntax long internal whitespace", 256 - newSet: func(t *testing.T) *flag.Set { 257 - return flag.NewSet() 258 - }, 259 - args: []string{"--de lete"}, 260 - wantErr: true, 261 - errMsg: `invalid flag name "de lete": cannot contain whitespace`, 262 173 }, 263 174 { 264 175 name: "valid long",
+48
internal/kind/kind.go
··· 1 + // Package kind defines a compact type tag identifying the underlying 2 + // concrete type of a Flag or Arg value. 3 + // 4 + // It exists so that hot paths do not have to do type switching which 5 + // boosts performance and cuts allocations. 6 + package kind 7 + 8 + // Kind identifies the underlying concrete type of a Flag or Arg value. 9 + type Kind uint8 10 + 11 + // Concrete kinds for every type in the public flag.Flaggable / arg.Argable 12 + // constraints. 13 + const ( 14 + Invalid Kind = iota 15 + Int 16 + Int8 17 + Int16 18 + Int32 19 + Int64 20 + Uint 21 + Uint8 22 + Uint16 23 + Uint32 24 + Uint64 25 + Uintptr 26 + Float32 27 + Float64 28 + String 29 + Bool 30 + BytesHex 31 + Count 32 + Time 33 + Duration 34 + IP 35 + URL 36 + IntSlice 37 + Int8Slice 38 + Int16Slice 39 + Int32Slice 40 + Int64Slice 41 + UintSlice 42 + Uint16Slice 43 + Uint32Slice 44 + Uint64Slice 45 + Float32Slice 46 + Float64Slice 47 + StringSlice 48 + )