|
@ -2,14 +2,26 @@ package parsers |
|
|
|
|
|
|
|
|
import ( |
|
|
import ( |
|
|
"errors" |
|
|
"errors" |
|
|
|
|
|
"fmt" |
|
|
"git.aiterp.net/rpdata/api/models" |
|
|
"git.aiterp.net/rpdata/api/models" |
|
|
"strconv" |
|
|
"strconv" |
|
|
"strings" |
|
|
"strings" |
|
|
"time" |
|
|
"time" |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
// ErrNotPost is returned by parsePost if the line is empty or not a post.
|
|
|
|
|
|
var ErrNotPost = errors.New("not a post") |
|
|
|
|
|
|
|
|
type ParseError struct { |
|
|
|
|
|
Line string |
|
|
|
|
|
Problem string |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (e *ParseError) Error() string { |
|
|
|
|
|
return fmt.Sprintf("Unrecognized post: %s (error: %s)", e.Line, e.Problem) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func IsParseError(err error) bool { |
|
|
|
|
|
_, ok := err.(*ParseError) |
|
|
|
|
|
return ok |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log.
|
|
|
// ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log.
|
|
|
var ErrEmptyLog = errors.New("no valid posts found in log") |
|
|
var ErrEmptyLog = errors.New("no valid posts found in log") |
|
@ -56,24 +68,36 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error |
|
|
// Do basic validation
|
|
|
// Do basic validation
|
|
|
line = strings.Trim(line, " \t\n\r") |
|
|
line = strings.Trim(line, " \t\n\r") |
|
|
if len(line) == 0 || !strings.HasPrefix(line, "[") { |
|
|
if len(line) == 0 || !strings.HasPrefix(line, "[") { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "no timestamp", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Parse timestamp
|
|
|
// Parse timestamp
|
|
|
tsEndIndex := strings.IndexByte(line, ']') |
|
|
tsEndIndex := strings.IndexByte(line, ']') |
|
|
if tsEndIndex == -1 || len(line) < tsEndIndex+5 { |
|
|
if tsEndIndex == -1 || len(line) < tsEndIndex+5 { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "incomplete timestamp", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
tsStr := line[1:tsEndIndex] |
|
|
tsStr := line[1:tsEndIndex] |
|
|
tsSplit := strings.Split(tsStr, ":") |
|
|
tsSplit := strings.Split(tsStr, ":") |
|
|
tsUnits := make([]int, len(tsSplit)) |
|
|
tsUnits := make([]int, len(tsSplit)) |
|
|
if len(tsSplit) < 2 { |
|
|
if len(tsSplit) < 2 { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "invalid timestamp", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
for i := range tsSplit { |
|
|
for i := range tsSplit { |
|
|
n, err := strconv.Atoi(tsSplit[i]) |
|
|
n, err := strconv.Atoi(tsSplit[i]) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "invalid number in timestamp", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
tsUnits[i] = n |
|
|
tsUnits[i] = n |
|
@ -94,7 +118,10 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error |
|
|
if line[tsEndIndex+2] == '*' { |
|
|
if line[tsEndIndex+2] == '*' { |
|
|
split := strings.SplitN(line[tsEndIndex+4:], " ", 2) |
|
|
split := strings.SplitN(line[tsEndIndex+4:], " ", 2) |
|
|
if len(split) == 1 { |
|
|
if len(split) == 1 { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "post is empty", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
post := models.Post{ |
|
|
post := models.Post{ |
|
@ -115,7 +142,10 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error |
|
|
} else if line[tsEndIndex+2] == '<' { |
|
|
} else if line[tsEndIndex+2] == '<' { |
|
|
split := strings.SplitN(line[tsEndIndex+2:], " ", 2) |
|
|
split := strings.SplitN(line[tsEndIndex+2:], " ", 2) |
|
|
if len(split) == 1 { |
|
|
if len(split) == 1 { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "post is empty", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
post := models.Post{ |
|
|
post := models.Post{ |
|
@ -134,6 +164,9 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error |
|
|
|
|
|
|
|
|
return post, nil |
|
|
return post, nil |
|
|
} else { |
|
|
} else { |
|
|
return models.Post{}, ErrNotPost |
|
|
|
|
|
|
|
|
return models.Post{}, &ParseError{ |
|
|
|
|
|
Line: line, |
|
|
|
|
|
Problem: "line is neither action nor text post", |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |