]> Sergey Matveev's repositories - sgblog.git/blob - cmd/sgblog/main.go
sgblog-topics helper command
[sgblog.git] / cmd / sgblog / main.go
1 /*
2 SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
3 Copyright (C) 2020-2021 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 // Git-backed CGI/inetd blogging/phlogging engine
19 package main
20
21 import (
22         "crypto/sha1"
23         "encoding/json"
24         "flag"
25         "fmt"
26         "io/ioutil"
27         "regexp"
28         "strings"
29
30         "github.com/go-git/go-git/v5"
31         "github.com/go-git/go-git/v5/plumbing"
32         "github.com/go-git/go-git/v5/plumbing/object"
33         "github.com/hjson/hjson-go"
34 )
35
36 const (
37         PageEntries = 50
38 )
39
40 var (
41         sha1DigestRe = regexp.MustCompilePOSIX(fmt.Sprintf("([0-9a-f]{%d,%d})", sha1.Size*2, sha1.Size*2))
42         repo         *git.Repository
43         notesTree    *object.Tree
44         commentsRef  *plumbing.Reference
45         commentsTree *object.Tree
46         topicsRef    *plumbing.Reference
47         topicsTree   *object.Tree
48 )
49
50 type Cfg struct {
51         GitPath string
52         Branch  string
53         Title   string
54
55         URLPrefix string
56
57         AtomBaseURL string
58         AtomId      string
59         AtomAuthor  string
60
61         CSS       string
62         Webmaster string
63         AboutURL  string
64         GitURLs   []string
65
66         CommentsNotesRef string
67         CommentsEmail    string
68
69         TopicsNotesRef  string
70         TopicsCachePath string
71
72         GopherDomain string
73 }
74
75 func msgSplit(msg string) []string {
76         lines := strings.Split(msg, "\n")
77         lines = lines[:len(lines)-1]
78         if len(lines) < 3 {
79                 lines = []string{lines[0], "", ""}
80         }
81         return lines
82 }
83
84 func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
85         var err error
86         repo, err = git.PlainOpen(cfg.GitPath)
87         if err != nil {
88                 return nil, err
89         }
90         head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
91         if err != nil {
92                 return nil, err
93         }
94         headHash := head.Hash()
95         if notes, err := repo.Notes(); err == nil {
96                 var notesRef *plumbing.Reference
97                 notes.ForEach(func(ref *plumbing.Reference) error {
98                         switch string(ref.Name()) {
99                         case "refs/notes/commits":
100                                 notesRef = ref
101                         case cfg.CommentsNotesRef:
102                                 commentsRef = ref
103                         case cfg.TopicsNotesRef:
104                                 topicsRef = ref
105                         }
106                         return nil
107                 })
108                 if notesRef != nil {
109                         if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
110                                 notesTree, _ = commentsCommit.Tree()
111                         }
112                 }
113                 if commentsRef != nil {
114                         if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
115                                 commentsTree, _ = commentsCommit.Tree()
116                         }
117                 }
118                 if topicsRef != nil {
119                         if topicsCommit, err := repo.CommitObject(topicsRef.Hash()); err == nil {
120                                 topicsTree, _ = topicsCommit.Tree()
121                         }
122                 }
123         }
124         return &headHash, nil
125 }
126
127 func readCfg(cfgPath string) (*Cfg, error) {
128         cfgRaw, err := ioutil.ReadFile(cfgPath)
129         if err != nil {
130                 return nil, err
131         }
132         var cfgGeneral map[string]interface{}
133         if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
134                 return nil, err
135         }
136         cfgRaw, err = json.Marshal(cfgGeneral)
137         if err != nil {
138                 return nil, err
139         }
140         var cfg *Cfg
141         if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
142                 return nil, err
143         }
144         return cfg, nil
145 }
146
147 func main() {
148         gopherCfgPath := flag.String("gopher", "", "Path to gopher-related configuration file")
149         flag.Usage = func() {
150                 fmt.Fprintf(flag.CommandLine.Output(), `Usage of sgblog:
151         sgblog -- run CGI HTTP backend
152         sgblog -gopher /path/to/cfg.hjson -- run UCSPI/inetd Gopher backend
153 `)
154         }
155         flag.Parse()
156         if *gopherCfgPath != "" {
157                 serveGopher(*gopherCfgPath)
158         } else {
159                 serveHTTP()
160         }
161 }