moth/pkg/award/award.go

183 lines
3.6 KiB
Go
Raw Normal View History

2020-08-17 11:04:08 -06:00
// Package award defines a MOTH award, and provides tools to use them.
package award
import (
2020-08-21 16:56:52 -06:00
"bytes"
2020-08-17 11:04:08 -06:00
"encoding/json"
"fmt"
"net/url"
2020-08-21 16:56:52 -06:00
"reflect"
"strconv"
2020-08-17 11:04:08 -06:00
"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.
2020-08-17 17:43:57 -06:00
type List []T
2020-08-17 11:04:08 -06:00
type Award interface {
When() int64
TeamID() string
Category() string
Points() int
MarshalJSON() ([]byte, error)
UnMarshalJSON(b []byte) error
}
2020-08-19 15:38:13 -06:00
// Len returns the length of the awards list.
2020-08-17 11:04:08 -06:00
func (awards List) Len() int {
return len(awards)
}
2020-08-19 15:38:13 -06:00
// Less returns true if i was awarded before j.
2020-08-17 11:04:08 -06:00
func (awards List) Less(i, j int) bool {
return awards[i].When < awards[j].When
}
2020-08-19 15:38:13 -06:00
// Swap exchanges the awards in positions i and j.
2020-08-17 11:04:08 -06:00
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.
2020-08-17 17:43:57 -06:00
func Parse(s string) (T, error) {
2020-08-17 11:04:08 -06:00
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 {
2020-08-17 17:43:57 -06:00
return ret, err
2020-08-17 11:04:08 -06:00
} else if n != 4 {
return ret, fmt.Errorf("malformed award string: only parsed %d fields", n)
2020-08-17 11:04:08 -06:00
}
2020-08-17 17:43:57 -06:00
return ret, nil
2020-08-17 11:04:08 -06:00
}
// String returns a log entry string for an award.T.
2020-08-17 17:43:57 -06:00
func (a T) String() string {
2020-08-17 11:04:08 -06:00
return fmt.Sprintf("%d %s %s %d", a.When, a.TeamID, a.Category, a.Points)
}
// Filename returns a string version of an award suitable for a filesystem
func (a T) Filename() string {
return fmt.Sprintf(
"%d-%s-%s-%d.award",
a.When,
url.PathEscape(a.TeamID),
url.PathEscape(a.Category),
a.Points,
)
}
2020-08-17 11:04:08 -06:00
// MarshalJSON returns the award event, encoded as a list.
2020-08-17 17:43:57 -06:00
func (a T) MarshalJSON() ([]byte, error) {
2020-08-17 11:04:08 -06:00
ao := []interface{}{
a.When,
a.TeamID,
a.Category,
a.Points,
}
return json.Marshal(ao)
}
2020-08-21 16:56:52 -06:00
// UnmarshalJSON decodes the JSON string b.
func (a T) UnmarshalJSON(b []byte) error {
r := bytes.NewReader(b)
dec := json.NewDecoder(r)
dec.UseNumber() // Don't use floats
// All this to make sure we get `[`
2020-09-17 19:48:17 -06:00
t, err := dec.Token()
if err != nil {
2020-08-21 16:56:52 -06:00
return err
2020-09-17 19:48:17 -06:00
}
switch token := t.(type) {
case json.Delim:
if token.String() != "[" {
2020-08-21 16:56:52 -06:00
return &json.UnmarshalTypeError{
2020-09-17 19:48:17 -06:00
Value: token.String(),
2020-08-21 16:56:52 -06:00
Type: reflect.TypeOf(a),
Offset: 0,
}
}
2020-09-17 19:48:17 -06:00
default:
return &json.UnmarshalTypeError{
Value: fmt.Sprintf("%v", t),
Type: reflect.TypeOf(a),
Offset: 0,
}
2020-08-21 16:56:52 -06:00
}
var num json.Number
if err := dec.Decode(&num); err != nil {
return err
}
if a.When, err = strconv.ParseInt(string(num), 10, 64); err != nil {
return err
}
if err := dec.Decode(&a.Category); err != nil {
return err
}
if err := dec.Decode(&a.TeamID); err != nil {
return err
}
if err := dec.Decode(&num); err != nil {
return err
}
if a.Points, err = strconv.Atoi(string(num)); err != nil {
return err
}
// All this to make sure we get `]`
2020-09-17 19:48:17 -06:00
t, err = dec.Token()
if err != nil {
2020-08-21 16:56:52 -06:00
return err
2020-09-17 19:48:17 -06:00
}
switch token := t.(type) {
case json.Delim:
if token.String() != "]" {
2020-08-21 16:56:52 -06:00
return &json.UnmarshalTypeError{
2020-09-17 19:48:17 -06:00
Value: token.String(),
2020-08-21 16:56:52 -06:00
Type: reflect.TypeOf(a),
Offset: 0,
}
}
2020-09-17 19:48:17 -06:00
default:
return &json.UnmarshalTypeError{
Value: fmt.Sprintf("%v", t),
Type: reflect.TypeOf(a),
Offset: 0,
}
2020-08-21 16:56:52 -06:00
}
return nil
}
2020-08-17 11:04:08 -06:00
// Equal returns true if two award events represent the same award.
// Timestamps are ignored in this comparison!
2020-08-17 17:43:57 -06:00
func (a T) Equal(o T) bool {
2020-08-17 11:04:08 -06:00
switch {
case a.TeamID != o.TeamID:
return false
case a.Category != o.Category:
return false
case a.Points != o.Points:
return false
}
return true
}