2 SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
3 Copyright (C) 2020-2022 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/>.
18 // Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
33 "github.com/go-git/go-git/v5"
34 "github.com/go-git/go-git/v5/plumbing"
35 "github.com/go-git/go-git/v5/plumbing/object"
36 "github.com/hjson/hjson-go"
37 "github.com/vorlif/spreak"
38 "golang.org/x/text/language"
46 sha1DigestRe = regexp.MustCompilePOSIX(fmt.Sprintf("([0-9a-f]{%d,%d})", sha1.Size*2, sha1.Size*2))
48 notesTree *object.Tree
49 commentsRef *plumbing.Reference
50 commentsTree *object.Tree
51 topicsRef *plumbing.Reference
52 topicsTree *object.Tree
54 localizer *spreak.Localizer
77 CommentsNotesRef string
81 TopicsCachePath string
86 func msgSplit(msg string) []string {
87 lines := strings.Split(msg, "\n")
88 lines = lines[:len(lines)-1]
90 lines = []string{lines[0], "", ""}
95 func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
97 repo, err = git.PlainOpen(cfg.GitPath)
101 head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
105 headHash := head.Hash()
106 if notes, err := repo.Notes(); err == nil {
107 var notesRef *plumbing.Reference
108 notes.ForEach(func(ref *plumbing.Reference) error {
109 switch string(ref.Name()) {
110 case "refs/notes/commits":
112 case cfg.CommentsNotesRef:
114 case cfg.TopicsNotesRef:
120 if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
121 notesTree, _ = commentsCommit.Tree()
124 if commentsRef != nil {
125 if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
126 commentsTree, _ = commentsCommit.Tree()
129 if topicsRef != nil {
130 if topicsCommit, err := repo.CommitObject(topicsRef.Hash()); err == nil {
131 topicsTree, _ = topicsCommit.Tree()
135 return &headHash, nil
138 func readCfg(cfgPath string) (*Cfg, error) {
139 cfgRaw, err := os.ReadFile(cfgPath)
143 var cfgGeneral map[string]interface{}
144 if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
147 cfgRaw, err = json.Marshal(cfgGeneral)
152 if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
158 func initLocalizer(lang string) {
159 fsys, _ := fs.Sub(locales, "locale")
160 bundle, err := spreak.NewBundle(
161 spreak.WithSourceLanguage(language.English),
162 spreak.WithDomainFs(spreak.NoDomain, fsys),
163 spreak.WithLanguage(language.Russian),
169 lang = language.English.String()
171 localizer = spreak.NewLocalizer(bundle, language.MustParse(lang))
175 gopherCfgPath := flag.String("gopher", "", "Path to gopher-related configuration file")
176 geminiCfgPath := flag.String("gemini", "", "Path to gemini-related configuration file")
177 flag.Usage = func() {
178 fmt.Fprintf(flag.CommandLine.Output(), `Usage of sgblog:
179 sgblog -- run CGI HTTP backend
180 sgblog -gopher /path/to/cfg.hjson -- run UCSPI/inetd Gopher backend
181 sgblog -gemini /path/to/cfg.hjson -- run UCSPI+tlss Gemini backend
185 log.SetFlags(log.Lshortfile)
186 if *gopherCfgPath != "" {
187 serveGopher(*gopherCfgPath)
188 } else if *geminiCfgPath != "" {
189 serveGemini(*geminiCfgPath)