options.go 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. package eval
  2. import (
  3. "reflect"
  4. "src.elv.sh/pkg/eval/vals"
  5. "src.elv.sh/pkg/parse"
  6. )
  7. // UnknownOption is thrown by a native function when called with an unknown option.
  8. type UnknownOption struct {
  9. OptName string
  10. }
  11. // Error implements the error interface.
  12. func (e UnknownOption) Error() string {
  13. return "unknown option: " + parse.Quote(e.OptName)
  14. }
  15. // RawOptions is the type of an argument a Go-native function can take to
  16. // declare that it wants to parse options itself. See the doc of NewGoFn for
  17. // details.
  18. type RawOptions map[string]any
  19. // Takes a raw option map and a pointer to a struct, and populate the struct
  20. // with options. A field named FieldName corresponds to the option named
  21. // field-name. Options that don't have corresponding fields in the struct causes
  22. // an error.
  23. //
  24. // Similar to vals.ScanMapToGo, but requires rawOpts to contain a subset of keys
  25. // supported by the struct.
  26. func scanOptions(rawOpts RawOptions, ptr any) error {
  27. _, keyIdx := vals.StructFieldsInfo(reflect.TypeOf(ptr).Elem())
  28. structValue := reflect.ValueOf(ptr).Elem()
  29. for k, v := range rawOpts {
  30. fieldIdx, ok := keyIdx[k]
  31. if !ok {
  32. return UnknownOption{k}
  33. }
  34. // An option with no value (e.g. `&a-opt`) has `$true` as its default value. However, if
  35. // the option struct member is a string we want an empty string as the default value.
  36. switch b := v.(type) {
  37. case bool:
  38. if b && structValue.Field(fieldIdx).Type().Name() == "string" {
  39. v = ""
  40. }
  41. }
  42. err := vals.ScanToGo(v, structValue.Field(fieldIdx).Addr().Interface())
  43. if err != nil {
  44. return err
  45. }
  46. }
  47. return nil
  48. }