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/>.
33 "github.com/hjson/hjson-go"
34 "go.stargrave.org/sgblog"
35 "gopkg.in/src-d/go-git.v4"
36 "gopkg.in/src-d/go-git.v4/plumbing"
37 "gopkg.in/src-d/go-git.v4/plumbing/object"
41 TmplGopherMenu = `{{$CR := printf "\r"}}{{$CRLF := printf "\r\n" -}}
42 {{- define "domainPort" }} {{.GopherDomain}} 70{{end}}{{$Cfg := .Cfg -}}
43 i{{.Cfg.Title}} ({{.Offset}}-{{.OffsetNext}}) err{{template "domainPort" .Cfg}}{{$CRLF -}}
44 {{- if .Cfg.AboutURL}}hAbout URL:{{.Cfg.AboutURL}}{{template "domainPort" .Cfg}}{{$CRLF}}{{end -}}
45 {{- if .Offset}}1Prev offset/{{.OffsetPrev}}{{template "domainPort" .Cfg}}{{$CRLF}}{{end -}}
46 {{- if not .LogEnded}}1Next offset/{{.OffsetNext}}{{template "domainPort" .Cfg}}{{$CRLF}}{{end -}}
47 {{- $datePrev := "0001-01-01" -}}
48 {{- range .Entries -}}
49 {{- $dateCur := .Commit.Author.When.Format "2006-01-02" -}}
50 {{- if ne $dateCur $datePrev}}{{$datePrev = $dateCur}}
51 i{{$dateCur}} err{{template "domainPort" $Cfg}}{{$CR}}{{end}}
52 0[{{.Commit.Author.When.Format "15:04"}}] {{.Title}} ({{.LinesNum}}L){{with .CommentsNum}} ({{.}}C){{end}} /{{.Commit.Hash.String}}{{template "domainPort" $Cfg}}{{$CR}}{{end}}
53 iGenerated by: SGBlog {{.Version}} err{{template "domainPort" .Cfg}}{{$CR}}
55 TmplGopherEntry = `What: {{.Commit.Hash.String}}
57 ------------------------------------------------------------------------
60 ------------------------------------------------------------------------
63 {{- if .Cfg.CommentsEmail}}
64 ------------------------------------------------------------------------
65 leave comment: mailto:{{.Cfg.CommentsEmail}}?subject={{.Commit.Hash.String}}
66 {{end}}{{range $idx, $comment := .Comments}}
67 ------------------------------------------------------------------------
71 ------------------------------------------------------------------------
72 Generated by: SGBlog {{.Version}}
76 type TableMenuEntry struct {
85 cfgRaw, err := ioutil.ReadFile(cfgPath)
89 var cfgGeneral map[string]interface{}
90 if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
93 cfgRaw, err = json.Marshal(cfgGeneral)
98 if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
101 if cfg.GopherDomain == "" {
102 log.Fatalln("GopherDomain is not configured")
105 headHash, err := initRepo(cfg)
110 scanner := bufio.NewScanner(io.LimitReader(os.Stdin, 1<<8))
112 log.Fatalln(errors.New("no CRLF found"))
114 selector := scanner.Text()
116 selector = "offset/0"
118 if strings.HasPrefix(selector, "offset/") {
119 offset, err := strconv.Atoi(selector[len("offset/"):])
123 repoLog, err := repo.Log(&git.LogOptions{From: *headHash})
127 for i := 0; i < offset; i++ {
128 if _, err = repoLog.Next(); err != nil {
133 entries := make([]TableMenuEntry, 0, PageEntries)
134 for i := 0; i < PageEntries; i++ {
135 commit, err := repoLog.Next()
140 lines := msgSplit(commit.Message)
141 entries = append(entries, TableMenuEntry{
144 LinesNum: len(lines) - 2,
145 CommentsNum: len(parseComments(getNote(commentsTree, commit.Hash))),
148 tmpl := template.Must(template.New("menu").Parse(TmplGopherMenu))
149 err = tmpl.Execute(os.Stdout, struct {
155 Entries []TableMenuEntry
160 OffsetPrev: offset - PageEntries,
161 OffsetNext: offset + PageEntries,
164 Version: sgblog.Version,
169 } else if strings.HasPrefix(selector, "URL:") {
170 selector = selector[len("URL:"):]
173 <meta http-equiv="Refresh" content="1; url=%s" />
174 <title>Redirect to non-gopher URL</title>
177 Redirecting to <a href="%s">%s</a>...
180 `, selector, selector, selector)
181 } else if sha1DigestRe.MatchString(selector) {
182 commit, err := repo.CommitObject(plumbing.NewHash(selector[1:]))
186 tmpl := template.Must(template.New("entry").Parse(TmplGopherEntry))
187 err = tmpl.Execute(os.Stdout, struct {
188 Commit *object.Commit
196 When: commit.Author.When.Format(sgblog.WhenFmt),
198 Note: string(getNote(notesTree, commit.Hash)),
199 Comments: parseComments(getNote(commentsTree, commit.Hash)),
200 Version: sgblog.Version,
206 log.Fatalln(errors.New("unknown selector"))