|
|
package main
import ( "bufio" "errors" "io" "strconv" "strings" "time" )
// ErrNotPost is returned by parsePost if the line is empty or not a post.
var ErrNotPost = errors.New("not a post")
// ErrTooShort is returned by parsePost if the line too short to be a post.
var ErrTooShort = errors.New("post too short")
// ErrInvalidTimestamp is returned by parsePost if the line is empty or not a post.
var ErrInvalidTimestamp = errors.New("invalid timestamp")
// A Post is a part of a log.
type Post struct { Time time.Time Kind string Nick string Text string }
func parsePosts(reader io.Reader, date time.Time) ([]Post, error) { prev := Post{} posts := make([]Post, 0, 8) bufReader := bufio.NewReader(reader)
for { line, err := bufReader.ReadString('\n') if err != nil && err != io.EOF { return nil, err }
if len(line) > 8 { post, err := parsePost(strings.Trim(line, " \n\r"), date, prev) if err != nil { return nil, err }
posts = append(posts, post) prev = post }
if err == io.EOF { break } }
return posts, nil }
func parsePost(line string, date time.Time, prev Post) (Post, error) { // Do basic validation
line = strings.Trim(line, " \t\n\r") if len(line) == 0 || !strings.HasPrefix(line, "[") { return Post{}, ErrNotPost }
// Parse timestamp
tsEndIndex := strings.IndexByte(line, ']') if tsEndIndex == -1 || len(line) < tsEndIndex+5 { return Post{}, ErrNotPost } tsStr := line[1:tsEndIndex] tsSplit := strings.Split(tsStr, ":") tsUnits := make([]int, len(tsSplit)) if len(tsSplit) < 2 { return Post{}, ErrNotPost } for i := range tsSplit { n, err := strconv.Atoi(tsSplit[i]) if err != nil { return Post{}, ErrNotPost }
tsUnits[i] = n } if len(tsUnits) == 2 { tsUnits = append(tsUnits, 0) }
ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location()) if !prev.Time.IsZero() && prev.Time.After(ts) { ts = ts.AddDate(0, 0, 1) }
if line[tsEndIndex+2] == '*' { split := strings.SplitN(line[tsEndIndex+4:], " ", 2)
post := Post{ Time: ts, Kind: "action", Nick: strings.TrimLeft(split[0], "+@!~"), Text: split[1], }
if post.Nick[0] == '=' { post.Kind = "scene" }
return post, nil } else if line[tsEndIndex+2] == '<' { split := strings.SplitN(line[tsEndIndex+2:], " ", 2)
post := Post{ Time: ts, Kind: "text", Nick: strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"), Text: split[1], }
if post.Nick[0] == '=' { post.Kind = "scene" }
return post, nil } else { return Post{}, ErrNotPost } }
|