Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
@@ -6,6 +6,7 @@ package interpres
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
@@ -23,6 +24,8 @@ var (
|
||||
localDateType = reflect.TypeFor[LocalDate]()
|
||||
localTimeType = reflect.TypeFor[LocalTime]()
|
||||
timeGoType = reflect.TypeFor[time.Time]()
|
||||
durationType = reflect.TypeFor[time.Duration]()
|
||||
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
|
||||
)
|
||||
|
||||
// encoder produces a TOML document from a Go value via a small intermediate
|
||||
@@ -319,6 +322,15 @@ func addField(doc *tomlDoc, name string, v reflect.Value, ctx string) error {
|
||||
v = reflect.ValueOf(mv)
|
||||
}
|
||||
}
|
||||
// A type that renders itself as text becomes a TOML string, whether it is
|
||||
// a scalar kind or a struct.
|
||||
s, isText, err := textValue(v)
|
||||
if err != nil {
|
||||
return &EncodeError{Path: joinKey(ctx, name), Err: err}
|
||||
}
|
||||
if isText {
|
||||
return doc.appendScalar(name, s, ctx)
|
||||
}
|
||||
v = followPtr(v)
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
@@ -500,6 +512,20 @@ func normaliseValue(v reflect.Value) (any, error) {
|
||||
if t := v.Type(); t == timeGoType || isLocalDateType(t) {
|
||||
return v.Interface(), nil
|
||||
}
|
||||
// TOML has no duration type, so a duration goes out in its canonical Go
|
||||
// form, the shape it comes back in.
|
||||
if v.Type() == durationType {
|
||||
return time.Duration(v.Int()).String(), nil
|
||||
}
|
||||
// A type that renders itself as text becomes a TOML string, scalar kinds
|
||||
// and structs alike.
|
||||
s, isText, err := textValue(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isText {
|
||||
return s, nil
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
return v.String(), nil
|
||||
@@ -573,10 +599,75 @@ func isLocalDateType(t reflect.Type) bool {
|
||||
return t == localDateTimeType || t == localDateType || t == localTimeType
|
||||
}
|
||||
|
||||
// isDateTimeType reports whether t is one of the four TOML date-time types,
|
||||
// which the encoder emits as bare atoms. Pointers are looked through. The types
|
||||
// carry time.Time's text methods through an embedded field, and the atom form
|
||||
// takes precedence over them.
|
||||
func isDateTimeType(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t == timeGoType || isLocalDateType(t)
|
||||
}
|
||||
|
||||
// isTextMarshalerType reports whether t or *t implements
|
||||
// encoding.TextMarshaler. An array of such values stays a value array, because
|
||||
// each element's TOML form is a string.
|
||||
func isTextMarshalerType(t reflect.Type) bool {
|
||||
if isDateTimeType(t) {
|
||||
return false
|
||||
}
|
||||
return t.Implements(textMarshalerType) || reflect.PointerTo(t).Implements(textMarshalerType)
|
||||
}
|
||||
|
||||
// textValue returns the string a value renders itself as through
|
||||
// encoding.TextMarshaler. The date-time types are excluded, because their
|
||||
// embedded time.Time would answer with an RFC 3339 string where the TOML form
|
||||
// is a bare timestamp. A nil pointer offers no text and is left to the ordinary
|
||||
// nil handling, which omits the field.
|
||||
func textValue(v reflect.Value) (string, bool, error) {
|
||||
for v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
if !v.IsValid() || isDateTimeType(v.Type()) {
|
||||
return "", false, nil
|
||||
}
|
||||
if v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
return "", false, nil
|
||||
}
|
||||
m, ok := textMarshalerOf(v)
|
||||
if !ok {
|
||||
return "", false, nil
|
||||
}
|
||||
b, err := m.MarshalText()
|
||||
if err != nil {
|
||||
return "", true, err
|
||||
}
|
||||
return string(b), true, nil
|
||||
}
|
||||
|
||||
// textMarshalerOf finds the encoding.TextMarshaler for v: on the value itself,
|
||||
// or on its address, so a pointer-receiver MarshalText is found on an
|
||||
// addressable struct field.
|
||||
func textMarshalerOf(v reflect.Value) (encoding.TextMarshaler, bool) {
|
||||
if !v.CanInterface() {
|
||||
return nil, false
|
||||
}
|
||||
if m, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
return m, true
|
||||
}
|
||||
if v.CanAddr() {
|
||||
if m, ok := v.Addr().Interface().(encoding.TextMarshaler); ok {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func isTableElementType(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
return !isScalarStruct(t)
|
||||
return !isScalarStruct(t) && !isTextMarshalerType(t)
|
||||
case reflect.Map:
|
||||
return t.Key().Kind() == reflect.String
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user