From a4e509fa3e93e999c4d878303be799d07b5a73ae Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sat, 1 May 2021 17:01:41 +0300 Subject: [PATCH] sgblog-topics helper command --- cmd/sgblog-topics/main.go | 91 +++++++++++++++++++++++++++++++++++++++ cmd/sgblog/gopher.go | 10 ++--- cmd/sgblog/http.go | 24 +++++------ cmd/sgblog/main.go | 73 ------------------------------- cmd/sgblog/topics.go | 3 +- common.go | 82 +++++++++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 91 deletions(-) create mode 100644 cmd/sgblog-topics/main.go diff --git a/cmd/sgblog-topics/main.go b/cmd/sgblog-topics/main.go new file mode 100644 index 0000000..121380f --- /dev/null +++ b/cmd/sgblog-topics/main.go @@ -0,0 +1,91 @@ +/* +SGBlog -- Git-backed CGI/inetd blogging/phlogging engine +Copyright (C) 2020-2021 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-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]) + } +} diff --git a/cmd/sgblog/gopher.go b/cmd/sgblog/gopher.go index b12a2b8..f682c41 100644 --- a/cmd/sgblog/gopher.go +++ b/cmd/sgblog/gopher.go @@ -137,9 +137,9 @@ Redirecting to %s... 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 %s... 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)) diff --git a/cmd/sgblog/http.go b/cmd/sgblog/http.go index d039ed3..7ed1ea6 100644 --- a/cmd/sgblog/http.go +++ b/cmd/sgblog/http.go @@ -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) diff --git a/cmd/sgblog/main.go b/cmd/sgblog/main.go index b373564..ef3ff45 100644 --- a/cmd/sgblog/main.go +++ b/cmd/sgblog/main.go @@ -19,22 +19,18 @@ along with this program. If not, see . 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) diff --git a/cmd/sgblog/topics.go b/cmd/sgblog/topics.go index 8e34bb4..be70e73 100644 --- a/cmd/sgblog/topics.go +++ b/cmd/sgblog/topics.go @@ -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) } } diff --git a/common.go b/common.go index ebb5a9d..d6357fc 100644 --- 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'}) +} -- 2.44.0