]> Sergey Matveev's repositories - sgblog.git/commitdiff
sgblog-topics helper command v0.17.0
authorSergey Matveev <stargrave@stargrave.org>
Sat, 1 May 2021 14:01:41 +0000 (17:01 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 1 May 2021 14:01:41 +0000 (17:01 +0300)
cmd/sgblog-topics/main.go [new file with mode: 0644]
cmd/sgblog/gopher.go
cmd/sgblog/http.go
cmd/sgblog/main.go
cmd/sgblog/topics.go
common.go

diff --git a/cmd/sgblog-topics/main.go b/cmd/sgblog-topics/main.go
new file mode 100644 (file)
index 0000000..121380f
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
+Copyright (C) 2020-2021 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
+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 <http://www.gnu.org/licenses/>.
+*/
+
+// Git-backed CGI/inetd blogging/phlogging engine
+package main
+
+import (
+       "flag"
+       "fmt"
+       "log"
+       "sort"
+
+       "github.com/go-git/go-git/v5"
+       "github.com/go-git/go-git/v5/plumbing"
+       "github.com/go-git/go-git/v5/plumbing/object"
+       "go.stargrave.org/sgblog"
+)
+
+func main() {
+       branch := flag.String("branch", "refs/heads/master", "Blog's branch reference name")
+       topicsRefName := flag.String("topics-ref", "refs/notes/topics", "Topics reference name")
+       flag.Usage = func() {
+               fmt.Fprintln(flag.CommandLine.Output(), "Show known topics")
+               flag.PrintDefaults()
+       }
+       flag.Parse()
+       log.SetFlags(0)
+
+       repo, err := git.PlainOpen(".")
+       if err != nil {
+               log.Fatalln(err)
+       }
+       head, err := repo.Reference(plumbing.ReferenceName(*branch), false)
+       if err != nil {
+               log.Fatalln(err)
+       }
+       headHash := head.Hash()
+
+       var topicsRef *plumbing.Reference
+       var topicsTree *object.Tree
+       if notes, err := repo.Notes(); err == nil {
+               notes.ForEach(func(ref *plumbing.Reference) error {
+                       if string(ref.Name()) == *topicsRefName {
+                               topicsRef = ref
+                       }
+                       return nil
+               })
+               if topicsRef != nil {
+                       if topicsCommit, err := repo.CommitObject(topicsRef.Hash()); err == nil {
+                               topicsTree, _ = topicsCommit.Tree()
+                       }
+               }
+       }
+
+       repoLog, err := repo.Log(&git.LogOptions{From: headHash})
+       if err != nil {
+               log.Fatalln(err)
+       }
+       topicsCounter := map[string]int{}
+       for {
+               commit, err := repoLog.Next()
+               if err != nil {
+                       break
+               }
+               for _, topic := range sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)) {
+                       topicsCounter[topic]++
+               }
+       }
+       topics := make([]string, 0, len(topicsCounter))
+       for topic := range topicsCounter {
+               topics = append(topics, topic)
+       }
+       sort.Strings(topics)
+       for _, topic := range topics {
+               fmt.Printf("%s\t%d\n", topic, topicsCounter[topic])
+       }
+}
index b12a2b893e1e1c02e180f70f8368f742d54d157b..f682c41debd460c7185948587ef26ce33ebbdd08 100644 (file)
@@ -137,9 +137,9 @@ Redirecting to <a href="%s">%s</a>...
                        Commit:   commit,
                        When:     commit.Author.When.Format(sgblog.WhenFmt),
                        Cfg:      cfg,
-                       Note:     string(getNote(notesTree, commit.Hash)),
-                       Comments: parseComments(getNote(commentsTree, commit.Hash)),
-                       Topics:   parseTopics(getNote(topicsTree, commit.Hash)),
+                       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,
@@ -202,8 +202,8 @@ Redirecting to <a href="%s">%s</a>...
                                Commit:      commit,
                                Title:       lines[0],
                                LinesNum:    len(lines) - 2,
-                               CommentsNum: len(parseComments(getNote(commentsTree, commit.Hash))),
-                               Topics:      parseTopics(getNote(topicsTree, commit.Hash)),
+                               CommentsNum: len(sgblog.ParseComments(sgblog.GetNote(repo, commentsTree, commit.Hash))),
+                               Topics:      sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)),
                        })
                }
                tmpl := template.Must(template.New("menu").Parse(TmplGopherMenu))
index d039ed38ec6b27b2964cc8811ac7904a6fce895f..7ed1ea6e33219ded65715e5f8524d166a8f6d3c8 100644 (file)
@@ -401,9 +401,9 @@ func serveHTTP() {
                                break
                        }
                        etagHash.Write(commit.Hash[:])
-                       commentsRaw := getNote(commentsTree, commit.Hash)
+                       commentsRaw := sgblog.GetNote(repo, commentsTree, commit.Hash)
                        etagHash.Write(commentsRaw)
-                       topicsRaw := getNote(topicsTree, commit.Hash)
+                       topicsRaw := sgblog.GetNote(repo, topicsTree, commit.Hash)
                        etagHash.Write(topicsRaw)
                        entries = append(entries, TableEntry{
                                Commit:      commit,
@@ -426,8 +426,8 @@ func serveHTTP() {
                                }
                                entry.DomainURLs = append(entry.DomainURLs, makeA(line, u.Host))
                        }
-                       entry.CommentsNum = len(parseComments(entry.CommentsRaw))
-                       entry.Topics = parseTopics(entry.TopicsRaw)
+                       entry.CommentsNum = len(sgblog.ParseComments(entry.CommentsRaw))
+                       entry.Topics = sgblog.ParseTopics(entry.TopicsRaw)
                        entries[i] = entry
                }
                offsetPrev := offset - PageEntries
@@ -533,7 +533,7 @@ func serveHTTP() {
                        }
                        lines := msgSplit(commit.Message)
                        var categories []atom.Category
-                       for _, topic := range parseTopics(getNote(topicsTree, commit.Hash)) {
+                       for _, topic := range sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)) {
                                categories = append(categories, atom.Category{Term: topic})
                        }
                        htmlized := make([]string, 0, len(lines))
@@ -613,7 +613,7 @@ func serveHTTP() {
                        if err != nil {
                                continue
                        }
-                       comments := parseComments(getNote(t, commentedHash))
+                       comments := sgblog.ParseComments(sgblog.GetNote(repo, t, commentedHash))
                        if len(comments) == 0 {
                                continue
                        }
@@ -673,9 +673,9 @@ func serveHTTP() {
                        cfg.AtomBaseURL, cfg.URLPrefix, "/",
                        commit.Hash.String(), "/", AtomCommentsFeed,
                }, "")
-               commentsRaw := getNote(commentsTree, commit.Hash)
+               commentsRaw := sgblog.GetNote(repo, commentsTree, commit.Hash)
                etagHash.Write(commentsRaw)
-               topicsRaw := getNote(topicsTree, commit.Hash)
+               topicsRaw := sgblog.GetNote(repo, topicsTree, commit.Hash)
                etagHash.Write(topicsRaw)
                if strings.HasSuffix(pathInfo, AtomCommentsFeed) {
                        etagHash.Write([]byte("ATOM COMMENTS"))
@@ -686,7 +686,7 @@ func serveHTTP() {
                                date string
                                body []string
                        }
-                       commentsRaw := parseComments(commentsRaw)
+                       commentsRaw := sgblog.ParseComments(commentsRaw)
                        var toSkip int
                        if len(commentsRaw) > PageEntries {
                                toSkip = len(commentsRaw) - PageEntries
@@ -762,7 +762,7 @@ func serveHTTP() {
                        out.Write(data)
                        goto AtomFinish
                }
-               notesRaw := getNote(notesTree, commit.Hash)
+               notesRaw := sgblog.GetNote(repo, notesTree, commit.Hash)
                etagHash.Write(notesRaw)
                checkETag(etagHash)
 
@@ -773,7 +773,7 @@ func serveHTTP() {
                if len(commit.ParentHashes) > 0 {
                        parent = commit.ParentHashes[0].String()
                }
-               commentsParsed := parseComments(commentsRaw)
+               commentsParsed := sgblog.ParseComments(commentsRaw)
                comments := make([]CommentEntry, 0, len(commentsParsed))
                for _, comment := range commentsParsed {
                        lines := strings.Split(comment, "\n")
@@ -813,7 +813,7 @@ func serveHTTP() {
                        Lines:           lines[2:],
                        NoteLines:       notesLines,
                        Comments:        comments,
-                       Topics:          parseTopics(topicsRaw),
+                       Topics:          sgblog.ParseTopics(topicsRaw),
                })
                if err != nil {
                        makeErr(err)
index b373564cbcd6dd31d14efeb17720862af395a392..ef3ff45d0128cf97c112c994f222e8e76a21553a 100644 (file)
@@ -19,22 +19,18 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package main
 
 import (
-       "bytes"
        "crypto/sha1"
        "encoding/json"
        "flag"
        "fmt"
        "io/ioutil"
        "regexp"
-       "sort"
        "strings"
-       "text/scanner"
 
        "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/hjson/hjson-go"
-       "go.cypherpunks.ru/recfile"
 )
 
 const (
@@ -85,75 +81,6 @@ func msgSplit(msg string) []string {
        return lines
 }
 
-func getNote(tree *object.Tree, what plumbing.Hash) []byte {
-       if tree == nil {
-               return nil
-       }
-       var entry *object.TreeEntry
-       var err error
-       paths := make([]string, 3)
-       paths[0] = what.String()
-       paths[1] = paths[0][:2] + "/" + paths[0][2:]
-       paths[2] = paths[1][:4+1] + "/" + paths[1][4+1:]
-       for _, p := range paths {
-               entry, err = tree.FindEntry(p)
-               if err == nil {
-                       break
-               }
-       }
-       if entry == nil {
-               return nil
-       }
-       blob, err := repo.BlobObject(entry.Hash)
-       if err != nil {
-               return nil
-       }
-       r, err := blob.Reader()
-       if err != nil {
-               return nil
-       }
-       data, err := ioutil.ReadAll(r)
-       if err != nil {
-               return nil
-       }
-       return bytes.TrimSuffix(data, []byte{'\n'})
-}
-
-func parseComments(data []byte) []string {
-       comments := []string{}
-       r := recfile.NewReader(bytes.NewReader(data))
-       for {
-               fields, err := r.Next()
-               if err != nil {
-                       break
-               }
-               if len(fields) != 3 ||
-                       fields[0].Name != "From" ||
-                       fields[1].Name != "Date" ||
-                       fields[2].Name != "Body" {
-                       continue
-               }
-               comments = append(comments, fmt.Sprintf(
-                       "%s: %s\n%s: %s\n%s",
-                       fields[0].Name, fields[0].Value,
-                       fields[1].Name, fields[1].Value,
-                       fields[2].Value,
-               ))
-       }
-       return comments
-}
-
-func parseTopics(data []byte) []string {
-       var s scanner.Scanner
-       s.Init(bytes.NewBuffer(data))
-       topics := []string{}
-       for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
-               topics = append(topics, s.TokenText())
-       }
-       sort.Strings(topics)
-       return topics
-}
-
 func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
        var err error
        repo, err = git.PlainOpen(cfg.GitPath)
index 8e34bb43409af0b9350a60434e1ee2b541b43cf4..be70e733ede38a7700a92124200d0a9f8b84619b 100644 (file)
@@ -27,6 +27,7 @@ import (
 
        "github.com/go-git/go-git/v5/plumbing"
        "github.com/go-git/go-git/v5/plumbing/object"
+       "go.stargrave.org/sgblog"
 )
 
 type TopicsCache map[string][]plumbing.Hash
@@ -75,7 +76,7 @@ NoCache:
                if err != nil {
                        break
                }
-               for _, topic := range parseTopics(getNote(topicsTree, commit.Hash)) {
+               for _, topic := range sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)) {
                        cache[topic] = append(cache[topic], commit.Hash)
                }
        }
index ebb5a9d1b7d0f0f93e33af16f09db0c43b475925..d6357fc984e9ba37c1850c05e2f9403135618b7c 100644 (file)
--- a/common.go
+++ b/common.go
@@ -1,7 +1,89 @@
 // SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
 package sgblog
 
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "sort"
+       "text/scanner"
+
+       "github.com/go-git/go-git/v5"
+       "github.com/go-git/go-git/v5/plumbing"
+       "github.com/go-git/go-git/v5/plumbing/object"
+       "go.cypherpunks.ru/recfile"
+)
+
 const (
        Version = "0.17.0"
        WhenFmt = "2006-01-02 15:04:05Z07:00"
 )
+
+func ParseComments(data []byte) []string {
+       comments := []string{}
+       r := recfile.NewReader(bytes.NewReader(data))
+       for {
+               fields, err := r.Next()
+               if err != nil {
+                       break
+               }
+               if len(fields) != 3 ||
+                       fields[0].Name != "From" ||
+                       fields[1].Name != "Date" ||
+                       fields[2].Name != "Body" {
+                       continue
+               }
+               comments = append(comments, fmt.Sprintf(
+                       "%s: %s\n%s: %s\n%s",
+                       fields[0].Name, fields[0].Value,
+                       fields[1].Name, fields[1].Value,
+                       fields[2].Value,
+               ))
+       }
+       return comments
+}
+
+func ParseTopics(data []byte) []string {
+       var s scanner.Scanner
+       s.Init(bytes.NewBuffer(data))
+       topics := []string{}
+       for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
+               topics = append(topics, s.TokenText())
+       }
+       sort.Strings(topics)
+       return topics
+}
+
+func GetNote(repo *git.Repository, tree *object.Tree, what plumbing.Hash) []byte {
+       if tree == nil {
+               return nil
+       }
+       var entry *object.TreeEntry
+       var err error
+       paths := make([]string, 3)
+       paths[0] = what.String()
+       paths[1] = paths[0][:2] + "/" + paths[0][2:]
+       paths[2] = paths[1][:4+1] + "/" + paths[1][4+1:]
+       for _, p := range paths {
+               entry, err = tree.FindEntry(p)
+               if err == nil {
+                       break
+               }
+       }
+       if entry == nil {
+               return nil
+       }
+       blob, err := repo.BlobObject(entry.Hash)
+       if err != nil {
+               return nil
+       }
+       r, err := blob.Reader()
+       if err != nil {
+               return nil
+       }
+       data, err := ioutil.ReadAll(r)
+       if err != nil {
+               return nil
+       }
+       return bytes.TrimSuffix(data, []byte{'\n'})
+}