Something went wrong. Try again.
Simple, intuitive CLI framework for Go pkg.go.dev/go.followtheprocess.codes/cli
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216// Package parse provides functions to parse strings into Go types and produce// detailed, consistent errors.//// It is used across both internal/flag and internal/arg to provide consistency.package parse
import ( "errors" "fmt" "strconv" "unsafe")
// Kind is the kind of parsing being done, either argument or flag.type Kind string
// Err is a generic parse error.//// Errors returned from the [Error] function will match this in a call// to [errors.Is].var Err = errors.New("parse error")
const ( // KindArgument is the [Kind] used for argument parsing. KindArgument Kind = "argument"
// KindFlag is the [Kind] used for flag parsing. KindFlag Kind = "flag")
const ( bits8 = 8 << iota // 8 bit integer bits16 // 16 bit integer bits32 // 32 bit integer bits64 // 64 bit integer)
const base10 = 10
// Error produces a formatted parse error.//// The kind must be [KindArgument] or [KindFlag], with name and str being the// name of the arg/flag and the invalid text that triggered the error.//// The type T is the type we were parsing str into and err is any underlying// error e.g. from strconv.//// // Make a flag parse error// var force bool// return parse.Error(parse.KindFlag, "force", "faklse", force, strconv.ErrSyntax)func Error[T any](kind Kind, name, str string, typ T, err error) error { // Ordinarily I wouldn't have a package like this concern itself with // details of other packages (like flag/arg) but given this package exists to produce consistent // behaviour and clear error messages in the narrow context of this cli framework, then // it makes sense the error is defined here too. return fmt.Errorf("%w: %s %q received invalid value %q (expected %T): %w", Err, kind, name, str, typ, err)}
// ErrorSlice produces a formatted parse error for a slice type.//// The kind should must be [KindArgument] or [KindFlag], with name and str being the// name of the arg/flag and the invalid text that triggered the error.//// The type T is the type we were parsing str into and err is any underlying// error e.g. from strconv.func ErrorSlice[T any](kind Kind, name, str string, typ T, err error) error { return fmt.Errorf("%w: %s %q (type %T) cannot append element %q: %w", Err, kind, name, typ, str, err)}
// Int parses an int from a string.func Int(str string) (int, error) { val, err := strconv.ParseInt(str, base10, bits64) if err != nil { return 0, err }
return int(val), nil}
// Int8 parses an int8 from a string.func Int8(str string) (int8, error) { val, err := strconv.ParseInt(str, base10, bits8) if err != nil { return 0, err }
return int8(val), nil}
// Int16 parses an int16 from a string.func Int16(str string) (int16, error) { val, err := strconv.ParseInt(str, base10, bits16) if err != nil { return 0, err }
return int16(val), nil}
// Int32 parses an int32 from a string.func Int32(str string) (int32, error) { val, err := strconv.ParseInt(str, base10, bits32) if err != nil { return 0, err }
return int32(val), nil}
// Int64 parses an int64 from a string.func Int64(str string) (int64, error) { val, err := strconv.ParseInt(str, base10, bits64) if err != nil { return 0, err }
return val, nil}
// Uint parses a uint from a string.func Uint(str string) (uint, error) { val, err := strconv.ParseUint(str, base10, 0) if err != nil { return 0, err }
return uint(val), nil}
// Uint8 parses an uint8 from a string.func Uint8(str string) (uint8, error) { val, err := strconv.ParseUint(str, base10, bits8) if err != nil { return 0, err }
return uint8(val), nil}
// Uint16 parses an uint16 from a string.func Uint16(str string) (uint16, error) { val, err := strconv.ParseUint(str, base10, bits16) if err != nil { return 0, err }
return uint16(val), nil}
// Uint32 parses an uint32 from a string.func Uint32(str string) (uint32, error) { val, err := strconv.ParseUint(str, base10, bits32) if err != nil { return 0, err }
return uint32(val), nil}
// Uint64 parses an uint64 from a string.func Uint64(str string) (uint64, error) { val, err := strconv.ParseUint(str, base10, bits64) if err != nil { return 0, err }
return val, nil}
// Float32 parses a float32 from a string.func Float32(str string) (float32, error) { val, err := strconv.ParseFloat(str, bits32) if err != nil { return 0, err }
return float32(val), nil}
// Float64 parses a float64 from a string.func Float64(str string) (float64, error) { val, err := strconv.ParseFloat(str, bits64) if err != nil { return 0, err }
return float64(val), nil}
// Cast converts a *T1 to a *T2, we use it here when we know (via generics and compile time checks)// that e.g. the Flag.value is a string, but we can't directly do Flag.value = "value" because// we can't assign a string to a generic 'T', but we *know* that the value *is* a string because when// instantiating a Flag[T], you have to provide (or compiler has to infer) Flag[string].//// # Safety//// Cast uses [unsafe.Pointer] to reinterpret *T1 as *T2. The type parameters are// both [any]. The compiler does not enforce that T1 and T2 are layout-compatible,// so callers are fully responsible for that invariant.//// All current call sites follow the same pattern: within a type switch on// any(*f.value) (where f.value is *T for some [flag.Flaggable] T), the matched// case has already proven the dynamic type, so *T and *Matched point to// bit-identical memory. The unsafe.Pointer round-trip is a no-op at runtime, it// exists solely because Go does not let us assign across generic type parameters// without going through [any], which would allocate.//// This matches the [unsafe.Pointer] rule "(1) Conversion of a *T1 to Pointer to// *T2": T2 is no larger than T1 and shares an equivalent memory layout.//// If a new call site is added, verify the type-switch invariant holds. Cast// outside that pattern is undefined behaviour.func Cast[T2, T1 any](v *T1) *T2 { return (*T2)(unsafe.Pointer(v))}