]> Sergey Matveev's repositories - sgblog.git/blob - cmd/sgblog/gopher.go
41dc9d16089c15737d8f18bc76d4ce81271d8c6b
[sgblog.git] / cmd / sgblog / gopher.go
1 /*
2 SGBlog -- Git-based CGI/inetd blogging/phlogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
4
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.
8
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.
13
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/>.
16 */
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "encoding/json"
24         "errors"
25         "fmt"
26         "io"
27         "io/ioutil"
28         "log"
29         "os"
30         "strconv"
31         "strings"
32         "time"
33
34         "github.com/hjson/hjson-go"
35         "go.stargrave.org/sgblog"
36         "gopkg.in/src-d/go-git.v4"
37         "gopkg.in/src-d/go-git.v4/plumbing"
38 )
39
40 const CRLF = "\r\n"
41
42 var DashLine = strings.Repeat("-", 72)
43
44 func serveGopher() {
45         cfgPath := os.Args[2]
46         cfgRaw, err := ioutil.ReadFile(cfgPath)
47         if err != nil {
48                 log.Fatalln(err)
49         }
50         var cfgGeneral map[string]interface{}
51         if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
52                 log.Fatalln(err)
53         }
54         cfgRaw, err = json.Marshal(cfgGeneral)
55         if err != nil {
56                 log.Fatalln(err)
57         }
58         var cfg *Cfg
59         if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
60                 log.Fatalln(err)
61         }
62         if cfg.GopherDomain == "" {
63                 log.Fatalln("GopherDomain is not configured")
64         }
65
66         headHash, err := initRepo(cfg)
67         if err != nil {
68                 log.Fatalln(err)
69         }
70
71         scanner := bufio.NewScanner(io.LimitReader(os.Stdin, 1<<8))
72         if !scanner.Scan() {
73                 log.Fatalln(errors.New("no CRLF found"))
74         }
75         selector := scanner.Text()
76         if selector == "" {
77                 selector = "offset/0"
78         }
79         if strings.HasPrefix(selector, "offset/") {
80                 offset, err := strconv.Atoi(selector[len("offset/"):])
81                 if err != nil {
82                         log.Fatalln(err)
83                 }
84                 repoLog, err := repo.Log(&git.LogOptions{From: *headHash})
85                 if err != nil {
86                         log.Fatalln(err)
87                 }
88                 commitN := 0
89                 for i := 0; i < offset; i++ {
90                         if _, err = repoLog.Next(); err != nil {
91                                 break
92                         }
93                         commitN++
94                 }
95
96                 logEnded := false
97                 var menu bytes.Buffer
98                 var yearPrev int
99                 var monthPrev time.Month
100                 var dayPrev int
101                 for i := 0; i < PageEntries; i++ {
102                         commit, err := repoLog.Next()
103                         if err != nil {
104                                 logEnded = true
105                                 break
106                         }
107                         yearCur, monthCur, dayCur := commit.Author.When.Date()
108                         if dayCur != dayPrev || monthCur != monthPrev || yearCur != yearPrev {
109                                 menu.WriteString(fmt.Sprintf(
110                                         "i%04d-%02d-%02d\t\tnull.host\t1%s",
111                                         yearCur, monthCur, dayCur, CRLF,
112                                 ))
113                                 yearPrev, monthPrev, dayPrev = yearCur, monthCur, dayCur
114                         }
115                         commitN++
116                         lines := msgSplit(commit.Message)
117                         var commentsValue string
118                         if l := len(parseComments(getNote(commentsTree, commit.Hash))); l > 0 {
119                                 commentsValue = fmt.Sprintf(" (%dC)", l)
120                         }
121                         menu.WriteString(fmt.Sprintf(
122                                 "0[%02d:%02d] %s (%dL)%s\t/%s\t%s\t%d%s",
123                                 commit.Author.When.Hour(),
124                                 commit.Author.When.Minute(),
125                                 lines[0],
126                                 len(lines)-2,
127                                 commentsValue,
128                                 commit.Hash.String(),
129                                 cfg.GopherDomain, 70, CRLF,
130                         ))
131                 }
132
133                 fmt.Printf(
134                         "i%s (%d-%d)\t\tnull.host\t1%s",
135                         cfg.Title,
136                         offset,
137                         offset+PageEntries,
138                         CRLF,
139                 )
140                 if cfg.AboutURL != "" {
141                         fmt.Printf("iAbout: %s\t\tnull.host\t1%s", cfg.AboutURL, CRLF)
142                 }
143                 if offset > 0 {
144                         offsetPrev := offset - PageEntries
145                         if offsetPrev < 0 {
146                                 offsetPrev = 0
147                         }
148                         fmt.Printf(
149                                 "1Prev\toffset/%d\t%s\t%d%s",
150                                 offsetPrev,
151                                 cfg.GopherDomain, 70, CRLF,
152                         )
153                 }
154                 if !logEnded {
155                         fmt.Printf(
156                                 "1Next\toffset/%d\t%s\t%d%s",
157                                 offset+PageEntries,
158                                 cfg.GopherDomain, 70, CRLF,
159                         )
160                 }
161                 fmt.Print(menu.String())
162                 fmt.Printf("iGenerated by: SGBlog %s\t\tnull.host\t1%s", sgblog.Version, CRLF)
163                 fmt.Print("." + CRLF)
164         } else if sha1DigestRe.MatchString(selector) {
165                 commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
166                 if err != nil {
167                         log.Fatalln(err)
168                 }
169                 fmt.Printf(
170                         "What: %s\nWhen: %s\n%s\n%s",
171                         commit.Hash.String(),
172                         commit.Author.When.Format(sgblog.WhenFmt),
173                         DashLine,
174                         commit.Message,
175                 )
176                 notesRaw := getNote(notesTree, commit.Hash)
177                 if len(notesRaw) > 0 {
178                         fmt.Printf("%s\nNote:\n%s\n", DashLine, string(notesRaw))
179                 }
180                 for i, comment := range parseComments(getNote(commentsTree, commit.Hash)) {
181                         fmt.Printf("%s\ncomment %d:\n%s\n", DashLine, i, comment)
182                 }
183                 fmt.Printf("%s\nGenerated by: SGBlog %s\n", DashLine, sgblog.Version)
184         } else {
185                 log.Fatalln(errors.New("unknown selector"))
186         }
187 }