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