/*
-SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
-Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
+SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
+Copyright (C) 2020-2022 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Git-backed CGI/inetd blogging/phlogging engine
+// Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
package main
import (
- "bytes"
"crypto/sha1"
+ "embed"
"encoding/json"
"flag"
"fmt"
- "io/ioutil"
+ "io/fs"
+ "log"
+ "os"
"regexp"
- "sort"
"strings"
- "text/scanner"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/hjson/hjson-go"
- "go.cypherpunks.ru/recfile"
+ "github.com/vorlif/spreak"
+ "golang.org/x/text/language"
)
const (
commentsTree *object.Tree
topicsRef *plumbing.Reference
topicsTree *object.Tree
+
+ localizer *spreak.Localizer
+
+ //go:embed locale/*
+ locales embed.FS
)
type Cfg struct {
GitPath string
Branch string
Title string
+ Lang string
URLPrefix string
return lines
}
-func getNote(tree *object.Tree, what plumbing.Hash) []byte {
- if tree == nil {
- return nil
- }
- var entry *object.TreeEntry
- var err error
- paths := make([]string, 3)
- paths[0] = what.String()
- paths[1] = paths[0][:2] + "/" + paths[0][2:]
- paths[2] = paths[1][:4+1] + "/" + paths[1][4+1:]
- for _, p := range paths {
- entry, err = tree.FindEntry(p)
- if err == nil {
- break
- }
- }
- if entry == nil {
- return nil
- }
- blob, err := repo.BlobObject(entry.Hash)
- if err != nil {
- return nil
- }
- r, err := blob.Reader()
- if err != nil {
- return nil
- }
- data, err := ioutil.ReadAll(r)
- if err != nil {
- return nil
- }
- return bytes.TrimSuffix(data, []byte{'\n'})
-}
-
-func parseComments(data []byte) []string {
- comments := []string{}
- r := recfile.NewReader(bytes.NewReader(data))
- for {
- fields, err := r.Next()
- if err != nil {
- break
- }
- if len(fields) != 3 ||
- fields[0].Name != "From" ||
- fields[1].Name != "Date" ||
- fields[2].Name != "Body" {
- continue
- }
- comments = append(comments, fmt.Sprintf(
- "%s: %s\n%s: %s\n%s",
- fields[0].Name, fields[0].Value,
- fields[1].Name, fields[1].Value,
- fields[2].Value,
- ))
- }
- return comments
-}
-
-func parseTopics(data []byte) []string {
- var s scanner.Scanner
- s.Init(bytes.NewBuffer(data))
- topics := []string{}
- for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
- topics = append(topics, s.TokenText())
- }
- sort.Strings(topics)
- return topics
-}
-
func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
var err error
repo, err = git.PlainOpen(cfg.GitPath)
}
func readCfg(cfgPath string) (*Cfg, error) {
- cfgRaw, err := ioutil.ReadFile(cfgPath)
+ cfgRaw, err := os.ReadFile(cfgPath)
if err != nil {
return nil, err
}
return cfg, nil
}
+func initLocalizer(lang string) {
+ fsys, _ := fs.Sub(locales, "locale")
+ bundle, err := spreak.NewBundle(
+ spreak.WithSourceLanguage(language.English),
+ spreak.WithDomainFs(spreak.NoDomain, fsys),
+ spreak.WithLanguage(language.Russian),
+ )
+ if err != nil {
+ log.Fatalln(err)
+ }
+ if lang == "" {
+ lang = language.English.String()
+ }
+ localizer = spreak.NewLocalizer(bundle, language.MustParse(lang))
+}
+
func main() {
gopherCfgPath := flag.String("gopher", "", "Path to gopher-related configuration file")
+ geminiCfgPath := flag.String("gemini", "", "Path to gemini-related configuration file")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), `Usage of sgblog:
sgblog -- run CGI HTTP backend
sgblog -gopher /path/to/cfg.hjson -- run UCSPI/inetd Gopher backend
+ sgblog -gemini /path/to/cfg.hjson -- run UCSPI+tlss Gemini backend
`)
}
flag.Parse()
+ log.SetFlags(log.Lshortfile)
if *gopherCfgPath != "" {
serveGopher(*gopherCfgPath)
+ } else if *geminiCfgPath != "" {
+ serveGemini(*geminiCfgPath)
} else {
serveHTTP()
}