Skip to content

Commit

Permalink
Implement the unsigned integer type (#40)
Browse files Browse the repository at this point in the history
This implements encoding and decoding unsigned integers.
  • Loading branch information
zorchenhimer authored Jun 15, 2021
1 parent 54451b9 commit 2df3b02
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 35 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ people := []struct {
FirstName string `fixed:"6,15"`
LastName string `fixed:"16,25"`
Grade float64 `fixed:"26,30"`
Age uint `fixed:"31,33"`
}{
{1, "Ian", "Lopshire", 99.5},
{1, "Ian", "Lopshire", 99.5, 20},
}

data, err := fixedwidth.Marshal(people)
Expand All @@ -36,7 +37,7 @@ if err != nil {
}
fmt.Printf("%s", data)
// Output:
// 1 Ian Lopshire 99.50
// 1 Ian Lopshire 99.5020
```

### Decode
Expand All @@ -47,13 +48,14 @@ var people []struct {
FirstName string `fixed:"6,15"`
LastName string `fixed:"16,25"`
Grade float64 `fixed:"26,30"`
Age uint `fixed:"31,33"`
}

// define some fixed-with data to parse
data := []byte("" +
"1 Ian Lopshire 99.50" + "\n" +
"2 John Doe 89.50" + "\n" +
"3 Jane Doe 79.50" + "\n")
"1 Ian Lopshire 99.50 20" + "\n" +
"2 John Doe 89.50 21" + "\n" +
"3 Jane Doe 79.50 22" + "\n")


err := fixedwidth.Unmarshal(data, &people)
Expand All @@ -65,9 +67,9 @@ fmt.Printf("%+v\n", people[0])
fmt.Printf("%+v\n", people[1])
fmt.Printf("%+v\n", people[2])
// Output:
//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5}
//{ID:2 FirstName:John LastName:Doe Grade:89.5}
//{ID:3 FirstName:Jane LastName:Doe Grade:79.5}
//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5 Age:20}
//{ID:2 FirstName:John LastName:Doe Grade:89.5 Age:21}
//{ID:3 FirstName:Jane LastName:Doe Grade:79.5 Age:22}
```

It is also possible to read data incrementally
Expand Down
14 changes: 14 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ func newValueSetter(t reflect.Type) valueSetter {
return floatSetter(32)
case reflect.Float64:
return floatSetter(64)
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
return uintSetter
}
return unknownSetter
}
Expand Down Expand Up @@ -396,3 +398,15 @@ func floatSetter(bitSize int) valueSetter {
return nil
}
}

func uintSetter(v reflect.Value, raw rawValue) error {
if len(raw.data) < 1 {
return nil
}
i, err := strconv.ParseUint(raw.data, 10, 64)
if err != nil {
return err
}
v.SetUint(uint64(i))
return nil
}
56 changes: 36 additions & 20 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ func ExampleUnmarshal() {
FirstName string `fixed:"6,15"`
LastName string `fixed:"16,25"`
Grade float64 `fixed:"26,30"`
Age uint `fixed:"31,33"`
}

// define some fixed-with data to parse
data := []byte("" +
"1 Ian Lopshire 99.50" + "\n" +
"2 John Doe 89.50" + "\n" +
"3 Jane Doe 79.50" + "\n")
"1 Ian Lopshire 99.50 20" + "\n" +
"2 John Doe 89.50 21" + "\n" +
"3 Jane Doe 79.50 22" + "\n")

err := Unmarshal(data, &people)
if err != nil {
Expand All @@ -34,9 +35,9 @@ func ExampleUnmarshal() {
fmt.Printf("%+v\n", people[1])
fmt.Printf("%+v\n", people[2])
// Output:
//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5}
//{ID:2 FirstName:John LastName:Doe Grade:89.5}
//{ID:3 FirstName:Jane LastName:Doe Grade:79.5}
//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5 Age:20}
//{ID:2 FirstName:John LastName:Doe Grade:89.5 Age:21}
//{ID:3 FirstName:Jane LastName:Doe Grade:79.5 Age:22}
}

func TestUnmarshal(t *testing.T) {
Expand All @@ -46,6 +47,7 @@ func TestUnmarshal(t *testing.T) {
Int int `fixed:"6,10"`
Float float64 `fixed:"11,15"`
TextUnmarshaler EncodableString `fixed:"16,20"`
Uint uint `fixed:"21,25"`
}
for _, tt := range []struct {
name string
Expand All @@ -56,45 +58,45 @@ func TestUnmarshal(t *testing.T) {
}{
{
name: "Slice Case (no trailing new line)",
rawValue: []byte("foo 123 1.2 bar" + "\n" + "bar 321 2.1 foo"),
rawValue: []byte("foo 123 1.2 bar 12345" + "\n" + "bar 321 2.1 foo 54321"),
target: &[]allTypes{},
expected: &[]allTypes{
{"foo", 123, 1.2, EncodableString{"bar", nil}},
{"bar", 321, 2.1, EncodableString{"foo", nil}},
{"foo", 123, 1.2, EncodableString{"bar", nil}, uint(12345)},
{"bar", 321, 2.1, EncodableString{"foo", nil}, uint(54321)},
},
shouldErr: false,
},
{
name: "Slice Case (trailing new line)",
rawValue: []byte("foo 123 1.2 bar" + "\n" + "bar 321 2.1 foo" + "\n"),
rawValue: []byte("foo 123 1.2 bar 12345" + "\n" + "bar 321 2.1 foo 54321" + "\n"),
target: &[]allTypes{},
expected: &[]allTypes{
{"foo", 123, 1.2, EncodableString{"bar", nil}},
{"bar", 321, 2.1, EncodableString{"foo", nil}},
{"foo", 123, 1.2, EncodableString{"bar", nil}, uint(12345)},
{"bar", 321, 2.1, EncodableString{"foo", nil}, uint(54321)},
},
shouldErr: false,
},
{
name: "Slice Case (blank line mid file)",
rawValue: []byte("foo 123 1.2 bar" + "\n" + "\n" + "bar 321 2.1 foo" + "\n"),
rawValue: []byte("foo 123 1.2 bar 12345" + "\n" + "\n" + "bar 321 2.1 foo 54321" + "\n"),
target: &[]allTypes{},
expected: &[]allTypes{
{"foo", 123, 1.2, EncodableString{"bar", nil}},
{"", 0, 0, EncodableString{"", nil}},
{"bar", 321, 2.1, EncodableString{"foo", nil}},
{"foo", 123, 1.2, EncodableString{"bar", nil}, uint(12345)},
{"", 0, 0, EncodableString{"", nil}, uint(0)},
{"bar", 321, 2.1, EncodableString{"foo", nil}, uint(54321)},
},
shouldErr: false,
},
{
name: "Basic Struct Case",
rawValue: []byte("foo 123 1.2 bar"),
rawValue: []byte("foo 123 1.2 bar 12345"),
target: &allTypes{},
expected: &allTypes{"foo", 123, 1.2, EncodableString{"bar", nil}},
expected: &allTypes{"foo", 123, 1.2, EncodableString{"bar", nil}, uint(12345)},
shouldErr: false,
},
{
name: "Unmarshal Error",
rawValue: []byte("foo nan ddd bar"),
rawValue: []byte("foo nan ddd bar baz"),
target: &allTypes{},
expected: &allTypes{},
shouldErr: true,
Expand All @@ -108,7 +110,7 @@ func TestUnmarshal(t *testing.T) {
},
{
name: "Invalid Target",
rawValue: []byte("foo 123 1.2 bar"),
rawValue: []byte("foo 123 1.2 bar baz"),
target: allTypes{},
expected: allTypes{},
shouldErr: true,
Expand Down Expand Up @@ -257,6 +259,20 @@ func TestNewValueSetter(t *testing.T) {
{"int16", []byte("1"), int16(1), false},
{"int32", []byte("1"), int32(1), false},
{"int64", []byte("1"), int64(1), false},

{"uint", []byte("1"), uint(1), false},
{"uint zero", []byte("0"), uint(0), false},
{"uint empty", []byte(""), uint(0), false},
{"*uint", []byte("1"), uintp(1), false},
{"*uint zero", []byte("0"), uintp(0), false},
{"*uint empty", []byte(""), (*uint)(nil), false},
{"uint Invalid", []byte("foo"), uint(0), true},
{"uint Negative", []byte("-1"), uint(0), true},

{"uint8", []byte("1"), uint8(1), false},
{"uint16", []byte("1"), uint16(1), false},
{"uint32", []byte("1"), uint32(1), false},
{"uint64", []byte("1"), uint64(1), false},
} {
t.Run(tt.name, func(t *testing.T) {
// ensure we have an addressable target
Expand Down
13 changes: 10 additions & 3 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
// In order for a type to be encodable, it must implement
// the encoding.TextMarshaler interface or be based on one
// of the following builtin types: string, int, int64,
// int32, int16, int8, float64, float32, or struct.
// Pointers to encodable types and interfaces containing
// encodable types are also encodable.
// int32, int16, int8, uint, uint64, uint32, uint16,
// uint8, float64, float32, or struct. Pointers to
// encodable types and interfaces containing encodable
// types are also encodable.
//
// nil pointers and interfaces will be omitted. zero vales
// will be encoded normally.
Expand Down Expand Up @@ -152,6 +153,8 @@ func newValueEncoder(t reflect.Type) valueEncoder {
return floatEncoder(2, 64)
case reflect.Float32:
return floatEncoder(2, 32)
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
return uintEncoder
}
return unknownTypeEncoder(t)
}
Expand Down Expand Up @@ -238,3 +241,7 @@ func unknownTypeEncoder(t reflect.Type) valueEncoder {
return nil, &MarshalInvalidTypeError{typeName: t.Name()}
}
}

func uintEncoder(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatUint(v.Uint(), 10)), nil
}
17 changes: 13 additions & 4 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ func ExampleMarshal() {
FirstName string `fixed:"6,15"`
LastName string `fixed:"16,25"`
Grade float64 `fixed:"26,30"`
Age uint `fixed:"31,33"`
}{
{1, "Ian", "Lopshire", 99.5},
{1, "Ian", "Lopshire", 99.5, 20},
}

data, err := Marshal(people)
Expand All @@ -27,7 +28,7 @@ func ExampleMarshal() {
}
fmt.Printf("%s", data)
// Output:
// 1 Ian Lopshire 99.50
// 1 Ian Lopshire 99.5020
}

func ExampleMarshal_configurableFormatting() {
Expand All @@ -37,8 +38,9 @@ func ExampleMarshal_configurableFormatting() {
FirstName string `fixed:"6,15,right,#"`
LastName string `fixed:"16,25,right,#"`
Grade float64 `fixed:"26,30,right,#"`
Age uint `fixed:"31,33,right,#"`
}{
{1, "Ian", "Lopshire", 99.5},
{1, "Ian", "Lopshire", 99.5, 20},
}

data, err := Marshal(people)
Expand All @@ -47,7 +49,7 @@ func ExampleMarshal_configurableFormatting() {
}
fmt.Printf("%s", data)
// Output:
// ####1#######Ian##Lopshire99.50
// ####1#######Ian##Lopshire99.50#20
}

func TestMarshal(t *testing.T) {
Expand Down Expand Up @@ -209,6 +211,13 @@ func TestNewValueEncoder(t *testing.T) {
{"TextUnmarshaler", EncodableString{"foo", nil}, []byte("foo"), false},
{"TextUnmarshaler interface", interface{}(EncodableString{"foo", nil}), []byte("foo"), false},
{"TextUnmarshaler error", EncodableString{"foo", errors.New("TextUnmarshaler error")}, []byte("foo"), true},

{"uint", uint(123), []byte("123"), false},
{"uint interface", interface{}(uint(123)), []byte("123"), false},
{"uint zero", uint(0), []byte("0"), false},
{"*uint", uintp(123), []byte("123"), false},
{"*uint zero", uintp(0), []byte("0"), false},
{"*uint nil", nilUint, []byte(""), false},
} {
t.Run(tt.name, func(t *testing.T) {
o, err := newValueEncoder(reflect.TypeOf(tt.i))(reflect.ValueOf(tt.i))
Expand Down
2 changes: 2 additions & 0 deletions fixedwidth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var (
nilFloat32 *float32
nilInt *int
nilString *string
nilUint *uint
)

func float64p(v float64) *float64 { return &v }
Expand All @@ -15,6 +16,7 @@ func int32p(v int32) *int32 { return &v }
func int16p(v int16) *int16 { return &v }
func int8p(v int8) *int8 { return &v }
func stringp(v string) *string { return &v }
func uintp(v uint) *uint { return &v }

// EncodableString is a string that implements the encoding TextUnmarshaler and TextMarshaler interface.
// This is useful for testing.
Expand Down

0 comments on commit 2df3b02

Please sign in to comment.