⬆️ Update dependencies

This commit is contained in:
2026-11-01 15:12:52 +01:00
parent fd0d7ddee6
commit 312884dd59
501 changed files with 98424 additions and 35494 deletions
+328 -90
View File
@@ -3,8 +3,9 @@ package kong
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"math/bits"
"net/url"
"os"
@@ -12,13 +13,12 @@ import (
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
var (
mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem()
boolMapperType = reflect.TypeOf((*BoolMapper)(nil)).Elem()
boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem()
jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
)
@@ -42,19 +42,26 @@ func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
}
// MapperValue may be implemented by fields in order to provide custom mapping.
// Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text.
type MapperValue interface {
Decode(ctx *DecodeContext) error
}
// BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values.
type BoolMapperValue interface {
MapperValue
IsBool() bool
}
type mapperValueAdapter struct {
isBool bool
}
func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
if target.Type().Implements(mapperValueType) {
return target.Interface().(MapperValue).Decode(ctx)
return target.Interface().(MapperValue).Decode(ctx) //nolint
}
return target.Addr().Interface().(MapperValue).Decode(ctx)
return target.Addr().Interface().(MapperValue).Decode(ctx) //nolint
}
func (m *mapperValueAdapter) IsBool() bool {
@@ -70,9 +77,9 @@ func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value
return err
}
if target.Type().Implements(textUnmarshalerType) {
return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
type binaryUnmarshalerAdapter struct{}
@@ -83,33 +90,59 @@ func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Val
if err != nil {
return err
}
if target.Type().Implements(textUnmarshalerType) {
return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
if target.Type().Implements(binaryUnmarshalerType) {
return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
type jsonUnmarshalerAdapter struct{}
func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
var value string
err := ctx.Scan.PopValueInto("value", &value)
if err != nil {
return err
}
if target.Type().Implements(jsonUnmarshalerType) {
return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
// A Mapper represents how a field is mapped from command-line values to Go.
//
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
//
// Additionally, if a type implements the MappverValue interface, it will be used.
// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
// Decode ctx.Value with ctx.Scanner into target.
Decode(ctx *DecodeContext, target reflect.Value) error
}
// VarsContributor can be implemented by a Mapper to contribute Vars during interpolation.
type VarsContributor interface {
Vars(ctx *Value) Vars
}
// A BoolMapper is a Mapper to a value that is a boolean.
//
// This is used solely for formatting help.
type BoolMapper interface {
Mapper
IsBool() bool
}
// BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean.
type BoolMapperExt interface {
Mapper
IsBoolFromValue(v reflect.Value) bool
}
// A MapperFunc is a single function that complies with the Mapper interface.
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { // nolint: golint
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: revive
return m(ctx, target)
}
@@ -166,7 +199,8 @@ func (r *Registry) ForType(typ reflect.Type) Mapper {
// Check if the type implements MapperValue.
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
if impl.Implements(mapperValueType) {
return &mapperValueAdapter{impl.Implements(boolMapperType)}
// FIXME: This should pass in the bool mapper.
return &mapperValueAdapter{impl.Implements(boolMapperValueType)}
}
}
// Next, try explicitly registered types.
@@ -177,10 +211,13 @@ func (r *Registry) ForType(typ reflect.Type) Mapper {
}
// Next try stdlib unmarshaler interfaces.
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
if impl.Implements(textUnmarshalerType) {
switch {
case impl.Implements(textUnmarshalerType):
return &textUnmarshalerAdapter{}
} else if impl.Implements(binaryUnmarshalerType) {
case impl.Implements(binaryUnmarshalerType):
return &binaryUnmarshalerAdapter{}
case impl.Implements(jsonUnmarshalerType):
return &jsonUnmarshalerAdapter{}
}
}
// Finally try registered kinds.
@@ -200,8 +237,8 @@ func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
//
// eg.
//
// Mapper string `kong:"type='colour'`
// registry.RegisterName("colour", ...)
// Mapper string `kong:"type='colour'`
// registry.RegisterName("colour", ...)
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
r.names[name] = mapper
return r
@@ -214,7 +251,7 @@ func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
}
// RegisterValue registers a Mapper by pointer to the field value.
func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
func (r *Registry) RegisterValue(ptr any, mapper Mapper) *Registry {
key := reflect.ValueOf(ptr)
if key.Kind() != reflect.Ptr {
panic("expected a pointer")
@@ -231,16 +268,15 @@ func (r *Registry) RegisterDefaults() *Registry {
RegisterKind(reflect.Int16, intDecoder(16)).
RegisterKind(reflect.Int32, intDecoder(32)).
RegisterKind(reflect.Int64, intDecoder(64)).
RegisterKind(reflect.Uint, uintDecoder(64)).
RegisterKind(reflect.Uint8, uintDecoder(bits.UintSize)).
RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
RegisterKind(reflect.Uint8, uintDecoder(8)).
RegisterKind(reflect.Uint16, uintDecoder(16)).
RegisterKind(reflect.Uint32, uintDecoder(32)).
RegisterKind(reflect.Uint64, uintDecoder(64)).
RegisterKind(reflect.Float32, floatDecoder(32)).
RegisterKind(reflect.Float64, floatDecoder(64)).
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
err := ctx.Scan.PopValueInto("string", target.Addr().Interface())
return err
return ctx.Scan.PopValueInto("string", target.Addr().Interface())
})).
RegisterKind(reflect.Bool, boolMapper{}).
RegisterKind(reflect.Slice, sliceDecoder(r)).
@@ -248,10 +284,13 @@ func (r *Registry) RegisterDefaults() *Registry {
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
RegisterName("path", pathMapper(r)).
RegisterName("existingfile", existingFileMapper(r)).
RegisterName("existingdir", existingDirMapper(r)).
RegisterName("counter", counterMapper())
RegisterName("counter", counterMapper()).
RegisterName("filecontent", fileContentMapper(r)).
RegisterKind(reflect.Ptr, ptrMapper{r})
}
type boolMapper struct{}
@@ -270,14 +309,14 @@ func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
target.SetBool(false)
default:
return errors.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
}
case bool:
target.SetBool(v)
default:
return errors.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
}
} else {
target.SetBool(true)
@@ -288,15 +327,23 @@ func (boolMapper) IsBool() bool { return true }
func durationDecoder() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
var value string
if err := ctx.Scan.PopValueInto("duration", &value); err != nil {
t, err := ctx.Scan.PopValue("duration")
if err != nil {
return err
}
r, err := time.ParseDuration(value)
if err != nil {
return errors.Errorf("expected duration but got %q: %s", value, err)
var d time.Duration
switch v := t.Value.(type) {
case string:
d, err = time.ParseDuration(v)
if err != nil {
return fmt.Errorf("expected duration but got %q: %v", v, err)
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) //nolint: forcetypeassert
default:
return fmt.Errorf("expected duration but got %q", v)
}
target.Set(reflect.ValueOf(r))
target.Set(reflect.ValueOf(d))
return nil
}
}
@@ -320,7 +367,7 @@ func timeDecoder() MapperFunc {
}
}
func intDecoder(bits int) MapperFunc { // nolint: dupl
func intDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("int")
if err != nil {
@@ -331,22 +378,25 @@ func intDecoder(bits int) MapperFunc { // nolint: dupl
case string:
sv = v
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
case float32, float64:
sv = fmt.Sprintf("%0.f", v)
default:
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseInt(sv, 10, bits)
n, err := strconv.ParseInt(sv, 0, bits)
if err != nil {
return errors.Errorf("expected a valid %d bit int but got %q", bits, sv)
return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv)
}
target.SetInt(n)
return nil
}
}
func uintDecoder(bits int) MapperFunc { // nolint: dupl
func uintDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("uint")
if err != nil {
@@ -357,15 +407,18 @@ func uintDecoder(bits int) MapperFunc { // nolint: dupl
case string:
sv = v
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
case float32, float64:
sv = fmt.Sprintf("%0.f", v)
default:
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseUint(sv, 10, bits)
n, err := strconv.ParseUint(sv, 0, bits)
if err != nil {
return errors.Errorf("expected a valid %d bit uint but got %q", bits, sv)
return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv)
}
target.SetUint(n)
return nil
@@ -382,7 +435,7 @@ func floatDecoder(bits int) MapperFunc {
case string:
n, err := strconv.ParseFloat(v, bits)
if err != nil {
return errors.Errorf("expected a float but got %q (%T)", t, t.Value)
return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
}
target.SetFloat(n)
@@ -393,10 +446,10 @@ func floatDecoder(bits int) MapperFunc {
target.SetFloat(v)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
target.Set(reflect.ValueOf(v))
target.SetFloat(reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0))).Float())
default:
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
}
return nil
}
@@ -408,32 +461,32 @@ func mapDecoder(r *Registry) MapperFunc {
target.Set(reflect.MakeMap(target.Type()))
}
el := target.Type()
sep := ctx.Value.Tag.MapSep
mapsep := ctx.Value.Tag.MapSep
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
// If decoding a flag, we need an argument.
// If decoding a flag, we need an value.
if t.IsEOL() {
return errors.Errorf("unexpected EOL")
return fmt.Errorf("missing value, expecting \"<key>=<value>%c...\"", mapsep)
}
switch v := t.Value.(type) {
case string:
childScanner = Scan(SplitEscaped(v, sep)...)
childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
case []map[string]interface{}:
case []map[string]any:
for _, m := range v {
err := jsonTranscode(m, target.Addr().Interface())
if err != nil {
return errors.WithStack(err)
return err
}
}
return nil
case map[string]interface{}:
case map[string]any:
return jsonTranscode(v, target.Addr().Interface())
default:
return errors.Errorf("invalid map value %q (of type %T)", t, t.Value)
return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value)
}
} else {
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
@@ -447,7 +500,7 @@ func mapDecoder(r *Registry) MapperFunc {
}
parts := strings.SplitN(token, "=", 2)
if len(parts) != 2 {
return errors.Errorf("expected \"<key>=<value>\" but got %q", token)
return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
}
key, value := parts[0], parts[1]
@@ -455,23 +508,23 @@ func mapDecoder(r *Registry) MapperFunc {
if typ := ctx.Value.Tag.Type; typ != "" {
parts := strings.Split(typ, ":")
if len(parts) != 2 {
return errors.Errorf("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
return errors.New("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
}
keyTypeName, valueTypeName = parts[0], parts[1]
}
keyScanner := Scan(key)
keyScanner := ScanAsType(FlagValueToken, key)
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
keyValue := reflect.New(el.Key()).Elem()
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
return errors.Errorf("invalid map key %q", key)
return fmt.Errorf("invalid map key %q", key)
}
valueScanner := Scan(value)
valueScanner := ScanAsType(FlagValueToken, value)
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
valueValue := reflect.New(el.Elem()).Elem()
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
return errors.Errorf("invalid map value %q", value)
return fmt.Errorf("invalid map value %q", value)
}
target.SetMapIndex(keyValue, valueValue)
@@ -487,19 +540,23 @@ func sliceDecoder(r *Registry) MapperFunc {
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
// If decoding a flag, we need an argument.
// If decoding a flag, we need a value.
tail := ""
if sep != -1 {
tail += string(sep) + "..."
}
if t.IsEOL() {
return errors.Errorf("unexpected EOL")
return fmt.Errorf("missing value, expecting \"<arg>%s\"", tail)
}
switch v := t.Value.(type) {
case string:
childScanner = Scan(SplitEscaped(v, sep)...)
childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
case []interface{}:
case []any:
return jsonTranscode(v, target.Addr().Interface())
default:
v = []interface{}{v}
v = []any{v}
return jsonTranscode(v, target.Addr().Interface())
}
} else {
@@ -508,13 +565,13 @@ func sliceDecoder(r *Registry) MapperFunc {
}
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
if childDecoder == nil {
return errors.Errorf("no mapper for element type of %s", target.Type())
return fmt.Errorf("no mapper for element type of %s", target.Type())
}
for !childScanner.Peek().IsEOL() {
childValue := reflect.New(el).Elem()
err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
if err != nil {
return errors.WithStack(err)
return err
}
target.Set(reflect.Append(target, childValue))
}
@@ -527,33 +584,88 @@ func pathMapper(r *Registry) MapperFunc {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
originalTarget := target
if target.Kind() == reflect.Ptr && target.Elem().Kind() == reflect.String {
if target.IsNil() {
return nil
}
target = target.Elem()
}
if target.Kind() != reflect.String {
return errors.Errorf("\"path\" type must be applied to a string not %s", target.Type())
return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
path = ExpandPath(path)
// Skip if path with default is explicitly set to "". For the current directory use ".".
if ctx.Value.HasDefault && path == "" {
if originalTarget.Kind() == reflect.Ptr {
originalTarget.Set(reflect.Zero(originalTarget.Type()))
}
return nil
}
if path != "-" {
path = ExpandPath(path)
}
target.SetString(path)
return nil
}
}
func fileMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
// Skip if value was already set to avoid opening the default file when an explicit value was provided
if ctx.Value.HasDefault && ctx.Value.Set {
return nil
}
var file *os.File
if path == "-" {
file = os.Stdin
} else {
path = ExpandPath(path)
file, err = os.Open(path) //nolint: gosec
if err != nil {
return err
}
}
target.Set(reflect.ValueOf(file))
return nil
}
}
func existingFileMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
return errors.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
// early return to avoid checking extra files that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
if path != "-" {
path = ExpandPath(path)
stat, err := os.Stat(path)
@@ -561,7 +673,7 @@ func existingFileMapper(r *Registry) MapperFunc {
return err
}
if stat.IsDir() {
return errors.Errorf("%q exists but is a directory", path)
return fmt.Errorf("%q exists but is a directory", path)
}
}
target.SetString(path)
@@ -575,26 +687,106 @@ func existingDirMapper(r *Registry) MapperFunc {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
return errors.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
// early return to avoid checking extra dirs that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
path = ExpandPath(path)
stat, err := os.Stat(path)
if err != nil {
return err
}
if !stat.IsDir() {
return errors.Errorf("%q exists but is not a directory", path)
return fmt.Errorf("%q exists but is not a directory", path)
}
target.SetString(path)
return nil
}
}
func fileContentMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() != reflect.Slice || target.Type().Elem().Kind() != reflect.Uint8 {
return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || ctx.Value.Set {
// early return to avoid checking extra dirs that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
var data []byte
if path != "-" {
path = ExpandPath(path)
data, err = os.ReadFile(path) //nolint:gosec
} else {
data, err = io.ReadAll(os.Stdin)
}
if err != nil {
if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
return fmt.Errorf("%q exists but is a directory: %w", path, err)
}
return err
}
target.SetBytes(data)
return nil
}
}
type ptrMapper struct {
r *Registry
}
var _ BoolMapperExt = (*ptrMapper)(nil)
// IsBoolFromValue implements BoolMapperExt
func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool {
elem := reflect.New(target.Type().Elem()).Elem()
nestedMapper := p.r.ForValue(elem)
if nestedMapper == nil {
return false
}
if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() {
return true
}
if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) {
return true
}
return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool
}
func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
elem := reflect.New(target.Type().Elem()).Elem()
nestedMapper := p.r.ForValue(elem)
if nestedMapper == nil {
return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
}
err := nestedMapper.Decode(ctx, elem)
if err != nil {
return err
}
target.Set(elem.Addr())
return nil
}
func counterMapper() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if ctx.Scan.Peek().Type == FlagValueToken {
@@ -602,19 +794,32 @@ func counterMapper() MapperFunc {
if err != nil {
return err
}
var n int64
switch v := t.Value.(type) {
case string:
n, err := strconv.ParseInt(v, 10, 64)
n, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
target.SetInt(n)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
target.Set(reflect.ValueOf(v))
n = reflect.ValueOf(v).Convert(reflect.TypeOf(int64(0))).Int()
default:
return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
// Assign by the target's kind, like the increment path below, so a
// counter declared as a uint or float field doesn't panic.
switch target.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
target.SetInt(n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
target.SetUint(uint64(n)) //nolint:gosec // a counter value is small and non-negative
case reflect.Float32, reflect.Float64:
target.SetFloat(float64(n))
default:
return fmt.Errorf("type:\"counter\" must be used with a numeric field")
}
return nil
}
@@ -630,7 +835,7 @@ func counterMapper() MapperFunc {
target.SetFloat(target.Float() + 1)
default:
return errors.Errorf("type:\"counter\" must be used with a numeric field")
return fmt.Errorf("type:\"counter\" must be used with a numeric field")
}
return nil
}
@@ -645,7 +850,7 @@ func urlMapper() MapperFunc {
}
url, err := url.Parse(urlStr)
if err != nil {
return errors.WithStack(err)
return err
}
target.Set(reflect.ValueOf(url))
return nil
@@ -656,19 +861,22 @@ func urlMapper() MapperFunc {
//
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
//
// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
func SplitEscaped(s string, sep rune) (out []string) {
if sep == -1 {
return []string{s}
}
escaped := false
token := ""
for _, ch := range s {
for i, ch := range s {
switch {
case escaped:
if ch != sep {
token += `\`
}
token += string(ch)
escaped = false
case ch == '\\':
case ch == '\\' && i < len(s)-1:
escaped = true
case ch == sep && !escaped:
out = append(out, token)
@@ -686,19 +894,46 @@ func SplitEscaped(s string, sep rune) (out []string) {
// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
//
// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
func JoinEscaped(s []string, sep rune) string {
escaped := []string{}
for _, e := range s {
escaped = append(escaped, strings.Replace(e, string(sep), `\`+string(sep), -1))
escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep)))
}
return strings.Join(escaped, string(sep))
}
// NamedFileContentFlag is a flag value that loads a file's contents and filename into its value.
type NamedFileContentFlag struct {
Filename string
Contents []byte
}
func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
return err
}
// This allows unsetting of file content flags.
if filename == "" {
*f = NamedFileContentFlag{}
return nil
}
filename = ExpandPath(filename)
data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
return fmt.Errorf("failed to open %q: %v", filename, err)
}
f.Contents = data
f.Filename = filename
return nil
}
// FileContentFlag is a flag value that loads a file's contents into its value.
type FileContentFlag []byte
func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
@@ -710,18 +945,21 @@ func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
return nil
}
filename = ExpandPath(filename)
data, err := ioutil.ReadFile(filename) // nolint: gosec
data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
return errors.Errorf("failed to open %q: %s", filename, err)
return fmt.Errorf("failed to open %q: %v", filename, err)
}
*f = data
return nil
}
func jsonTranscode(in, out interface{}) error {
func jsonTranscode(in, out any) error {
data, err := json.Marshal(in)
if err != nil {
return errors.WithStack(err)
return err
}
return errors.Wrapf(json.Unmarshal(data, out), "%#v -> %T", in, out)
if err = json.Unmarshal(data, out); err != nil {
return fmt.Errorf("%#v -> %T: %v", in, out, err)
}
return nil
}