/* SGBlog -- Git-backed CGI/inetd blogging/phlogging engine Copyright (C) 2020 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package main import ( "bufio" "bytes" "encoding/json" "errors" "fmt" "io" "io/ioutil" "log" "os" "strconv" "strings" "time" "github.com/hjson/hjson-go" "go.stargrave.org/sgblog" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" ) const CRLF = "\r\n" var DashLine = strings.Repeat("-", 72) func makeI(cfg *Cfg, value string) string { return strings.Join([]string{"i" + value, "err", cfg.GopherDomain, "70", CRLF}, "\t") } func serveGopher() { cfgPath := os.Args[2] cfgRaw, err := ioutil.ReadFile(cfgPath) if err != nil { log.Fatalln(err) } var cfgGeneral map[string]interface{} if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil { log.Fatalln(err) } cfgRaw, err = json.Marshal(cfgGeneral) if err != nil { log.Fatalln(err) } var cfg *Cfg if err = json.Unmarshal(cfgRaw, &cfg); err != nil { log.Fatalln(err) } if cfg.GopherDomain == "" { log.Fatalln("GopherDomain is not configured") } headHash, err := initRepo(cfg) if err != nil { log.Fatalln(err) } scanner := bufio.NewScanner(io.LimitReader(os.Stdin, 1<<8)) if !scanner.Scan() { log.Fatalln(errors.New("no CRLF found")) } selector := scanner.Text() if selector == "" { selector = "offset/0" } if strings.HasPrefix(selector, "offset/") { offset, err := strconv.Atoi(selector[len("offset/"):]) if err != nil { log.Fatalln(err) } repoLog, err := repo.Log(&git.LogOptions{From: *headHash}) if err != nil { log.Fatalln(err) } commitN := 0 for i := 0; i < offset; i++ { if _, err = repoLog.Next(); err != nil { break } commitN++ } logEnded := false var menu bytes.Buffer var yearPrev int var monthPrev time.Month var dayPrev int for i := 0; i < PageEntries; i++ { commit, err := repoLog.Next() if err != nil { logEnded = true break } yearCur, monthCur, dayCur := commit.Author.When.Date() if dayCur != dayPrev || monthCur != monthPrev || yearCur != yearPrev { menu.WriteString(makeI(cfg, fmt.Sprintf( "%04d-%02d-%02d", yearCur, monthCur, dayCur, ))) yearPrev, monthPrev, dayPrev = yearCur, monthCur, dayCur } commitN++ lines := msgSplit(commit.Message) var commentsValue string if l := len(parseComments(getNote(commentsTree, commit.Hash))); l > 0 { commentsValue = fmt.Sprintf(" (%dC)", l) } menu.WriteString(fmt.Sprintf( "0[%02d:%02d] %s (%dL)%s\t/%s\t%s\t%d%s", commit.Author.When.Hour(), commit.Author.When.Minute(), lines[0], len(lines)-2, commentsValue, commit.Hash.String(), cfg.GopherDomain, 70, CRLF, )) } fmt.Print(makeI(cfg, fmt.Sprintf("%s (%d-%d)", cfg.Title, offset, offset+PageEntries))) if cfg.AboutURL != "" { fmt.Printf( "hAbout\tURL:%s\t%s\t%d%s", cfg.AboutURL, cfg.GopherDomain, 70, CRLF, ) } if offset > 0 { offsetPrev := offset - PageEntries if offsetPrev < 0 { offsetPrev = 0 } fmt.Printf( "1Prev\toffset/%d\t%s\t%d%s", offsetPrev, cfg.GopherDomain, 70, CRLF, ) } if !logEnded { fmt.Printf( "1Next\toffset/%d\t%s\t%d%s", offset+PageEntries, cfg.GopherDomain, 70, CRLF, ) } fmt.Print(menu.String()) fmt.Printf(makeI(cfg, "Generated by: SGBlog "+sgblog.Version)) fmt.Print("." + CRLF) } else if strings.HasPrefix(selector, "URL:") { selector = selector[len("URL:"):] fmt.Printf(` Redirect to non-gopher URL Redirecting to %s... `, selector, selector, selector) } else if sha1DigestRe.MatchString(selector) { commit, err := repo.CommitObject(plumbing.NewHash(selector[1:])) if err != nil { log.Fatalln(err) } fmt.Printf( "What: %s\nWhen: %s\n%s\n%s", commit.Hash.String(), commit.Author.When.Format(sgblog.WhenFmt), DashLine, commit.Message, ) notesRaw := getNote(notesTree, commit.Hash) if len(notesRaw) > 0 { fmt.Printf("%s\nNote:\n%s\n", DashLine, string(notesRaw)) } if cfg.CommentsEmail != "" { fmt.Printf( "%s\nleave comment: mailto:%s?subject=%s\n", DashLine, cfg.CommentsEmail, commit.Hash.String(), ) } for i, comment := range parseComments(getNote(commentsTree, commit.Hash)) { fmt.Printf("%s\ncomment %d:\n%s\n", DashLine, i, comment) } fmt.Printf("%s\nGenerated by: SGBlog %s\n", DashLine, sgblog.Version) } else { log.Fatalln(errors.New("unknown selector")) } }