Compare commits
6 Commits
0d8af97e2c
...
22f01d3ab2
Author | SHA1 | Date |
---|---|---|
kayos@tcp.direct | 22f01d3ab2 | |
kayos@tcp.direct | 97bfa7c496 | |
kayos | 043fb93abf | |
dependabot[bot] | 42c3b2a2bd | |
kayos | 0f7cf8c521 | |
dependabot[bot] | e7a55c068b |
2
go.mod
2
go.mod
|
@ -4,6 +4,7 @@ go 1.19
|
|||
|
||||
require (
|
||||
git.tcp.direct/kayos/common v0.9.7
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fasthttp/router v1.5.1
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0
|
||||
github.com/knadh/koanf/providers/basicflag v1.0.0
|
||||
|
@ -11,6 +12,7 @@ require (
|
|||
github.com/knadh/koanf/v2 v2.1.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/valyala/fasthttp v1.55.0
|
||||
golang.org/x/text v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
3
go.sum
3
go.sum
|
@ -4,6 +4,7 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
|
|||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
|
||||
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
||||
|
@ -49,6 +50,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
nullprogram.com/x/rng v1.1.0 h1:SMU7DHaQSWtKJNTpNFIFt8Wd/KSmOuSDPXrMFp/UMro=
|
||||
nullprogram.com/x/rng v1.1.0/go.mod h1:glGw6V87vyfawxCzqOABL3WfL95G65az9Z2JZCylCkg=
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.tcp.direct/kayos/common/pool"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidTOML = errors.New("invalid TOML")
|
||||
ErrBadTarget = errors.New("bad target")
|
||||
ErrMismatchingSchema = errors.New("mismatching schema types")
|
||||
)
|
||||
|
||||
type decoder struct {
|
||||
buf *pool.Buffer
|
||||
utf8 io.Reader
|
||||
target interface{}
|
||||
inArray bool
|
||||
tables map[string]interface{}
|
||||
}
|
||||
|
||||
func newDecoder(input []byte, v interface{}) *decoder {
|
||||
d := &decoder{
|
||||
target: v,
|
||||
utf8: unicode.UTF8.NewDecoder().Reader(bytes.NewReader(input)),
|
||||
tables: make(map[string]interface{}),
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decoder) nextTableName() (tableName []byte, err error) {
|
||||
if _, err = d.buf.ReadBytes('['); err != nil {
|
||||
return nil, fmt.Errorf("%w (%w): %w", ErrInvalidTOML, io.ErrUnexpectedEOF, err)
|
||||
}
|
||||
|
||||
if tableName, err = d.buf.ReadBytes(']'); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("%w: missing ']'", io.ErrUnexpectedEOF)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %w", ErrInvalidTOML, err)
|
||||
}
|
||||
|
||||
tableName = tableName[:len(tableName)-1]
|
||||
|
||||
return tableName, nil
|
||||
}
|
||||
|
||||
func handleBool(v any) (bool, error) {
|
||||
vBool, boolOK := v.(bool)
|
||||
vInt, intOK := v.(int64)
|
||||
vStr, strOK := v.(string)
|
||||
switch {
|
||||
case intOK:
|
||||
if vInt == 0 || vInt == 1 {
|
||||
vBool = vInt == 1
|
||||
boolOK = true
|
||||
}
|
||||
case strOK:
|
||||
parsedBool, parseErr := strconv.ParseBool(vStr)
|
||||
if parseErr != nil {
|
||||
return false, fmt.Errorf("%w: expected bool-ish: %w", ErrMismatchingSchema, parseErr)
|
||||
}
|
||||
vBool = parsedBool
|
||||
boolOK = true
|
||||
case boolOK:
|
||||
default:
|
||||
}
|
||||
if !boolOK {
|
||||
return false, fmt.Errorf("%w: expected bool, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
|
||||
return vBool, nil
|
||||
}
|
||||
|
||||
func handleBottom(targetElem reflect.Value, v any, j int) error {
|
||||
if !targetElem.CanSet() {
|
||||
return fmt.Errorf("%w: cannot set field", ErrBadTarget)
|
||||
}
|
||||
|
||||
println(targetElem.Type().Kind().String())
|
||||
|
||||
switch targetElem.Kind() {
|
||||
case reflect.String:
|
||||
vStr, strOK := v.(string)
|
||||
if !strOK {
|
||||
return fmt.Errorf("%w: expected string, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
targetElem.SetString(vStr)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
vInt, intOK := v.(int64)
|
||||
if !intOK {
|
||||
return fmt.Errorf("%w: expected int, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
targetElem.SetInt(vInt)
|
||||
case reflect.Slice:
|
||||
switch field := targetElem; field.Type().Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
vInt, intOK := v.([]int64)
|
||||
if !intOK {
|
||||
return fmt.Errorf("%w: expected int slice, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
slice := reflect.MakeSlice(field.Type().Elem(), len(vInt), len(vInt))
|
||||
for i := 0; i < len(vInt); i++ {
|
||||
slice.Index(i).SetInt(vInt[i])
|
||||
}
|
||||
targetElem.Field(j).Set(slice)
|
||||
case reflect.String:
|
||||
vStr, strOK := v.([]string)
|
||||
if !strOK {
|
||||
return fmt.Errorf("%w: expected string slice, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
slice := reflect.MakeSlice(field.Type().Elem(), len(vStr), len(vStr))
|
||||
for i := 0; i < len(vStr); i++ {
|
||||
slice.Index(i).SetString(vStr[i])
|
||||
}
|
||||
targetElem.Field(j).Set(slice)
|
||||
}
|
||||
case reflect.Bool:
|
||||
vBool, boolErr := handleBool(v)
|
||||
if boolErr != nil {
|
||||
return fmt.Errorf("%w: %w", ErrMismatchingSchema, boolErr)
|
||||
}
|
||||
targetElem.Field(j).SetBool(vBool)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) handleLine(lines []string, i int, myTable map[string]interface{}) error {
|
||||
var key string
|
||||
var value interface{}
|
||||
split := strings.SplitN(lines[i], "=", 2)
|
||||
if len(split) != 2 {
|
||||
return nil
|
||||
}
|
||||
key = strings.TrimSpace(split[0])
|
||||
valueString := strings.TrimSpace(split[1])
|
||||
|
||||
if valueString == "[" {
|
||||
d.inArray = true
|
||||
var continued []byte
|
||||
var err error
|
||||
continued, err = d.buf.ReadBytes(']')
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w in mthe middle of array %w", ErrInvalidTOML, io.ErrUnexpectedEOF, err)
|
||||
}
|
||||
valueString += string(continued)
|
||||
valueString = strings.ReplaceAll(valueString, "\n", "")
|
||||
}
|
||||
|
||||
switch {
|
||||
case d.inArray:
|
||||
valueString = strings.Trim(valueString, "[]")
|
||||
asplit := strings.Split(valueString, ",")
|
||||
intVals := make([]int64, 0, len(asplit))
|
||||
for aindex := range asplit {
|
||||
asplit[aindex] = strings.Trim(strings.TrimSpace(asplit[aindex]), "\"'")
|
||||
avalInt, intErr := strconv.ParseInt(valueString, 10, 64)
|
||||
if intErr == nil {
|
||||
intVals = append(intVals, avalInt)
|
||||
}
|
||||
println("array value: ", asplit[aindex])
|
||||
}
|
||||
if len(intVals) == len(asplit) && len(intVals) > 0 {
|
||||
value = intVals
|
||||
} else {
|
||||
value = asplit
|
||||
}
|
||||
d.inArray = false
|
||||
default:
|
||||
valueString = strings.Trim(valueString, "\"'")
|
||||
valBool, boolErr := strconv.ParseBool(valueString)
|
||||
valInt, intErr := strconv.ParseInt(valueString, 10, 64)
|
||||
switch {
|
||||
case boolErr == nil:
|
||||
value = valBool
|
||||
case intErr == nil:
|
||||
value = valInt
|
||||
default:
|
||||
value = valueString
|
||||
}
|
||||
}
|
||||
|
||||
if value == nil || key == "" || len(valueString) == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
fmt.Printf("key: %s, value(%T): %v\n", key, value, value)
|
||||
|
||||
myTable[key] = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findStructTag(struc reflect.Value, sought string) (int, error) {
|
||||
for i := 0; i < struc.NumField(); i++ {
|
||||
if struc.Type().Field(i).Tag.Get("toml") == sought {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("%w: %s", ErrMismatchingSchema, sought)
|
||||
|
||||
}
|
||||
|
||||
func (d *decoder) handleTables() error {
|
||||
spew.Dump(d.tables)
|
||||
|
||||
for k, v := range d.tables {
|
||||
if !strings.Contains(k, ".") {
|
||||
vTable, tableOK := v.(map[string]interface{})
|
||||
if !tableOK {
|
||||
return fmt.Errorf("%w: expected table, got %T", ErrMismatchingSchema, v)
|
||||
}
|
||||
println("table:", vTable)
|
||||
|
||||
targetElem := reflect.ValueOf(d.target).Elem()
|
||||
|
||||
fieldIndex, fieldErr := findStructTag(targetElem, k)
|
||||
if fieldErr != nil {
|
||||
continue
|
||||
}
|
||||
println("field index: ", fieldIndex)
|
||||
if targetElem.Field(fieldIndex).Type().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("%w: expected struct, got %T", ErrMismatchingSchema, targetElem.Field(fieldIndex).Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) start() error {
|
||||
d.buf = bufs.Get()
|
||||
defer bufs.MustPut(d.buf)
|
||||
|
||||
n, err := d.buf.ReadFrom(d.utf8)
|
||||
if err == nil && n == 0 {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrInvalidTOML, err)
|
||||
}
|
||||
|
||||
for d.buf.Len() > 0 {
|
||||
var tableName []byte
|
||||
var tableNameErr error
|
||||
|
||||
if tableName, tableNameErr = d.nextTableName(); tableNameErr != nil && !errors.Is(tableNameErr, io.EOF) {
|
||||
return tableNameErr
|
||||
}
|
||||
|
||||
println("table: ", string(tableName))
|
||||
|
||||
myTable, mapAssertOK := d.tables[string(tableName)].(map[string]interface{})
|
||||
if !mapAssertOK {
|
||||
d.tables[string(tableName)] = make(map[string]interface{})
|
||||
myTable = d.tables[string(tableName)].(map[string]interface{}) //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
var tableData []byte
|
||||
if tableData, err = d.buf.ReadBytes('['); err != nil && !errors.Is(err, io.EOF) {
|
||||
return fmt.Errorf("%w: %w", ErrInvalidTOML, err)
|
||||
}
|
||||
|
||||
tableDataStr := string(tableData)
|
||||
|
||||
lines := strings.Split(tableDataStr, "\n")
|
||||
|
||||
if strings.Contains(lines[len(lines)-1], "[") && !strings.Contains(lines[len(lines)-1], "=") {
|
||||
_ = d.buf.UnreadByte()
|
||||
}
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if err = d.handleLine(lines, i, myTable); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.tables) == 0 {
|
||||
return fmt.Errorf("%w: empty TOML, %w", ErrInvalidTOML, io.ErrUnexpectedEOF)
|
||||
}
|
||||
}
|
||||
return d.handleTables()
|
||||
}
|
||||
|
||||
func trimTest(data []byte) (trimmed []byte) {
|
||||
for _, c := range []byte{' ', '\n', '\t', '\r', '[', ']', '='} {
|
||||
trimmed = bytes.ReplaceAll(data, []byte{c}, []byte(""))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UnmarshalTOML(data []byte, v interface{}) error {
|
||||
var err error
|
||||
if data, err = unicode.UTF8.NewDecoder().Bytes(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Contains(data, []byte("[")) {
|
||||
return fmt.Errorf("%w: missing '['", ErrInvalidTOML)
|
||||
}
|
||||
if !bytes.Contains(data, []byte("]")) {
|
||||
return fmt.Errorf("%w: missing ']'", ErrInvalidTOML)
|
||||
}
|
||||
if !bytes.Contains(data, []byte("=")) {
|
||||
return fmt.Errorf("%w: missing '='", ErrInvalidTOML)
|
||||
}
|
||||
if len(trimTest(data)) < 2 {
|
||||
return fmt.Errorf("%w: empty TOML", ErrInvalidTOML)
|
||||
}
|
||||
|
||||
ref := reflect.ValueOf(v)
|
||||
if ref.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("%w: non-pointer", ErrBadTarget)
|
||||
}
|
||||
if ref.IsNil() {
|
||||
return fmt.Errorf("%w: nil pointer", ErrBadTarget)
|
||||
}
|
||||
if ref.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("%w: non-struct", ErrBadTarget)
|
||||
}
|
||||
data = bytes.ReplaceAll(data, []byte("\r"), []byte(""))
|
||||
|
||||
return newDecoder(data, v).start()
|
||||
}
|
|
@ -23,7 +23,7 @@ type subStruct struct {
|
|||
parent *subStruct
|
||||
}
|
||||
|
||||
func (sub *subStruct) writeKey(buf *pool.Buffer) {
|
||||
func (sub *subStruct) writeTableHeader(buf *pool.Buffer) {
|
||||
parent := sub.parent
|
||||
var parents = make([]string, 0)
|
||||
for parent != nil {
|
||||
|
@ -33,28 +33,22 @@ func (sub *subStruct) writeKey(buf *pool.Buffer) {
|
|||
slices.Reverse(parents)
|
||||
parents = append(parents, sub.name)
|
||||
if len(parents) > 1 {
|
||||
writeKey(buf, parents[0], parents[1:]...)
|
||||
writeTableHeaders(buf, parents[0], parents[1:]...)
|
||||
}
|
||||
if len(parents) == 1 {
|
||||
writeKey(buf, parents[0], parents[1:]...)
|
||||
writeTableHeaders(buf, parents[0], parents[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func mustValidateKey(key string) {
|
||||
for _, b := range key {
|
||||
func mustValidateTableName(tableName string) {
|
||||
for _, b := range tableName {
|
||||
if !unicode.IsLetter(b) && b != '.' && b != '_' {
|
||||
panic("key must contain only letters, bad character: " + string(b))
|
||||
panic("table name must contain only letters, bad character: " + string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shouldSkip(field reflect.StructField) (skip bool) {
|
||||
defer func() {
|
||||
if skip {
|
||||
print("should skip: " + field.Name + " (tag: " + field.Tag.Get("toml") + ")? ")
|
||||
println(strconv.FormatBool(skip))
|
||||
}
|
||||
}()
|
||||
if !field.IsExported() || field.Tag.Get("toml") == "-" {
|
||||
skip = true
|
||||
return
|
||||
|
@ -62,13 +56,13 @@ func shouldSkip(field reflect.StructField) (skip bool) {
|
|||
return
|
||||
}
|
||||
|
||||
func writeKey(buf *pool.Buffer, key string, subkeys ...string) {
|
||||
mustValidateKey(key)
|
||||
func writeTableHeaders(buf *pool.Buffer, tableName string, subTableNames ...string) {
|
||||
mustValidateTableName(tableName)
|
||||
_, _ = buf.WriteString("[")
|
||||
_, _ = buf.WriteString(key)
|
||||
if len(subkeys) > 0 {
|
||||
for _, sub := range subkeys {
|
||||
mustValidateKey(sub)
|
||||
_, _ = buf.WriteString(tableName)
|
||||
if len(subTableNames) > 0 {
|
||||
for _, sub := range subTableNames {
|
||||
mustValidateTableName(sub)
|
||||
_, _ = buf.WriteString(".")
|
||||
_, _ = buf.WriteString(sub)
|
||||
}
|
||||
|
@ -86,7 +80,7 @@ func handlePanic(err error) error {
|
|||
panic(r)
|
||||
}
|
||||
ve := &reflect.ValueError{}
|
||||
if !errors.As(r.(error), &ve) {
|
||||
if !errors.As(r.(error), &ve) { //golint:forcetypeassert
|
||||
panic(r)
|
||||
}
|
||||
if err == nil {
|
||||
|
@ -123,6 +117,7 @@ func handleBottomLevelField(field reflect.StructField, ref reflect.Value, buf *p
|
|||
}
|
||||
_, _ = buf.WriteString(field.Tag.Get("toml"))
|
||||
_, _ = buf.WriteString(" = ")
|
||||
//goland:noinspection GoSwitchMissingCasesForIotaConsts
|
||||
switch ref.Kind() {
|
||||
case reflect.String:
|
||||
if err := handleString(ref.String(), buf); err != nil {
|
||||
|
@ -135,7 +130,8 @@ func handleBottomLevelField(field reflect.StructField, ref reflect.Value, buf *p
|
|||
case reflect.Bool:
|
||||
_, _ = buf.WriteString(strconv.FormatBool(ref.Bool()))
|
||||
case reflect.Slice:
|
||||
switch ref.Type().Elem().Kind() {
|
||||
//goland:noinspection GoSwitchMissingCasesForIotaConsts
|
||||
switch ref.Type().Elem().Kind() { //nolint:exhaustive
|
||||
case reflect.String:
|
||||
if ref.Len() == 0 {
|
||||
return nil
|
||||
|
@ -150,6 +146,18 @@ func handleBottomLevelField(field reflect.StructField, ref reflect.Value, buf *p
|
|||
}
|
||||
}
|
||||
_, _ = buf.WriteString("]")
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if ref.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
_, _ = buf.WriteString("[")
|
||||
for i := 0; i < ref.Len(); i++ {
|
||||
_, _ = buf.WriteString(strconv.FormatInt(ref.Index(i).Int(), 10))
|
||||
if i < ref.Len()-1 {
|
||||
_, _ = buf.WriteString(", ")
|
||||
}
|
||||
}
|
||||
_, _ = buf.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,14 +167,6 @@ func handleBottomLevelField(field reflect.StructField, ref reflect.Value, buf *p
|
|||
}
|
||||
|
||||
func allChildrenEmpty(sub reflect.Value) (empty bool, err error) {
|
||||
/* var additionalMsg string
|
||||
defer func() {
|
||||
debugMsg := "allChildrenEmpty in '" + sub.Type().Name() + "'? " + strconv.FormatBool(empty)
|
||||
if additionalMsg != "" {
|
||||
debugMsg += ": " + additionalMsg
|
||||
}
|
||||
println(debugMsg)
|
||||
}()*/
|
||||
if sub.Kind() != reflect.Struct {
|
||||
return false, errors.New("input to allChildrenEmpty must be a struct")
|
||||
}
|
||||
|
@ -194,24 +194,22 @@ func (sub *subStruct) handleSubStructField(field reflect.StructField, ref reflec
|
|||
return nil
|
||||
}
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
println("pointer: " + field.Name)
|
||||
ref = ref.Elem()
|
||||
if !ref.IsValid() {
|
||||
println("invalid pointer: " + field.Name)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ft := ref.Type()
|
||||
|
||||
switch ft.Kind() {
|
||||
switch ft.Kind() { //nolint:exhaustive
|
||||
case reflect.Struct:
|
||||
child := &subStruct{name: field.Tag.Get("toml"), ref: ref, parent: sub}
|
||||
if empty, err := allChildrenEmpty(ref); empty || err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = buf.WriteString("\n")
|
||||
child.writeKey(buf)
|
||||
child.writeTableHeader(buf)
|
||||
for i := 0; i < ft.NumField(); i++ {
|
||||
if err := child.handleSubStructField(ft.Field(i), ref.Field(i), buf); err != nil {
|
||||
return err
|
||||
|
@ -248,7 +246,7 @@ func handleFieldTopLevel(field reflect.StructField, ref reflect.Value, buf *pool
|
|||
}
|
||||
|
||||
child := &subStruct{name: field.Tag.Get("toml"), parent: nil, ref: ref}
|
||||
child.writeKey(buf)
|
||||
child.writeTableHeader(buf)
|
||||
|
||||
for i := 0; i < ref.NumField(); i++ {
|
||||
if err := child.handleSubStructField(ref.Type().Field(i), ref.Field(i), buf); err != nil {
|
||||
|
@ -285,7 +283,3 @@ func MarshalTOML(v interface{}) (data []byte, err error) {
|
|||
|
||||
return bytes.TrimSpace(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
func UnmarshalTOML(data []byte, v interface{}) error {
|
||||
return nil
|
||||
}
|
|
@ -2,8 +2,11 @@ package toml
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -17,57 +20,55 @@ var (
|
|||
testResErrFmt = "\n" + wantedErr + "\n%s\n" + actualErr + "\n%s\n" + divErr + "\n"
|
||||
)
|
||||
|
||||
type Test struct {
|
||||
Name string
|
||||
UnmarshalInput []byte
|
||||
UnmarshalOutput any
|
||||
MarshalInput any
|
||||
MarshalOutput []byte
|
||||
wantError error
|
||||
type test struct {
|
||||
Name string
|
||||
Unmarshaled any
|
||||
Marshaled []byte
|
||||
wantError error
|
||||
}
|
||||
|
||||
type Geets struct {
|
||||
Geeters string `toml:"geeters"`
|
||||
type TestGeets struct {
|
||||
Geeters string `toml:"geeters"`
|
||||
YeetIndex []int `toml:"yeet_index"`
|
||||
}
|
||||
type McGeeParameters struct {
|
||||
Username string `toml:"username"`
|
||||
SkipTag string `toml:"-"`
|
||||
SubGeet *Geets `toml:"sub_geet"`
|
||||
type TestMcGeeParameters struct {
|
||||
Username string `toml:"username"`
|
||||
SkipTag string `toml:"-"`
|
||||
SubGeet *TestGeets `toml:"sub_geet"`
|
||||
}
|
||||
type YeetersonParameters struct {
|
||||
ServerName string `toml:"server_name"`
|
||||
DenyList []string `toml:"deny_list"`
|
||||
PortNumber int `toml:"port_number"`
|
||||
YeetMode bool `toml:"yeet_mode"`
|
||||
McGee McGeeParameters `toml:"mcgeet"`
|
||||
unexported string `toml:"unexported"` //golint:unused
|
||||
type TestYeetersonParameters struct {
|
||||
ServerName string `toml:"server_name"`
|
||||
DenyList []string `toml:"deny_list"`
|
||||
PortNumber int `toml:"port_number"`
|
||||
YeetMode bool `toml:"yeet_mode"`
|
||||
McGee TestMcGeeParameters `toml:"mcgeet"`
|
||||
unexported string `toml:"unexported"` //golint:unused
|
||||
}
|
||||
type Parameters struct {
|
||||
Yeeterson YeetersonParameters `toml:"yeet"`
|
||||
McGee McGeeParameters `toml:"mcgee"`
|
||||
SkipMe string `toml:"-"`
|
||||
type parameters struct {
|
||||
Yeeterson TestYeetersonParameters `toml:"yeet"`
|
||||
McGee TestMcGeeParameters `toml:"mcgee"`
|
||||
SkipMe string `toml:"-"`
|
||||
|
||||
skipMe string `toml:"skip_me"` //golint:unused
|
||||
}
|
||||
|
||||
var simpleYeeterson = YeetersonParameters{
|
||||
var simpleYeeterson = TestYeetersonParameters{
|
||||
ServerName: "yeeterson",
|
||||
DenyList: []string{"yeet", "yeeterson", "yeeterson.com"},
|
||||
PortNumber: 8080,
|
||||
YeetMode: true,
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func TestMarshalTOML(t *testing.T) {
|
||||
test1 := Test{
|
||||
var (
|
||||
test1 = test{
|
||||
Name: "simple",
|
||||
MarshalInput: Parameters{
|
||||
Unmarshaled: parameters{
|
||||
Yeeterson: simpleYeeterson,
|
||||
McGee: McGeeParameters{
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "mcgee",
|
||||
},
|
||||
},
|
||||
MarshalOutput: []byte(`[yeet]
|
||||
Marshaled: []byte(`[yeet]
|
||||
server_name = "yeeterson"
|
||||
deny_list = ["yeet", "yeeterson", "yeeterson.com"]
|
||||
port_number = 8080
|
||||
|
@ -77,22 +78,22 @@ yeet_mode = true
|
|||
username = "mcgee"`),
|
||||
}
|
||||
|
||||
test2 := Test{
|
||||
Name: "with empty string, negative number, spaced strings, and punctuation",
|
||||
MarshalInput: Parameters{
|
||||
Yeeterson: YeetersonParameters{
|
||||
test2 = test{
|
||||
Name: "with empty string, negative number, spaced strings, punctuation",
|
||||
Unmarshaled: parameters{
|
||||
Yeeterson: TestYeetersonParameters{
|
||||
ServerName: "",
|
||||
DenyList: []string{"yeet it."},
|
||||
PortNumber: -5,
|
||||
YeetMode: false,
|
||||
},
|
||||
McGee: McGeeParameters{
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "the yeet guy",
|
||||
SkipTag: "skip me",
|
||||
},
|
||||
SkipMe: "skip me",
|
||||
},
|
||||
MarshalOutput: []byte(`[yeet]
|
||||
Marshaled: []byte(`[yeet]
|
||||
server_name = ""
|
||||
deny_list = ["yeet it."]
|
||||
port_number = -5
|
||||
|
@ -102,25 +103,25 @@ yeet_mode = false
|
|||
username = "the yeet guy"`),
|
||||
}
|
||||
|
||||
yeetersonWithChild := YeetersonParameters{
|
||||
yeetersonWithChild = TestYeetersonParameters{
|
||||
ServerName: simpleYeeterson.ServerName,
|
||||
DenyList: simpleYeeterson.DenyList,
|
||||
PortNumber: simpleYeeterson.PortNumber,
|
||||
YeetMode: simpleYeeterson.YeetMode,
|
||||
McGee: McGeeParameters{
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "Yeeterson McGeeterson",
|
||||
},
|
||||
}
|
||||
|
||||
test3 := Test{
|
||||
test3 = test{
|
||||
Name: "with sub-structs",
|
||||
MarshalInput: Parameters{
|
||||
Unmarshaled: parameters{
|
||||
Yeeterson: yeetersonWithChild,
|
||||
McGee: McGeeParameters{
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "mcgee",
|
||||
},
|
||||
},
|
||||
MarshalOutput: []byte(`[yeet]
|
||||
Marshaled: []byte(`[yeet]
|
||||
server_name = "yeeterson"
|
||||
deny_list = ["yeet", "yeeterson", "yeeterson.com"]
|
||||
port_number = 8080
|
||||
|
@ -133,49 +134,72 @@ username = "Yeeterson McGeeterson"
|
|||
username = "mcgee"`),
|
||||
}
|
||||
|
||||
test4 := Test{
|
||||
test4 = test{
|
||||
Name: "with empty structs",
|
||||
MarshalInput: Parameters{
|
||||
Yeeterson: YeetersonParameters{},
|
||||
McGee: McGeeParameters{
|
||||
Unmarshaled: parameters{
|
||||
Yeeterson: TestYeetersonParameters{},
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "mcgeets",
|
||||
SubGeet: &Geets{},
|
||||
SubGeet: &TestGeets{},
|
||||
},
|
||||
},
|
||||
MarshalOutput: []byte(`[mcgee]
|
||||
Marshaled: []byte(`[mcgee]
|
||||
username = "mcgeets"`),
|
||||
}
|
||||
|
||||
test5 := Test{
|
||||
test5 = test{
|
||||
Name: "with pointer struct",
|
||||
MarshalInput: Parameters{
|
||||
Yeeterson: YeetersonParameters{},
|
||||
McGee: McGeeParameters{
|
||||
Unmarshaled: parameters{
|
||||
Yeeterson: TestYeetersonParameters{},
|
||||
McGee: TestMcGeeParameters{
|
||||
Username: "geetsies",
|
||||
SubGeet: &Geets{Geeters: "geets"},
|
||||
SubGeet: &TestGeets{Geeters: "geets", YeetIndex: []int{1, 2, 3}},
|
||||
},
|
||||
},
|
||||
MarshalOutput: []byte(`[mcgee]
|
||||
Marshaled: []byte(`[mcgee]
|
||||
username = "geetsies"
|
||||
|
||||
[mcgee.sub_geet]
|
||||
geeters = "geets"`),
|
||||
geeters = "geets"
|
||||
yeet_index = [1, 2, 3]`),
|
||||
}
|
||||
|
||||
tests := []Test{test1, test2, test3, test4, test5}
|
||||
tests = []test{test1, test2, test3, test4, test5}
|
||||
)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
output, err := MarshalTOML(test.MarshalInput)
|
||||
if !errors.Is(err, test.wantError) {
|
||||
//nolint:funlen
|
||||
func TestMarshalTOML(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
output, err := MarshalTOML(tt.Unmarshaled)
|
||||
if !errors.Is(err, tt.wantError) {
|
||||
errWantString := "nil"
|
||||
if test.wantError != nil {
|
||||
errWantString = test.wantError.Error()
|
||||
if tt.wantError != nil {
|
||||
errWantString = tt.wantError.Error()
|
||||
}
|
||||
t.Errorf(testResErrFmt, errWantString, err)
|
||||
}
|
||||
if string(output) != string(test.MarshalOutput) {
|
||||
t.Errorf(testResFmt, test.MarshalOutput, output)
|
||||
if string(output) != string(tt.Marshaled) {
|
||||
t.Errorf(testResFmt, tt.Marshaled, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalTOML(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
output := new(parameters)
|
||||
err := UnmarshalTOML(tt.Marshaled, output)
|
||||
if !errors.Is(err, tt.wantError) {
|
||||
errWantString := "nil"
|
||||
if tt.wantError != nil {
|
||||
errWantString = tt.wantError.Error()
|
||||
}
|
||||
t.Errorf(testResErrFmt, errWantString, err)
|
||||
}
|
||||
if !reflect.DeepEqual(*output, tt.Unmarshaled) {
|
||||
t.Errorf(testResFmt, tt.Unmarshaled, spew.Sdump(*output))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue