Skip to content

Commit

Permalink
Encode structs
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg committed Aug 15, 2023
1 parent e7edea1 commit 5e39ac0
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 3 deletions.
62 changes: 60 additions & 2 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,69 @@ func (e *Encoder) marshalMap(val reflect.Value) error {
return nil
}

// TODO(cristaloleg): support this.
func (e *Encoder) marshalStruct(_ reflect.Value) error {
func (e *Encoder) marshalStruct(x reflect.Value) error {
dict := make(dictStruct, 0, x.Type().NumField())

dict, err := walkStruct(dict, x)
if err != nil {
return err
}

sort.Sort(dict)

e.buf.WriteByte('d')
for _, def := range dict {
e.marshalString(def.Key)
if err := e.marshal(def.Value.Interface()); err != nil {
return err
}
}
e.buf.WriteByte('e')
return nil
}


type dictStruct []dictPair

type dictPair struct {
Key string
Value reflect.Value
}

func (d dictStruct) Len() int { return len(d) }
func (d dictStruct) Less(i, j int) bool { return d[i].Key < d[j].Key }
func (d dictStruct) Swap(i, j int) { d[i], d[j] = d[j], d[i] }

func walkStruct(dict dictStruct, v reflect.Value) (dictStruct, error) {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
strField := t.Field(i)
field := v.FieldByIndex(strField.Index)

if !field.CanInterface() || isNil(field) {
continue
}

tag, ok := fieldTag(strField)
if !ok {
continue
}

if tag == "" && strField.Anonymous &&
strField.Type.Kind() == reflect.Struct {

var err error
dict, err = walkStruct(dict, field)
if err != nil {
return nil, err
}
} else {
dict = append(dict, dictPair{Key: tag, Value: field})
}
}
return dict, nil
}

func (e *Encoder) marshalDictionaryNew(dict D) error {
if len(dict) == 0 {
e.buf.WriteString("de")
Expand Down
17 changes: 17 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ func TestMarshalMap(t *testing.T) {
testLoopMarshal(t, tcs)
}

func TestMarshalStruct(t *testing.T) {
type foo struct {
A string `bencode:"a-field"`
B int `bencode:"-"`
C bool `bencode:"c-bool-field,omitempty"`
D map[string]int
}

tcs := []marshalTestCase{
{
foo{"aa", 10, true, map[string]int{"x": 42}},
`d1:Dd1:xi42ee7:a-field2:aa12:c-bool-fieldi1ee`, false,
},
}
testLoopMarshal(t, tcs)
}

func TestMarshalPointer(t *testing.T) {
b := true
s := "well"
Expand Down
55 changes: 54 additions & 1 deletion util.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package bencode

import "sort"
import (
"reflect"
"sort"
"strings"
"unicode"
)

func sortStrings(ss []string) {
if len(ss) <= strSliceLen {
Expand All @@ -23,3 +28,51 @@ func sortStrings(ss []string) {
sort.Strings(ss)
}
}

func fieldTag(field reflect.StructField) (string, bool) {
tag := field.Tag.Get("bencode")

var opts string
switch {
case tag == "":
return field.Name, true
case tag == "-":
return "", false
default:
if idx := strings.Index(tag, ","); idx != -1 {
tag, opts = tag[:idx], tag[idx+1:]
}
}

switch {
case strings.Contains(opts, ",omitempty"):
return "", false
case !isValidTag(tag):
return field.Name, true
default:
return tag, true
}
}

func isValidTag(key string) bool {
if key == "" {
return false
}

for _, c := range key {
if c != ' ' && c != '$' && c != '-' && c != '_' && c != '.' &&
!unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
return true
}

func isNil(v reflect.Value) bool {
switch v.Kind() {
case reflect.Interface, reflect.Ptr:
return v.IsNil()
default:
return false
}
}

0 comments on commit 5e39ac0

Please sign in to comment.