github.com/maresnic/mr-kong@v1.0.0/mapper.go (about)

     1  package kong
     2  
     3  import (
     4  	"encoding"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"math/bits"
    10  	"net/url"
    11  	"os"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  var (
    19  	mapperValueType       = reflect.TypeOf((*MapperValue)(nil)).Elem()
    20  	boolMapperValueType   = reflect.TypeOf((*BoolMapperValue)(nil)).Elem()
    21  	jsonUnmarshalerType   = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
    22  	textUnmarshalerType   = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
    23  	binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
    24  )
    25  
    26  // DecodeContext is passed to a Mapper's Decode().
    27  //
    28  // It contains the Value being decoded into and the Scanner to parse from.
    29  type DecodeContext struct {
    30  	// Value being decoded into.
    31  	Value *Value
    32  	// Scan contains the input to scan into Target.
    33  	Scan *Scanner
    34  }
    35  
    36  // WithScanner creates a clone of this context with a new Scanner.
    37  func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
    38  	return &DecodeContext{
    39  		Value: r.Value,
    40  		Scan:  scan,
    41  	}
    42  }
    43  
    44  // MapperValue may be implemented by fields in order to provide custom mapping.
    45  // Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text.
    46  type MapperValue interface {
    47  	Decode(ctx *DecodeContext) error
    48  }
    49  
    50  // BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values.
    51  type BoolMapperValue interface {
    52  	MapperValue
    53  	IsBool() bool
    54  }
    55  
    56  type mapperValueAdapter struct {
    57  	isBool bool
    58  }
    59  
    60  func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
    61  	if target.Type().Implements(mapperValueType) {
    62  		return target.Interface().(MapperValue).Decode(ctx) //nolint
    63  	}
    64  	return target.Addr().Interface().(MapperValue).Decode(ctx) //nolint
    65  }
    66  
    67  func (m *mapperValueAdapter) IsBool() bool {
    68  	return m.isBool
    69  }
    70  
    71  type textUnmarshalerAdapter struct{}
    72  
    73  func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
    74  	var value string
    75  	err := ctx.Scan.PopValueInto("value", &value)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	if target.Type().Implements(textUnmarshalerType) {
    80  		return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
    81  	}
    82  	return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
    83  }
    84  
    85  type binaryUnmarshalerAdapter struct{}
    86  
    87  func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
    88  	var value string
    89  	err := ctx.Scan.PopValueInto("value", &value)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if target.Type().Implements(binaryUnmarshalerType) {
    94  		return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
    95  	}
    96  	return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
    97  }
    98  
    99  type jsonUnmarshalerAdapter struct{}
   100  
   101  func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
   102  	var value string
   103  	err := ctx.Scan.PopValueInto("value", &value)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if target.Type().Implements(jsonUnmarshalerType) {
   108  		return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
   109  	}
   110  	return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
   111  }
   112  
   113  // A Mapper represents how a field is mapped from command-line values to Go.
   114  //
   115  // Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
   116  //
   117  // Additionally, if a type implements the MapperValue interface, it will be used.
   118  type Mapper interface {
   119  	// Decode ctx.Value with ctx.Scanner into target.
   120  	Decode(ctx *DecodeContext, target reflect.Value) error
   121  }
   122  
   123  // VarsContributor can be implemented by a Mapper to contribute Vars during interpolation.
   124  type VarsContributor interface {
   125  	Vars(ctx *Value) Vars
   126  }
   127  
   128  // A BoolMapper is a Mapper to a value that is a boolean.
   129  //
   130  // This is used solely for formatting help.
   131  type BoolMapper interface {
   132  	Mapper
   133  	IsBool() bool
   134  }
   135  
   136  // BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean.
   137  type BoolMapperExt interface {
   138  	Mapper
   139  	IsBoolFromValue(v reflect.Value) bool
   140  }
   141  
   142  // A MapperFunc is a single function that complies with the Mapper interface.
   143  type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
   144  
   145  func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: revive
   146  	return m(ctx, target)
   147  }
   148  
   149  // A Registry contains a set of mappers and supporting lookup methods.
   150  type Registry struct {
   151  	names  map[string]Mapper
   152  	types  map[reflect.Type]Mapper
   153  	kinds  map[reflect.Kind]Mapper
   154  	values map[reflect.Value]Mapper
   155  }
   156  
   157  // NewRegistry creates a new (empty) Registry.
   158  func NewRegistry() *Registry {
   159  	return &Registry{
   160  		names:  map[string]Mapper{},
   161  		types:  map[reflect.Type]Mapper{},
   162  		kinds:  map[reflect.Kind]Mapper{},
   163  		values: map[reflect.Value]Mapper{},
   164  	}
   165  }
   166  
   167  // ForNamedValue finds a mapper for a value with a user-specified name.
   168  //
   169  // Will return nil if a mapper can not be determined.
   170  func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper {
   171  	if mapper, ok := r.names[name]; ok {
   172  		return mapper
   173  	}
   174  	return r.ForValue(value)
   175  }
   176  
   177  // ForValue looks up the Mapper for a reflect.Value.
   178  func (r *Registry) ForValue(value reflect.Value) Mapper {
   179  	if mapper, ok := r.values[value]; ok {
   180  		return mapper
   181  	}
   182  	return r.ForType(value.Type())
   183  }
   184  
   185  // ForNamedType finds a mapper for a type with a user-specified name.
   186  //
   187  // Will return nil if a mapper can not be determined.
   188  func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper {
   189  	if mapper, ok := r.names[name]; ok {
   190  		return mapper
   191  	}
   192  	return r.ForType(typ)
   193  }
   194  
   195  // ForType finds a mapper from a type, by type, then kind.
   196  //
   197  // Will return nil if a mapper can not be determined.
   198  func (r *Registry) ForType(typ reflect.Type) Mapper {
   199  	// Check if the type implements MapperValue.
   200  	for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
   201  		if impl.Implements(mapperValueType) {
   202  			// FIXME: This should pass in the bool mapper.
   203  			return &mapperValueAdapter{impl.Implements(boolMapperValueType)}
   204  		}
   205  	}
   206  	// Next, try explicitly registered types.
   207  	var mapper Mapper
   208  	var ok bool
   209  	if mapper, ok = r.types[typ]; ok {
   210  		return mapper
   211  	}
   212  	// Next try stdlib unmarshaler interfaces.
   213  	for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
   214  		switch {
   215  		case impl.Implements(textUnmarshalerType):
   216  			return &textUnmarshalerAdapter{}
   217  		case impl.Implements(binaryUnmarshalerType):
   218  			return &binaryUnmarshalerAdapter{}
   219  		case impl.Implements(jsonUnmarshalerType):
   220  			return &jsonUnmarshalerAdapter{}
   221  		}
   222  	}
   223  	// Finally try registered kinds.
   224  	if mapper, ok = r.kinds[typ.Kind()]; ok {
   225  		return mapper
   226  	}
   227  	return nil
   228  }
   229  
   230  // RegisterKind registers a Mapper for a reflect.Kind.
   231  func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
   232  	r.kinds[kind] = mapper
   233  	return r
   234  }
   235  
   236  // RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name.
   237  //
   238  // eg.
   239  //
   240  //			Mapper string `kong:"type='colour'`
   241  //	  	registry.RegisterName("colour", ...)
   242  func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
   243  	r.names[name] = mapper
   244  	return r
   245  }
   246  
   247  // RegisterType registers a Mapper for a reflect.Type.
   248  func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
   249  	r.types[typ] = mapper
   250  	return r
   251  }
   252  
   253  // RegisterValue registers a Mapper by pointer to the field value.
   254  func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
   255  	key := reflect.ValueOf(ptr)
   256  	if key.Kind() != reflect.Ptr {
   257  		panic("expected a pointer")
   258  	}
   259  	key = key.Elem()
   260  	r.values[key] = mapper
   261  	return r
   262  }
   263  
   264  // RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
   265  func (r *Registry) RegisterDefaults() *Registry {
   266  	return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
   267  		RegisterKind(reflect.Int8, intDecoder(8)).
   268  		RegisterKind(reflect.Int16, intDecoder(16)).
   269  		RegisterKind(reflect.Int32, intDecoder(32)).
   270  		RegisterKind(reflect.Int64, intDecoder(64)).
   271  		RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
   272  		RegisterKind(reflect.Uint8, uintDecoder(8)).
   273  		RegisterKind(reflect.Uint16, uintDecoder(16)).
   274  		RegisterKind(reflect.Uint32, uintDecoder(32)).
   275  		RegisterKind(reflect.Uint64, uintDecoder(64)).
   276  		RegisterKind(reflect.Float32, floatDecoder(32)).
   277  		RegisterKind(reflect.Float64, floatDecoder(64)).
   278  		RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
   279  			return ctx.Scan.PopValueInto("string", target.Addr().Interface())
   280  		})).
   281  		RegisterKind(reflect.Bool, boolMapper{}).
   282  		RegisterKind(reflect.Slice, sliceDecoder(r)).
   283  		RegisterKind(reflect.Map, mapDecoder(r)).
   284  		RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
   285  		RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
   286  		RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
   287  		RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
   288  		RegisterName("path", pathMapper(r)).
   289  		RegisterName("existingfile", existingFileMapper(r)).
   290  		RegisterName("existingdir", existingDirMapper(r)).
   291  		RegisterName("counter", counterMapper()).
   292  		RegisterName("filecontent", fileContentMapper(r)).
   293  		RegisterKind(reflect.Ptr, ptrMapper{r})
   294  }
   295  
   296  type boolMapper struct{}
   297  
   298  func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
   299  	if ctx.Scan.Peek().Type == FlagValueToken {
   300  		token := ctx.Scan.Pop()
   301  		switch v := token.Value.(type) {
   302  		case string:
   303  			v = strings.ToLower(v)
   304  			switch v {
   305  			case "true", "1", "yes":
   306  				target.SetBool(true)
   307  
   308  			case "false", "0", "no":
   309  				target.SetBool(false)
   310  
   311  			default:
   312  				return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
   313  			}
   314  
   315  		case bool:
   316  			target.SetBool(v)
   317  
   318  		default:
   319  			return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
   320  		}
   321  	} else {
   322  		target.SetBool(true)
   323  	}
   324  	return nil
   325  }
   326  func (boolMapper) IsBool() bool { return true }
   327  
   328  func durationDecoder() MapperFunc {
   329  	return func(ctx *DecodeContext, target reflect.Value) error {
   330  		t, err := ctx.Scan.PopValue("duration")
   331  		if err != nil {
   332  			return err
   333  		}
   334  		var d time.Duration
   335  		switch v := t.Value.(type) {
   336  		case string:
   337  			d, err = time.ParseDuration(v)
   338  			if err != nil {
   339  				return fmt.Errorf("expected duration but got %q: %v", v, err)
   340  			}
   341  		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
   342  			d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) //nolint: forcetypeassert
   343  		default:
   344  			return fmt.Errorf("expected duration but got %q", v)
   345  		}
   346  		target.Set(reflect.ValueOf(d))
   347  		return nil
   348  	}
   349  }
   350  
   351  func timeDecoder() MapperFunc {
   352  	return func(ctx *DecodeContext, target reflect.Value) error {
   353  		format := time.RFC3339
   354  		if ctx.Value.Format != "" {
   355  			format = ctx.Value.Format
   356  		}
   357  		var value string
   358  		if err := ctx.Scan.PopValueInto("time", &value); err != nil {
   359  			return err
   360  		}
   361  		t, err := time.Parse(format, value)
   362  		if err != nil {
   363  			return err
   364  		}
   365  		target.Set(reflect.ValueOf(t))
   366  		return nil
   367  	}
   368  }
   369  
   370  func intDecoder(bits int) MapperFunc { //nolint: dupl
   371  	return func(ctx *DecodeContext, target reflect.Value) error {
   372  		t, err := ctx.Scan.PopValue("int")
   373  		if err != nil {
   374  			return err
   375  		}
   376  		var sv string
   377  		switch v := t.Value.(type) {
   378  		case string:
   379  			sv = v
   380  
   381  		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   382  			sv = fmt.Sprintf("%v", v)
   383  
   384  		case float32, float64:
   385  			sv = fmt.Sprintf("%0.f", v)
   386  
   387  		default:
   388  			return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
   389  		}
   390  		n, err := strconv.ParseInt(sv, 10, bits)
   391  		if err != nil {
   392  			return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv)
   393  		}
   394  		target.SetInt(n)
   395  		return nil
   396  	}
   397  }
   398  
   399  func uintDecoder(bits int) MapperFunc { //nolint: dupl
   400  	return func(ctx *DecodeContext, target reflect.Value) error {
   401  		t, err := ctx.Scan.PopValue("uint")
   402  		if err != nil {
   403  			return err
   404  		}
   405  		var sv string
   406  		switch v := t.Value.(type) {
   407  		case string:
   408  			sv = v
   409  
   410  		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   411  			sv = fmt.Sprintf("%v", v)
   412  
   413  		case float32, float64:
   414  			sv = fmt.Sprintf("%0.f", v)
   415  
   416  		default:
   417  			return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
   418  		}
   419  		n, err := strconv.ParseUint(sv, 10, bits)
   420  		if err != nil {
   421  			return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv)
   422  		}
   423  		target.SetUint(n)
   424  		return nil
   425  	}
   426  }
   427  
   428  func floatDecoder(bits int) MapperFunc {
   429  	return func(ctx *DecodeContext, target reflect.Value) error {
   430  		t, err := ctx.Scan.PopValue("float")
   431  		if err != nil {
   432  			return err
   433  		}
   434  		switch v := t.Value.(type) {
   435  		case string:
   436  			n, err := strconv.ParseFloat(v, bits)
   437  			if err != nil {
   438  				return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
   439  			}
   440  			target.SetFloat(n)
   441  
   442  		case float32:
   443  			target.SetFloat(float64(v))
   444  
   445  		case float64:
   446  			target.SetFloat(v)
   447  
   448  		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   449  			target.Set(reflect.ValueOf(v))
   450  
   451  		default:
   452  			return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
   453  		}
   454  		return nil
   455  	}
   456  }
   457  
   458  func mapDecoder(r *Registry) MapperFunc {
   459  	return func(ctx *DecodeContext, target reflect.Value) error {
   460  		if target.IsNil() {
   461  			target.Set(reflect.MakeMap(target.Type()))
   462  		}
   463  		el := target.Type()
   464  		mapsep := ctx.Value.Tag.MapSep
   465  		var childScanner *Scanner
   466  		if ctx.Value.Flag != nil {
   467  			t := ctx.Scan.Pop()
   468  			// If decoding a flag, we need an value.
   469  			if t.IsEOL() {
   470  				return fmt.Errorf("missing value, expecting \"<key>=<value>%c...\"", mapsep)
   471  			}
   472  			switch v := t.Value.(type) {
   473  			case string:
   474  				childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
   475  
   476  			case []map[string]interface{}:
   477  				for _, m := range v {
   478  					err := jsonTranscode(m, target.Addr().Interface())
   479  					if err != nil {
   480  						return err
   481  					}
   482  				}
   483  				return nil
   484  
   485  			case map[string]interface{}:
   486  				return jsonTranscode(v, target.Addr().Interface())
   487  
   488  			default:
   489  				return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value)
   490  			}
   491  		} else {
   492  			tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
   493  			childScanner = ScanFromTokens(tokens...)
   494  		}
   495  		for !childScanner.Peek().IsEOL() {
   496  			var token string
   497  			err := childScanner.PopValueInto("map", &token)
   498  			if err != nil {
   499  				return err
   500  			}
   501  			parts := strings.SplitN(token, "=", 2)
   502  			if len(parts) != 2 {
   503  				return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
   504  			}
   505  			key, value := parts[0], parts[1]
   506  
   507  			keyTypeName, valueTypeName := "", ""
   508  			if typ := ctx.Value.Tag.Type; typ != "" {
   509  				parts := strings.Split(typ, ":")
   510  				if len(parts) != 2 {
   511  					return errors.New("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
   512  				}
   513  				keyTypeName, valueTypeName = parts[0], parts[1]
   514  			}
   515  
   516  			keyScanner := ScanAsType(FlagValueToken, key)
   517  			keyDecoder := r.ForNamedType(keyTypeName, el.Key())
   518  			keyValue := reflect.New(el.Key()).Elem()
   519  			if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
   520  				return fmt.Errorf("invalid map key %q", key)
   521  			}
   522  
   523  			valueScanner := ScanAsType(FlagValueToken, value)
   524  			valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
   525  			valueValue := reflect.New(el.Elem()).Elem()
   526  			if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
   527  				return fmt.Errorf("invalid map value %q", value)
   528  			}
   529  
   530  			target.SetMapIndex(keyValue, valueValue)
   531  		}
   532  		return nil
   533  	}
   534  }
   535  
   536  func sliceDecoder(r *Registry) MapperFunc {
   537  	return func(ctx *DecodeContext, target reflect.Value) error {
   538  		el := target.Type().Elem()
   539  		sep := ctx.Value.Tag.Sep
   540  		var childScanner *Scanner
   541  		if ctx.Value.Flag != nil {
   542  			t := ctx.Scan.Pop()
   543  			// If decoding a flag, we need a value.
   544  			if t.IsEOL() {
   545  				return fmt.Errorf("missing value, expecting \"<arg>%c...\"", sep)
   546  			}
   547  			switch v := t.Value.(type) {
   548  			case string:
   549  				childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
   550  
   551  			case []interface{}:
   552  				return jsonTranscode(v, target.Addr().Interface())
   553  
   554  			default:
   555  				v = []interface{}{v}
   556  				return jsonTranscode(v, target.Addr().Interface())
   557  			}
   558  		} else {
   559  			tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
   560  			childScanner = ScanFromTokens(tokens...)
   561  		}
   562  		childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
   563  		if childDecoder == nil {
   564  			return fmt.Errorf("no mapper for element type of %s", target.Type())
   565  		}
   566  		for !childScanner.Peek().IsEOL() {
   567  			childValue := reflect.New(el).Elem()
   568  			err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
   569  			if err != nil {
   570  				return err
   571  			}
   572  			target.Set(reflect.Append(target, childValue))
   573  		}
   574  		return nil
   575  	}
   576  }
   577  
   578  func pathMapper(r *Registry) MapperFunc {
   579  	return func(ctx *DecodeContext, target reflect.Value) error {
   580  		if target.Kind() == reflect.Slice {
   581  			return sliceDecoder(r)(ctx, target)
   582  		}
   583  		if target.Kind() == reflect.Ptr && target.Elem().Kind() == reflect.String {
   584  			if target.IsNil() {
   585  				return nil
   586  			}
   587  			target = target.Elem()
   588  		}
   589  		if target.Kind() != reflect.String {
   590  			return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type())
   591  		}
   592  		var path string
   593  		err := ctx.Scan.PopValueInto("file", &path)
   594  		if err != nil {
   595  			return err
   596  		}
   597  		if path != "-" {
   598  			path = ExpandPath(path)
   599  		}
   600  		target.SetString(path)
   601  		return nil
   602  	}
   603  }
   604  
   605  func fileMapper(r *Registry) MapperFunc {
   606  	return func(ctx *DecodeContext, target reflect.Value) error {
   607  		if target.Kind() == reflect.Slice {
   608  			return sliceDecoder(r)(ctx, target)
   609  		}
   610  		var path string
   611  		err := ctx.Scan.PopValueInto("file", &path)
   612  		if err != nil {
   613  			return err
   614  		}
   615  		var file *os.File
   616  		if path == "-" {
   617  			file = os.Stdin
   618  		} else {
   619  			path = ExpandPath(path)
   620  			file, err = os.Open(path) //nolint: gosec
   621  			if err != nil {
   622  				return err
   623  			}
   624  		}
   625  		target.Set(reflect.ValueOf(file))
   626  		return nil
   627  	}
   628  }
   629  
   630  func existingFileMapper(r *Registry) MapperFunc {
   631  	return func(ctx *DecodeContext, target reflect.Value) error {
   632  		if target.Kind() == reflect.Slice {
   633  			return sliceDecoder(r)(ctx, target)
   634  		}
   635  		if target.Kind() != reflect.String {
   636  			return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
   637  		}
   638  		var path string
   639  		err := ctx.Scan.PopValueInto("file", &path)
   640  		if err != nil {
   641  			return err
   642  		}
   643  
   644  		if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
   645  			// early return to avoid checking extra files that may not exist;
   646  			// this hack only works because the value provided on the cli is
   647  			// checked before the default value is checked (if default is set).
   648  			return nil
   649  		}
   650  
   651  		if path != "-" {
   652  			path = ExpandPath(path)
   653  			stat, err := os.Stat(path)
   654  			if err != nil {
   655  				return err
   656  			}
   657  			if stat.IsDir() {
   658  				return fmt.Errorf("%q exists but is a directory", path)
   659  			}
   660  		}
   661  		target.SetString(path)
   662  		return nil
   663  	}
   664  }
   665  
   666  func existingDirMapper(r *Registry) MapperFunc {
   667  	return func(ctx *DecodeContext, target reflect.Value) error {
   668  		if target.Kind() == reflect.Slice {
   669  			return sliceDecoder(r)(ctx, target)
   670  		}
   671  		if target.Kind() != reflect.String {
   672  			return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
   673  		}
   674  		var path string
   675  		err := ctx.Scan.PopValueInto("file", &path)
   676  		if err != nil {
   677  			return err
   678  		}
   679  
   680  		if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
   681  			// early return to avoid checking extra dirs that may not exist;
   682  			// this hack only works because the value provided on the cli is
   683  			// checked before the default value is checked (if default is set).
   684  			return nil
   685  		}
   686  
   687  		path = ExpandPath(path)
   688  		stat, err := os.Stat(path)
   689  		if err != nil {
   690  			return err
   691  		}
   692  		if !stat.IsDir() {
   693  			return fmt.Errorf("%q exists but is not a directory", path)
   694  		}
   695  		target.SetString(path)
   696  		return nil
   697  	}
   698  }
   699  
   700  func fileContentMapper(r *Registry) MapperFunc {
   701  	return func(ctx *DecodeContext, target reflect.Value) error {
   702  		if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 {
   703  			return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type())
   704  		}
   705  		var path string
   706  		err := ctx.Scan.PopValueInto("file", &path)
   707  		if err != nil {
   708  			return err
   709  		}
   710  
   711  		if !ctx.Value.Active || ctx.Value.Set {
   712  			// early return to avoid checking extra dirs that may not exist;
   713  			// this hack only works because the value provided on the cli is
   714  			// checked before the default value is checked (if default is set).
   715  			return nil
   716  		}
   717  
   718  		var data []byte
   719  		if path != "-" {
   720  			path = ExpandPath(path)
   721  			data, err = os.ReadFile(path) //nolint:gosec
   722  		} else {
   723  			data, err = io.ReadAll(os.Stdin)
   724  		}
   725  		if err != nil {
   726  			if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
   727  				return fmt.Errorf("%q exists but is a directory: %w", path, err)
   728  			}
   729  			return err
   730  		}
   731  		target.SetBytes(data)
   732  		return nil
   733  	}
   734  }
   735  
   736  type ptrMapper struct {
   737  	r *Registry
   738  }
   739  
   740  var _ BoolMapperExt = (*ptrMapper)(nil)
   741  
   742  // IsBoolFromValue implements BoolMapperExt
   743  func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool {
   744  	elem := reflect.New(target.Type().Elem()).Elem()
   745  	nestedMapper := p.r.ForValue(elem)
   746  	if nestedMapper == nil {
   747  		return false
   748  	}
   749  	if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() {
   750  		return true
   751  	}
   752  	if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) {
   753  		return true
   754  	}
   755  	return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool
   756  }
   757  
   758  func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
   759  	elem := reflect.New(target.Type().Elem()).Elem()
   760  	nestedMapper := p.r.ForValue(elem)
   761  	if nestedMapper == nil {
   762  		return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
   763  	}
   764  	err := nestedMapper.Decode(ctx, elem)
   765  	if err != nil {
   766  		return err
   767  	}
   768  	target.Set(elem.Addr())
   769  	return nil
   770  }
   771  
   772  func counterMapper() MapperFunc {
   773  	return func(ctx *DecodeContext, target reflect.Value) error {
   774  		if ctx.Scan.Peek().Type == FlagValueToken {
   775  			t, err := ctx.Scan.PopValue("counter")
   776  			if err != nil {
   777  				return err
   778  			}
   779  			switch v := t.Value.(type) {
   780  			case string:
   781  				n, err := strconv.ParseInt(v, 10, 64)
   782  				if err != nil {
   783  					return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
   784  				}
   785  				target.SetInt(n)
   786  
   787  			case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   788  				target.Set(reflect.ValueOf(v))
   789  
   790  			default:
   791  				return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
   792  			}
   793  			return nil
   794  		}
   795  
   796  		switch target.Kind() {
   797  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   798  			target.SetInt(target.Int() + 1)
   799  
   800  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   801  			target.SetUint(target.Uint() + 1)
   802  
   803  		case reflect.Float32, reflect.Float64:
   804  			target.SetFloat(target.Float() + 1)
   805  
   806  		default:
   807  			return fmt.Errorf("type:\"counter\" must be used with a numeric field")
   808  		}
   809  		return nil
   810  	}
   811  }
   812  
   813  func urlMapper() MapperFunc {
   814  	return func(ctx *DecodeContext, target reflect.Value) error {
   815  		var urlStr string
   816  		err := ctx.Scan.PopValueInto("url", &urlStr)
   817  		if err != nil {
   818  			return err
   819  		}
   820  		url, err := url.Parse(urlStr)
   821  		if err != nil {
   822  			return err
   823  		}
   824  		target.Set(reflect.ValueOf(url))
   825  		return nil
   826  	}
   827  }
   828  
   829  // SplitEscaped splits a string on a separator.
   830  //
   831  // It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
   832  //
   833  //	SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
   834  func SplitEscaped(s string, sep rune) (out []string) {
   835  	if sep == -1 {
   836  		return []string{s}
   837  	}
   838  	escaped := false
   839  	token := ""
   840  	for i, ch := range s {
   841  		switch {
   842  		case escaped:
   843  			if ch != sep {
   844  				token += `\`
   845  			}
   846  			token += string(ch)
   847  			escaped = false
   848  		case ch == '\\' && i < len(s)-1:
   849  			escaped = true
   850  		case ch == sep && !escaped:
   851  			out = append(out, token)
   852  			token = ""
   853  			escaped = false
   854  		default:
   855  			token += string(ch)
   856  		}
   857  	}
   858  	if token != "" {
   859  		out = append(out, token)
   860  	}
   861  	return
   862  }
   863  
   864  // JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
   865  //
   866  //	JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
   867  func JoinEscaped(s []string, sep rune) string {
   868  	escaped := []string{}
   869  	for _, e := range s {
   870  		escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep)))
   871  	}
   872  	return strings.Join(escaped, string(sep))
   873  }
   874  
   875  // NamedFileContentFlag is a flag value that loads a file's contents and filename into its value.
   876  type NamedFileContentFlag struct {
   877  	Filename string
   878  	Contents []byte
   879  }
   880  
   881  func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
   882  	var filename string
   883  	err := ctx.Scan.PopValueInto("filename", &filename)
   884  	if err != nil {
   885  		return err
   886  	}
   887  	// This allows unsetting of file content flags.
   888  	if filename == "" {
   889  		*f = NamedFileContentFlag{}
   890  		return nil
   891  	}
   892  	filename = ExpandPath(filename)
   893  	data, err := os.ReadFile(filename) //nolint: gosec
   894  	if err != nil {
   895  		return fmt.Errorf("failed to open %q: %v", filename, err)
   896  	}
   897  	f.Contents = data
   898  	f.Filename = filename
   899  	return nil
   900  }
   901  
   902  // FileContentFlag is a flag value that loads a file's contents into its value.
   903  type FileContentFlag []byte
   904  
   905  func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
   906  	var filename string
   907  	err := ctx.Scan.PopValueInto("filename", &filename)
   908  	if err != nil {
   909  		return err
   910  	}
   911  	// This allows unsetting of file content flags.
   912  	if filename == "" {
   913  		*f = nil
   914  		return nil
   915  	}
   916  	filename = ExpandPath(filename)
   917  	data, err := os.ReadFile(filename) //nolint: gosec
   918  	if err != nil {
   919  		return fmt.Errorf("failed to open %q: %v", filename, err)
   920  	}
   921  	*f = data
   922  	return nil
   923  }
   924  
   925  func jsonTranscode(in, out interface{}) error {
   926  	data, err := json.Marshal(in)
   927  	if err != nil {
   928  		return err
   929  	}
   930  	if err = json.Unmarshal(data, out); err != nil {
   931  		return fmt.Errorf("%#v -> %T: %v", in, out, err)
   932  	}
   933  	return nil
   934  }