From 7e812bd615115b2dc7b9b2c838a2f30efe8bf17a Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Wed, 7 Nov 2018 19:31:51 +0100 Subject: [PATCH] rpdata-logpaste: Added new commandline tool. --- Dockerfile | 1 + cmd/rpdata-logpaste/main.go | 78 ++++++++++++++++++++++ cmd/rpdata-logpaste/post.go | 126 ++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 cmd/rpdata-logpaste/main.go create mode 100644 cmd/rpdata-logpaste/post.go diff --git a/Dockerfile b/Dockerfile index 518cbd5..c2cb8a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logim # TODO: RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport +RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-logpaste ./cmd/rpdata-logpaste ## 2. Distribute # Use alpine linux diff --git a/cmd/rpdata-logpaste/main.go b/cmd/rpdata-logpaste/main.go new file mode 100644 index 0000000..f29b1d0 --- /dev/null +++ b/cmd/rpdata-logpaste/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "flag" + "log" + "os" + "time" + + "git.aiterp.net/rpdata/api/internal/store" + "git.aiterp.net/rpdata/api/models/posts" + + "git.aiterp.net/rpdata/api/models/logs" +) + +var dateStr string +var tzStr string +var channel string +var fileName string + +func main() { + var inputPosts []Post + + flag.StringVar(&dateStr, "date", "2013-04-04", "Date in YYYY-MM-DD form.") + flag.StringVar(&tzStr, "tz", "Europe/Oslo", "Time zone for the date.") + flag.StringVar(&channel, "channel", "#RedrockAgency", "Channel it's set in.") + flag.StringVar(&fileName, "file", "-", "File to read from (- = stdin).") + flag.Parse() + + tz, err := time.LoadLocation(tzStr) + if err != nil { + log.Fatalln("Parse timezone:", err) + } + + date, err := time.ParseInLocation("2006-01-02", dateStr, tz) + if err != nil { + log.Fatalln("Parse date:", err) + } + + err = store.Init() + if err != nil { + log.Fatalln("Init store:", err) + } + + if fileName != "-" { + file, fileErr := os.Open(fileName) + if fileErr != nil { + log.Fatalln("Open file:", fileErr) + } + + inputPosts, err = parsePosts(file, date) + file.Close() + } else { + inputPosts, err = parsePosts(os.Stdin, date) + } + if err != nil { + log.Fatalln("Read inputPosts:", err) + } + + if len(inputPosts) == 0 { + log.Fatalln("Input file contained no posts.") + } + + l, err := logs.Add(inputPosts[0].Time, channel, "", "", "", false) + if err != nil { + log.Fatalln("Add log:", err) + } + + log.Println("Added log", l.ID) + + for _, inputPost := range inputPosts { + p, err := posts.Add(l, inputPost.Time, inputPost.Kind, inputPost.Nick, inputPost.Text) + if err != nil { + log.Fatalln("Add post:", err) + } + + log.Println("Added post", p.ID) + } +} diff --git a/cmd/rpdata-logpaste/post.go b/cmd/rpdata-logpaste/post.go new file mode 100644 index 0000000..fec3640 --- /dev/null +++ b/cmd/rpdata-logpaste/post.go @@ -0,0 +1,126 @@ +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 + } +}