2 SGBlog -- Git-based CGI blogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as
7 published by the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // Git-based CGI blogging engine
35 "github.com/hjson/hjson-go"
36 "go.stargrave.org/sgblog"
37 "gopkg.in/src-d/go-git.v4"
38 "gopkg.in/src-d/go-git.v4/plumbing"
43 var DashLine = strings.Repeat("-", 72)
47 cfgRaw, err := ioutil.ReadFile(cfgPath)
51 var cfgGeneral map[string]interface{}
52 if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
55 cfgRaw, err = json.Marshal(cfgGeneral)
60 if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
63 if cfg.GopherDomain == "" {
64 log.Fatalln("GopherDomain is not configured")
67 headHash, err := initRepo(cfg)
72 scanner := bufio.NewScanner(io.LimitReader(os.Stdin, 1<<8))
74 log.Fatalln(errors.New("no CRLF found"))
76 selector := scanner.Text()
80 if strings.HasPrefix(selector, "offset/") {
81 offset, err := strconv.Atoi(selector[len("offset/"):])
85 repoLog, err := repo.Log(&git.LogOptions{From: *headHash})
90 for i := 0; i < offset; i++ {
91 if _, err = repoLog.Next(); err != nil {
100 var monthPrev time.Month
102 for i := 0; i < PageEntries; i++ {
103 commit, err := repoLog.Next()
108 yearCur, monthCur, dayCur := commit.Author.When.Date()
109 if dayCur != dayPrev || monthCur != monthPrev || yearCur != yearPrev {
110 menu.WriteString(fmt.Sprintf(
111 "i%04d-%02d-%02d\t\tnull.host\t1%s",
112 yearCur, monthCur, dayCur, CRLF,
114 yearPrev, monthPrev, dayPrev = yearCur, monthCur, dayCur
117 lines := msgSplit(commit.Message)
118 var commentsValue string
119 if l := len(parseComments(getNote(commentsTree, commit.Hash))); l > 0 {
120 commentsValue = fmt.Sprintf(" (%dC)", l)
122 menu.WriteString(fmt.Sprintf(
123 "0[%02d:%02d] %s (%dL)%s\t/%s\t%s\t%d%s",
124 commit.Author.When.Hour(),
125 commit.Author.When.Minute(),
129 commit.Hash.String(),
130 cfg.GopherDomain, 70, CRLF,
134 var links bytes.Buffer
136 offsetPrev := offset - PageEntries
140 links.WriteString(fmt.Sprintf(
141 "1Prev\toffset/%d\t%s\t%d%s",
143 cfg.GopherDomain, 70, CRLF,
147 links.WriteString(fmt.Sprintf(
148 "1Next\toffset/%d\t%s\t%d%s",
150 cfg.GopherDomain, 70, CRLF,
155 "i%s (%d-%d)\t\tnull.host\t1%s",
161 if cfg.AboutURL != "" {
162 fmt.Printf("iAbout: %s\t\tnull.host\t1%s", cfg.AboutURL, CRLF)
164 fmt.Print(links.String())
165 fmt.Print(menu.String())
166 fmt.Print("." + CRLF)
167 } else if sha1DigestRe.MatchString(selector) {
168 commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
173 "What: %s\nWhen: %s\n%s\n%s",
174 commit.Hash.String(),
175 commit.Author.When.Format(sgblog.WhenFmt),
179 notesRaw := getNote(notesTree, commit.Hash)
180 if len(notesRaw) > 0 {
181 fmt.Printf("%s\nNote:\n%s\n", DashLine, string(notesRaw))
183 for i, comment := range parseComments(getNote(commentsTree, commit.Hash)) {
184 fmt.Printf("%s\ncomment %d:\n%s\n", DashLine, i, comment)
187 log.Fatalln(errors.New("unknown selector"))