package scfg import ( "encoding" "fmt" "io" "reflect" "strconv" ) // Decoder reads and decodes an scfg document from an input stream. type Decoder struct { r io.Reader } // NewDecoder returns a new decoder which reads from r. func NewDecoder(r io.Reader) *Decoder { return &Decoder{r} } // Decode reads scfg document from the input and stores it in the value pointed // to by v. // // If v is nil or not a pointer, Decode returns an error. // // Blocks can be unmarshaled to: // // - Maps. Each directive is unmarshaled into a map entry. The map key must // be a string. // - Structs. Each directive is unmarshaled into a struct field. // // Duplicate directives are not allowed, unless the struct field or map value // is a slice of values representing a directive: structs or maps. // // Directives can be unmarshaled to: // // - Maps. The children block is unmarshaled into the map. Parameters are not // allowed. // - Structs. The children block is unmarshaled into the struct. Parameters // are allowed if one of the struct fields contains the "param" option in // its tag. // - Slices. Parameters are unmarshaled into the slice. Children blocks are // not allowed. // - Arrays. Parameters are unmarshaled into the array. The number of // parameters must match exactly the length of the array. Children blocks // are not allowed. // - Strings, booleans, integers, floating-point values, values implementing // encoding.TextUnmarshaler. Only a single parameter is allowed and is // unmarshaled into the value. Children blocks are not allowed. // // The decoding of each struct field can be customized by the format string // stored under the "scfg" key in the struct field's tag. The tag contains the // name of the field possibly followed by a comma-separated list of options. // The name may be empty in order to specify options without overriding the // default field name. As a special case, if the field name is "-", the field // is ignored. The "param" option specifies that directive parameters are // stored in this field (the name must be empty). func (dec *Decoder) Decode(v interface{}) error { block, err := Read(dec.r) if err != nil { return err } rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return fmt.Errorf("scfg: invalid value for unmarshaling") } return unmarshalBlock(block, rv) } func unmarshalBlock(block Block, v reflect.Value) error { v = unwrapPointers(v) t := v.Type() dirsByName := make(map[string][]*Directive, len(block)) for _, dir := range block { dirsByName[dir.Name] = append(dirsByName[dir.Name], dir) } switch v.Kind() { case reflect.Map: if t.Key().Kind() != reflect.String { return fmt.Errorf("scfg: map key type must be string") } if v.IsNil() { v.Set(reflect.MakeMap(t)) } else if v.Len() > 0 { clearMap(v) } for name, dirs := range dirsByName { mv := reflect.New(t.Elem()).Elem() if err := unmarshalDirectiveList(dirs, mv); err != nil { return err } v.SetMapIndex(reflect.ValueOf(name), mv) } case reflect.Struct: si, err := getStructInfo(t) if err != nil { return err } for name, dirs := range dirsByName { fieldIndex, ok := si.children[name] if !ok { return newUnmarshalDirectiveError(dirs[0], "unknown directive") } fv := v.Field(fieldIndex) if err := unmarshalDirectiveList(dirs, fv); err != nil { return err } } default: return fmt.Errorf("scfg: unsupported type for unmarshaling blocks: %v", t) } return nil } func unmarshalDirectiveList(dirs []*Directive, v reflect.Value) error { v = unwrapPointers(v) t := v.Type() if v.Kind() != reflect.Slice || !isDirectiveType(t.Elem()) { if len(dirs) > 1 { return newUnmarshalDirectiveError(dirs[1], "directive must not be specified more than once") } return unmarshalDirective(dirs[0], v) } sv := reflect.MakeSlice(t, len(dirs), len(dirs)) for i, dir := range dirs { if err := unmarshalDirective(dir, sv.Index(i)); err != nil { return err } } v.Set(sv) return nil } // isDirectiveType checks whether a type can only be unmarshaled as a // directive, not as a parameter. Accepting too many types here would result in // ambiguities, see: // https://lists.sr.ht/~emersion/public-inbox/%3C20230629132458.152205-1-contact%40emersion.fr%3E#%3Ch4Y2peS_YBqY3ar4XlmPDPiNBFpYGns3EBYUx3_6zWEhV2o8_-fBQveRujGADWYhVVCucHBEryFGoPtpC3d3mQ-x10pWnFogfprbQTSvtxc=@emersion.fr%3E func isDirectiveType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } textUnmarshalerType := reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() if reflect.PtrTo(t).Implements(textUnmarshalerType) { return false } switch t.Kind() { case reflect.Struct, reflect.Map: return true default: return false } } func unmarshalDirective(dir *Directive, v reflect.Value) error { v = unwrapPointers(v) t := v.Type() if v.CanAddr() { if _, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok { if len(dir.Children) != 0 { return newUnmarshalDirectiveError(dir, "directive requires zero children") } return unmarshalParamList(dir, v) } } switch v.Kind() { case reflect.Map: if len(dir.Params) > 0 { return newUnmarshalDirectiveError(dir, "directive requires zero parameters") } if err := unmarshalBlock(dir.Children, v); err != nil { return err } case reflect.Struct: si, err := getStructInfo(t) if err != nil { return err } if si.param >= 0 { if err := unmarshalParamList(dir, v.Field(si.param)); err != nil { return err } } else { if len(dir.Params) > 0 { return newUnmarshalDirectiveError(dir, "directive requires zero parameters") } } if err := unmarshalBlock(dir.Children, v); err != nil { return err } default: if len(dir.Children) != 0 { return newUnmarshalDirectiveError(dir, "directive requires zero children") } if err := unmarshalParamList(dir, v); err != nil { return err } } return nil } func unmarshalParamList(dir *Directive, v reflect.Value) error { switch v.Kind() { case reflect.Slice: t := v.Type() sv := reflect.MakeSlice(t, len(dir.Params), len(dir.Params)) for i, param := range dir.Params { if err := unmarshalParam(param, sv.Index(i)); err != nil { return newUnmarshalParamError(dir, i, err) } } v.Set(sv) case reflect.Array: if len(dir.Params) != v.Len() { return newUnmarshalDirectiveError(dir, fmt.Sprintf("directive requires exactly %v parameters", v.Len())) } for i, param := range dir.Params { if err := unmarshalParam(param, v.Index(i)); err != nil { return newUnmarshalParamError(dir, i, err) } } default: if len(dir.Params) != 1 { return newUnmarshalDirectiveError(dir, "directive requires exactly one parameter") } if err := unmarshalParam(dir.Params[0], v); err != nil { return newUnmarshalParamError(dir, 0, err) } } return nil } func unmarshalParam(param string, v reflect.Value) error { v = unwrapPointers(v) t := v.Type() // TODO: improve our logic following: // https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/encoding/json/decode.go;drc=b9b8cecbfc72168ca03ad586cc2ed52b0e8db409;l=421 if v.CanAddr() { if v, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok { return v.UnmarshalText([]byte(param)) } } switch v.Kind() { case reflect.String: v.Set(reflect.ValueOf(param)) case reflect.Bool: switch param { case "true": v.Set(reflect.ValueOf(true)) case "false": v.Set(reflect.ValueOf(false)) default: return fmt.Errorf("invalid bool parameter %q", param) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := strconv.ParseInt(param, 10, t.Bits()) if err != nil { return fmt.Errorf("invalid %v parameter: %v", t, err) } v.Set(reflect.ValueOf(i).Convert(t)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, err := strconv.ParseUint(param, 10, t.Bits()) if err != nil { return fmt.Errorf("invalid %v parameter: %v", t, err) } v.Set(reflect.ValueOf(u).Convert(t)) case reflect.Float32, reflect.Float64: f, err := strconv.ParseFloat(param, t.Bits()) if err != nil { return fmt.Errorf("invalid %v parameter: %v", t, err) } v.Set(reflect.ValueOf(f).Convert(t)) default: return fmt.Errorf("unsupported type for unmarshaling parameter: %v", t) } return nil } func unwrapPointers(v reflect.Value) reflect.Value { for v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() } return v } func clearMap(v reflect.Value) { for _, k := range v.MapKeys() { v.SetMapIndex(k, reflect.Value{}) } } type unmarshalDirectiveError struct { lineno int name string msg string } func newUnmarshalDirectiveError(dir *Directive, msg string) *unmarshalDirectiveError { return &unmarshalDirectiveError{ name: dir.Name, lineno: dir.lineno, msg: msg, } } func (err *unmarshalDirectiveError) Error() string { return fmt.Sprintf("line %v, directive %q: %v", err.lineno, err.name, err.msg) } type unmarshalParamError struct { lineno int directive string paramIndex int err error } func newUnmarshalParamError(dir *Directive, paramIndex int, err error) *unmarshalParamError { return &unmarshalParamError{ directive: dir.Name, lineno: dir.lineno, paramIndex: paramIndex, err: err, } } func (err *unmarshalParamError) Error() string { return fmt.Sprintf("line %v, directive %q, parameter %v: %v", err.lineno, err.directive, err.paramIndex+1, err.err) }