]> Sergey Matveev's repositories - sgblog.git/blobdiff - cmd/sgblog/gopher.go
Templates translation
[sgblog.git] / cmd / sgblog / gopher.go
index a4a69fd261105b44f621e9d7384f0f7c61d6dd06..2c8578fa6d9aaffa8cf0229d73e1f9a1a119713f 100644 (file)
@@ -1,6 +1,6 @@
 /*
-SGBlog -- Git-based CGI/inetd blogging/phlogging engine
-Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
+Copyright (C) 2020-2022 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
@@ -19,49 +19,51 @@ package main
 
 import (
        "bufio"
-       "bytes"
-       "encoding/json"
+       _ "embed"
        "errors"
        "fmt"
        "io"
-       "io/ioutil"
        "log"
+       "net/url"
        "os"
        "strconv"
        "strings"
-       "time"
+       "text/template"
 
-       "github.com/hjson/hjson-go"
+       "github.com/go-git/go-git/v5"
+       "github.com/go-git/go-git/v5/plumbing"
+       "github.com/go-git/go-git/v5/plumbing/object"
+       "github.com/vorlif/spreak"
        "go.stargrave.org/sgblog"
-       "gopkg.in/src-d/go-git.v4"
-       "gopkg.in/src-d/go-git.v4/plumbing"
 )
 
-const CRLF = "\r\n"
+var (
+       //go:embed gopher-menu.tmpl
+       TmplGopherMenuRaw string
+       //go:embed gopher-entry.tmpl
+       TmplGopherEntryRaw string
 
-var DashLine = strings.Repeat("-", 72)
+       TmplGopherMenu  = template.Must(template.New("gopher-menu").Parse(TmplGopherMenuRaw))
+       TmplGopherEntry = template.Must(template.New("gopher-entry").Parse(TmplGopherEntryRaw))
+)
 
-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)
+type TableMenuEntry struct {
+       Commit      *object.Commit
+       Title       string
+       LinesNum    int
+       CommentsNum int
+       Topics      []string
+}
+
+func serveGopher(cfgPath string) {
+       cfg, err := readCfg(cfgPath)
        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")
        }
+       initLocalizer(cfg.Lang)
 
        headHash, err := initRepo(cfg)
        if err != nil {
@@ -76,8 +78,54 @@ func serveGopher() {
        if selector == "" {
                selector = "offset/0"
        }
-       if strings.HasPrefix(selector, "offset/") {
-               offset, err := strconv.Atoi(selector[len("offset/"):])
+       selectorParts := strings.Split(selector, "/")
+       if strings.HasPrefix(selector, "URL:") {
+               selector = selector[len("URL:"):]
+               fmt.Printf(`<!DOCTYPE html>
+<html>
+<head>
+       <meta http-equiv="Refresh" content="1; url=%s" />
+       <title>Redirect to non-gopher URL</title>
+</head>
+<body>
+Redirecting to <a href="%s">%s</a>...
+</body>
+</html>
+`, selector, selector, selector)
+       } else if sha1DigestRe.MatchString(selector) {
+               commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               err = TmplGopherEntry.Execute(os.Stdout, struct {
+                       T            *spreak.Localizer
+                       Commit       *object.Commit
+                       When         string
+                       Cfg          *Cfg
+                       Note         string
+                       Comments     []string
+                       Topics       []string
+                       Version      string
+                       TitleEscaped string
+               }{
+                       T:        localizer,
+                       Commit:   commit,
+                       When:     commit.Author.When.Format(sgblog.WhenFmt),
+                       Cfg:      cfg,
+                       Note:     string(sgblog.GetNote(repo, notesTree, commit.Hash)),
+                       Comments: sgblog.ParseComments(sgblog.GetNote(repo, commentsTree, commit.Hash)),
+                       Topics:   sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)),
+                       Version:  sgblog.Version,
+                       TitleEscaped: url.PathEscape(fmt.Sprintf(
+                               "Re: %s (%s)", msgSplit(commit.Message)[0], commit.Hash,
+                       )),
+               })
+               if err != nil {
+                       log.Fatalln(err)
+               }
+       } else if len(selectorParts) > 1 &&
+               selectorParts[len(selectorParts)-2] == "offset" {
+               offset, err := strconv.Atoi(selectorParts[len(selectorParts)-1])
                if err != nil {
                        log.Fatalln(err)
                }
@@ -85,103 +133,85 @@ func serveGopher() {
                if err != nil {
                        log.Fatalln(err)
                }
-               commitN := 0
-               for i := 0; i < offset; i++ {
-                       if _, err = repoLog.Next(); err != nil {
-                               break
+               topicsCache, err := getTopicsCache(cfg, repoLog)
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               repoLog, err = repo.Log(&git.LogOptions{From: *headHash})
+               if err != nil {
+                       log.Fatalln(err)
+               }
+
+               var topic string
+               if len(selectorParts) == 3 {
+                       topic = selectorParts[0]
+               }
+               var commits CommitIterNext
+               if topic == "" {
+                       for i := 0; i < offset; i++ {
+                               if _, err = repoLog.Next(); err != nil {
+                                       break
+                               }
+                       }
+                       commits = repoLog
+               } else {
+                       hashes := topicsCache[topic]
+                       if hashes == nil {
+                               log.Fatalln(errors.New("no posts with that topic"))
                        }
-                       commitN++
+                       if len(hashes) > offset {
+                               hashes = hashes[offset:]
+                       }
+                       commits = &HashesIter{hashes}
                }
 
                logEnded := false
-               var menu bytes.Buffer
-               var yearPrev int
-               var monthPrev time.Month
-               var dayPrev int
+               entries := make([]TableMenuEntry, 0, PageEntries)
                for i := 0; i < PageEntries; i++ {
-                       commit, err := repoLog.Next()
+                       commit, err := commits.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,
-                       ))
+                       entries = append(entries, TableMenuEntry{
+                               Commit:      commit,
+                               Title:       lines[0],
+                               LinesNum:    len(lines) - 2,
+                               CommentsNum: len(sgblog.ParseComments(sgblog.GetNote(repo, commentsTree, commit.Hash))),
+                               Topics:      sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)),
+                       })
                }
-
-               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)
+               offsetPrev := offset - PageEntries
+               if offsetPrev < 0 {
+                       offsetPrev = 0
                }
-               fmt.Print(links.String())
-               fmt.Print(menu.String())
-               fmt.Print("." + CRLF)
-       } else if sha1DigestRe.MatchString(selector) {
-               commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
+               err = TmplGopherMenu.Execute(os.Stdout, struct {
+                       T          *spreak.Localizer
+                       Cfg        *Cfg
+                       Topic      string
+                       Offset     int
+                       OffsetPrev int
+                       OffsetNext int
+                       LogEnded   bool
+                       Entries    []TableMenuEntry
+                       Topics     []string
+                       Version    string
+               }{
+                       T:          localizer,
+                       Cfg:        cfg,
+                       Topic:      topic,
+                       Offset:     offset,
+                       OffsetPrev: offsetPrev,
+                       OffsetNext: offset + PageEntries,
+                       LogEnded:   logEnded,
+                       Entries:    entries,
+                       Topics:     topicsCache.Topics(),
+                       Version:    sgblog.Version,
+               })
                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"))
        }