package main
import (
+ "bufio"
"bytes"
"compress/gzip"
"encoding/hex"
const (
PageEntries = 50
AtomFeed = "feed.atom"
+ CRLF = "\r\n"
)
var (
"https": struct{}{},
"telnet": struct{}{},
}
+ DashLine = strings.Repeat("-", 72)
)
type TableEntry struct {
CommentsNotesRef string
CommentsEmail string
+
+ GopherDomain string
}
func makeA(href, text string) string {
}
}
-func main() {
- cfgPath := os.Getenv("SGBLOG_CFG")
- if cfgPath == "" {
- log.Fatalln("SGBLOG_CFG is not set")
- }
- pathInfo, exists := os.LookupEnv("PATH_INFO")
- if !exists {
- pathInfo = "/"
+func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
+ var err error
+ repo, err = git.PlainOpen(cfg.GitPath)
+ if err != nil {
+ return nil, err
}
- queryValues, err := url.ParseQuery(os.Getenv("QUERY_STRING"))
+ head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
if err != nil {
- makeErr(err)
+ return nil, err
}
+ headHash := head.Hash()
+ if notes, err := repo.Notes(); err == nil {
+ var notesRef *plumbing.Reference
+ var commentsRef *plumbing.Reference
+ notes.ForEach(func(ref *plumbing.Reference) error {
+ switch string(ref.Name()) {
+ case "refs/notes/commits":
+ notesRef = ref
+ case cfg.CommentsNotesRef:
+ commentsRef = ref
+ }
+ return nil
+ })
+ if notesRef != nil {
+ if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
+ notesTree, _ = commentsCommit.Tree()
+ }
+ }
+ if commentsRef != nil {
+ if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
+ commentsTree, _ = commentsCommit.Tree()
+ }
+ }
+ }
+ return &headHash, nil
+}
+func serveHTTP() {
+ cfgPath := os.Getenv("SGBLOG_CFG")
+ if cfgPath == "" {
+ log.Fatalln("SGBLOG_CFG is not set")
+ }
cfgRaw, err := ioutil.ReadFile(cfgPath)
if err != nil {
makeErr(err)
if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
makeErr(err)
}
+ pathInfo, exists := os.LookupEnv("PATH_INFO")
+ if !exists {
+ pathInfo = "/"
+ }
+ queryValues, err := url.ParseQuery(os.Getenv("QUERY_STRING"))
+ if err != nil {
+ makeErr(err)
+ }
etagHash, err := blake2b.New256(nil)
if err != nil {
atomURL := cfg.AtomBaseURL + cfg.URLPrefix + "/" + AtomFeed
defaultLinks = append(defaultLinks, `<link rel="alternate" title="Atom feed" href="`+atomURL+`" type="application/atom+xml">`)
- repo, err = git.PlainOpen(cfg.GitPath)
- if err != nil {
- makeErr(err)
- }
- head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
+ headHash, err := initRepo(cfg)
if err != nil {
makeErr(err)
}
out.Write(refs.Bytes())
out.Write([]byte("\n"))
} else if pathInfo == "/"+AtomFeed {
- commit, err := repo.CommitObject(head.Hash())
+ commit, err := repo.CommitObject(*headHash)
if err != nil {
makeErr(err)
}
}
os.Stdout.Write(outBuf.Bytes())
}
+
+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
+ for i := 0; i < PageEntries; i++ {
+ commit, err := repoLog.Next()
+ if err != nil {
+ logEnded = true
+ break
+ }
+ 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[%s] %s (%dL)%s\t/%s\t%s\t%d%s",
+ commit.Author.When.Format(sgblog.WhenFmt),
+ 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"))
+ }
+}
+
+func main() {
+ if len(os.Args) == 3 && os.Args[1] == "-gopher" {
+ serveGopher()
+ } else {
+ serveHTTP()
+ }
+}