/*
-SGBlog -- Git-based CGI blogging engine
+SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Git-based CGI blogging engine
package main
import (
"bufio"
- "bytes"
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"
- "time"
+ "text/template"
"github.com/hjson/hjson-go"
"go.stargrave.org/sgblog"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
)
-const CRLF = "\r\n"
+const (
+ TmplGopherMenu = `{{$Cfg := .Cfg}}{{$CR := .CR}}i{{.Cfg.Title}} ({{.Offset}}-{{.OffsetNext}}) err {{.Cfg.GopherDomain}} 70{{.CR}}
+{{if .Cfg.AboutURL}}hAbout URL:{{.Cfg.AboutURL}} {{.Cfg.GopherDomain}} 70{{.CR}}{{end}}
+{{if .Offset}}1Prev offset/{{.OffsetPrev}} {{.Cfg.GopherDomain}} 70{{.CR}}{{end}}
+{{if not .LogEnded}}1Next offset/{{.OffsetNext}} {{.Cfg.GopherDomain}} 70{{.CR}}{{end -}}
+{{- $yearPrev := 0}}{{$monthPrev := 0}}{{$dayPrev := 0 -}}
+{{- range .Entries }}{{$yearCur := .Commit.Author.When.Year}}{{$monthCur := .Commit.Author.When.Month}}{{$dayCur := .Commit.Author.When.Day -}}
+{{- if or (ne $dayCur $dayPrev) (ne $monthCur $monthPrev) (ne $yearCur $yearPrev)}}{{$dayPrev = $dayCur}}{{$monthPrev = $monthCur}}{{$yearPrev = $yearCur}}
+i{{$yearCur | printf "%04d"}}-{{$monthCur | printf "%02d"}}-{{$dayCur | printf "%02d"}} err {{$Cfg.GopherDomain}} 70{{$CR}}{{end}}
+0[{{.Commit.Author.When.Hour | printf "%02d"}}:{{.Commit.Author.When.Minute | printf "%02d"}}] {{.Title}} ({{.LinesNum}}L){{if .CommentsNum}} ({{.CommentsNum}}C){{end}} /{{.Commit.Hash.String}} {{$Cfg.GopherDomain}} 70{{$CR}}{{end}}
+iGenerated by: SGBlog {{.Version}} err {{.Cfg.GopherDomain}} 70{{.CR}}
+.{{.CR}}
+`
+ TmplGopherEntry = `What: {{.Commit.Hash.String}}
+When: {{.When}}
+------------------------------------------------------------------------
+{{.Commit.Message -}}
+{{- if .Note}}
+------------------------------------------------------------------------
+Note:
+{{.Note}}{{end -}}
+{{- if .Cfg.CommentsEmail}}
+------------------------------------------------------------------------
+leave comment: mailto:{{.Cfg.CommentsEmail}}?subject={{.Commit.Hash.String}}
+{{end}}{{range $idx, $comment := .Comments}}
+------------------------------------------------------------------------
+comment {{$idx}}:
+{{$comment}}
+{{end}}
+------------------------------------------------------------------------
+Generated by: SGBlog {{.Version}}
+`
+)
-var DashLine = strings.Repeat("-", 72)
+type TableMenuEntry struct {
+ Commit *object.Commit
+ Title string
+ LinesNum int
+ CommentsNum int
+}
func serveGopher() {
cfgPath := os.Args[2]
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
+ entries := make([]TableMenuEntry, 0, PageEntries)
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,
- ))
+ entries = append(entries, TableMenuEntry{
+ Commit: commit,
+ Title: lines[0],
+ LinesNum: len(lines) - 2,
+ CommentsNum: len(parseComments(getNote(commentsTree, commit.Hash))),
+ })
}
- 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)
+ tmpl := template.Must(template.New("menu").Parse(TmplGopherMenu))
+ err = tmpl.Execute(os.Stdout, struct {
+ Cfg *Cfg
+ Offset int
+ OffsetPrev int
+ OffsetNext int
+ LogEnded bool
+ Entries []TableMenuEntry
+ Version string
+ CR string
+ }{
+ Cfg: cfg,
+ Offset: offset,
+ OffsetPrev: offset - PageEntries,
+ OffsetNext: offset + PageEntries,
+ LogEnded: logEnded,
+ Entries: entries,
+ Version: sgblog.Version,
+ CR: "\r",
+ })
+ if err != nil {
+ log.Fatalln(err)
}
- fmt.Print(links.String())
- fmt.Print(menu.String())
- fmt.Print("." + CRLF)
+ } else if strings.HasPrefix(selector, "URL:") {
+ selector = selector[len("URL:"):]
+ fmt.Printf(`<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)
}
- 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)
+ tmpl := template.Must(template.New("entry").Parse(TmplGopherEntry))
+ err = tmpl.Execute(os.Stdout, struct {
+ Commit *object.Commit
+ When string
+ Cfg *Cfg
+ Note string
+ Comments []string
+ Version string
+ }{
+ Commit: commit,
+ When: commit.Author.When.Format(sgblog.WhenFmt),
+ Cfg: cfg,
+ Note: string(getNote(notesTree, commit.Hash)),
+ Comments: parseComments(getNote(commentsTree, commit.Hash)),
+ Version: sgblog.Version,
+ })
+ if err != nil {
+ log.Fatalln(err)
}
} else {
log.Fatalln(errors.New("unknown selector"))