You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							126 lines
						
					
					
						
							2.6 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							126 lines
						
					
					
						
							2.6 KiB
						
					
					
				| 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 | |
| 	} | |
| }
 |