2 SGBlog -- Git-backed CGI/inetd blogging/phlogging 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/>.
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"
42 var DashLine = strings.Repeat("-", 72)
44 func makeI(cfg *Cfg, value string) string {
45 return strings.Join([]string{"i" + value, "err", cfg.GopherDomain, "70", CRLF}, "\t")
50 cfgRaw, err := ioutil.ReadFile(cfgPath)
54 var cfgGeneral map[string]interface{}
55 if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
58 cfgRaw, err = json.Marshal(cfgGeneral)
63 if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
66 if cfg.GopherDomain == "" {
67 log.Fatalln("GopherDomain is not configured")
70 headHash, err := initRepo(cfg)
75 scanner := bufio.NewScanner(io.LimitReader(os.Stdin, 1<<8))
77 log.Fatalln(errors.New("no CRLF found"))
79 selector := scanner.Text()
83 if strings.HasPrefix(selector, "offset/") {
84 offset, err := strconv.Atoi(selector[len("offset/"):])
88 repoLog, err := repo.Log(&git.LogOptions{From: *headHash})
93 for i := 0; i < offset; i++ {
94 if _, err = repoLog.Next(); err != nil {
101 var menu bytes.Buffer
103 var monthPrev time.Month
105 for i := 0; i < PageEntries; i++ {
106 commit, err := repoLog.Next()
111 yearCur, monthCur, dayCur := commit.Author.When.Date()
112 if dayCur != dayPrev || monthCur != monthPrev || yearCur != yearPrev {
113 menu.WriteString(makeI(cfg, fmt.Sprintf(
114 "%04d-%02d-%02d", yearCur, monthCur, dayCur,
116 yearPrev, monthPrev, dayPrev = yearCur, monthCur, dayCur
119 lines := msgSplit(commit.Message)
120 var commentsValue string
121 if l := len(parseComments(getNote(commentsTree, commit.Hash))); l > 0 {
122 commentsValue = fmt.Sprintf(" (%dC)", l)
124 menu.WriteString(fmt.Sprintf(
125 "0[%02d:%02d] %s (%dL)%s\t/%s\t%s\t%d%s",
126 commit.Author.When.Hour(),
127 commit.Author.When.Minute(),
131 commit.Hash.String(),
132 cfg.GopherDomain, 70, CRLF,
136 fmt.Print(makeI(cfg, fmt.Sprintf("%s (%d-%d)", cfg.Title, offset, offset+PageEntries)))
137 if cfg.AboutURL != "" {
139 "hAbout\tURL:%s\t%s\t%d%s",
141 cfg.GopherDomain, 70, CRLF,
145 offsetPrev := offset - PageEntries
150 "1Prev\toffset/%d\t%s\t%d%s",
152 cfg.GopherDomain, 70, CRLF,
157 "1Next\toffset/%d\t%s\t%d%s",
159 cfg.GopherDomain, 70, CRLF,
162 fmt.Print(menu.String())
163 fmt.Printf(makeI(cfg, "Generated by: SGBlog "+sgblog.Version))
164 fmt.Print("." + CRLF)
165 } else if strings.HasPrefix(selector, "URL:") {
166 selector = selector[len("URL:"):]
169 <meta http-equiv="Refresh" content="1; url=%s" />
170 <title>Redirect to non-gopher URL</title>
173 Redirecting to <a href="%s">%s</a>...
176 `, selector, selector, selector)
177 } else if sha1DigestRe.MatchString(selector) {
178 commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
183 "What: %s\nWhen: %s\n%s\n%s",
184 commit.Hash.String(),
185 commit.Author.When.Format(sgblog.WhenFmt),
189 notesRaw := getNote(notesTree, commit.Hash)
190 if len(notesRaw) > 0 {
191 fmt.Printf("%s\nNote:\n%s\n", DashLine, string(notesRaw))
193 if cfg.CommentsEmail != "" {
195 "%s\nleave comment: mailto:%s?subject=%s\n",
198 commit.Hash.String(),
201 for i, comment := range parseComments(getNote(commentsTree, commit.Hash)) {
202 fmt.Printf("%s\ncomment %d:\n%s\n", DashLine, i, comment)
204 fmt.Printf("%s\nGenerated by: SGBlog %s\n", DashLine, sgblog.Version)
206 log.Fatalln(errors.New("unknown selector"))