package parser import ( "bytes" "crypto/sha1" "encoding/hex" "errors" "fmt" "math" "strconv" "strings" "git.kleiax.de/homepage/field" ) type PuzzleBank struct { ParserHelper } func (pb *PuzzleBank) Parse(data []byte) error { if pb == nil { return errors.New("nil puzzle bank parser") } var parsed []*field.Field for index, rawLine := range bytes.Split(data, []byte("\n")) { lineNumber := index + 1 line := strings.TrimSpace(string(rawLine)) if line == "" { continue } columns := strings.Fields(line) if len(columns) != 3 { return fmt.Errorf("line %d: %w: expected hash, puzzle and rating", lineNumber, ErrInvalidInput) } if len(columns[0]) != 12 { return fmt.Errorf("line %d: %w: hash has %d characters, expected 12", lineNumber, ErrInvalidInput, len(columns[0])) } if _, err := hex.DecodeString(columns[0]); err != nil { return fmt.Errorf("line %d: %w: invalid hash: %v", lineNumber, ErrInvalidInput, err) } digest := sha1.Sum([]byte(columns[1])) if columns[0] != hex.EncodeToString(digest[:])[:12] { return fmt.Errorf("line %d: %w: hash does not match puzzle", lineNumber, ErrInvalidInput) } rating, err := strconv.ParseFloat(columns[2], 64) if err != nil { return fmt.Errorf("line %d: %w: invalid rating %q: %v", lineNumber, ErrInvalidInput, columns[2], err) } if rating < 0 || math.IsNaN(rating) || math.IsInf(rating, 0) { return fmt.Errorf("line %d: %w: rating must be a finite non-negative number", lineNumber, ErrInvalidInput) } parsedField, err := classicField(columns[1], rating) if err != nil { return fmt.Errorf("line %d: %w", lineNumber, err) } parsed = append(parsed, parsedField) } if len(parsed) == 0 { return ErrNoPuzzles } pb.fields = parsed return nil }