diff --git a/award.go b/award.go new file mode 100644 index 0000000..3e22f50 --- /dev/null +++ b/award.go @@ -0,0 +1,87 @@ +// Package award defines a MOTH award, and provides tools to use them. +package award + +import ( + "encoding/json" + "fmt" + "strings" +) + +// T represents a single award event. +type T struct { + // Unix epoch time of this event + When int64 + TeamID string + Category string + Points int +} + +// List is a collection of award events. +type List []*T + +// Len implements sort.Interface. +func (awards List) Len() int { + return len(awards) +} + +// Less implements sort.Interface. +func (awards List) Less(i, j int) bool { + return awards[i].When < awards[j].When +} + +// Swap implements sort.Interface. +func (awards List) Swap(i, j int) { + tmp := awards[i] + awards[i] = awards[j] + awards[j] = tmp +} + +// Parse parses a string log entry into an award.T. +func Parse(s string) (*T, error) { + ret := T{} + + s = strings.TrimSpace(s) + + n, err := fmt.Sscanf(s, "%d %s %s %d", &ret.When, &ret.TeamID, &ret.Category, &ret.Points) + if err != nil { + return nil, err + } else if n != 4 { + return nil, fmt.Errorf("Malformed award string: only parsed %d fields", n) + } + + return &ret, nil +} + +// String returns a log entry string for an award.T. +func (a *T) String() string { + return fmt.Sprintf("%d %s %s %d", a.When, a.TeamID, a.Category, a.Points) +} + +// MarshalJSON returns the award event, encoded as a list. +func (a *T) MarshalJSON() ([]byte, error) { + if a == nil { + return []byte("null"), nil + } + ao := []interface{}{ + a.When, + a.TeamID, + a.Category, + a.Points, + } + + return json.Marshal(ao) +} + +// Equal returns true if two award events represent the same award. +// Timestamps are ignored in this comparison! +func (a *T) Equal(o *T) bool { + switch { + case a.TeamID != o.TeamID: + return false + case a.Category != o.Category: + return false + case a.Points != o.Points: + return false + } + return true +} diff --git a/award_test.go b/award_test.go new file mode 100644 index 0000000..59a9c88 --- /dev/null +++ b/award_test.go @@ -0,0 +1,77 @@ +package award + +import ( + "sort" + "testing" +) + +func TestAward(t *testing.T) { + entry := "1536958399 1a2b3c4d counting 10" + a, err := Parse(entry) + if err != nil { + t.Error(err) + return + } + if a.TeamID != "1a2b3c4d" { + t.Error("TeamID parsed wrong") + } + if a.Category != "counting" { + t.Error("Category parsed wrong") + } + if a.Points != 10 { + t.Error("Points parsed wrong") + } + + if a.String() != entry { + t.Error("String conversion wonky") + } + + b, err := Parse(entry[2:]) + if err != nil { + t.Error(err) + } + if !a.Equal(b) { + t.Error("Different timestamp events do not compare equal") + } + + c, err := Parse(entry[:len(entry)-1]) + if err != nil { + t.Error(err) + } + if a.Equal(c) { + t.Error("Different pount values compare equal") + } + + if ja, err := a.MarshalJSON(); err != nil { + t.Error(err) + } else if string(ja) != `[1536958399,"1a2b3c4d","counting",10]` { + t.Error("JSON wrong") + } + + if _, err := Parse("bad bad bad 1"); err == nil { + t.Error("Not throwing error on bad timestamp") + } + if _, err := Parse("1 bad bad bad"); err == nil { + t.Error("Not throwing error on bad points") + } +} + +func TestAwardList(t *testing.T) { + a, _ := Parse("1536958399 1a2b3c4d counting 1") + b, _ := Parse("1536958400 1a2b3c4d counting 1") + c, _ := Parse("1536958300 1a2b3c4d counting 1") + list := List{a, b, c} + + if sort.IsSorted(list) { + t.Error("Unsorted list thinks it's sorted") + } + + sort.Stable(list) + if (list[0] != c) || (list[1] != a) || (list[2] != b) { + t.Error("Sorting didn't") + } + + if !sort.IsSorted(list) { + t.Error("Sorted list thinks it isn't") + } +}