/* SGBlog -- Git-based CGI blogging 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 . */ // Git-based CGI blogging engine 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 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(fmt.Sprintf( "i%04d-%02d-%02d\t\tnull.host\t1%s", yearCur, monthCur, dayCur, CRLF, )) 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, )) } var links bytes.Buffer if offset > 0 { offsetPrev := offset - PageEntries if offsetPrev < 0 { offsetPrev = 0 } links.WriteString(fmt.Sprintf( "1Prev\toffset/%d\t%s\t%d%s", offsetPrev, cfg.GopherDomain, 70, CRLF, )) } if !logEnded { links.WriteString(fmt.Sprintf( "1Next\toffset/%d\t%s\t%d%s", offset+PageEntries, cfg.GopherDomain, 70, CRLF, )) } fmt.Printf( "i%s (%d-%d)\t\tnull.host\t1%s", cfg.Title, offset, offset+PageEntries, CRLF, ) if cfg.AboutURL != "" { fmt.Printf("iAbout: %s\t\tnull.host\t1%s", cfg.AboutURL, CRLF) } fmt.Print(links.String()) fmt.Print(menu.String()) fmt.Print("." + CRLF) } 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)) } for i, comment := range parseComments(getNote(commentsTree, commit.Hash)) { fmt.Printf("%s\ncomment %d:\n%s\n", DashLine, i, comment) } } else { log.Fatalln(errors.New("unknown selector")) } }