This commit is contained in:
tjpcc 2023-09-17 14:00:03 -06:00
parent ebea6fe755
commit ce0def95f3
15 changed files with 279 additions and 5 deletions

5
cmd.go
View File

@ -5,13 +5,18 @@ import (
"context"
"errors"
"os/exec"
"sync"
)
var mut sync.Mutex
var gitbinpath string
func findbin() string {
if gitbinpath == "" {
mut.Lock()
gitbinpath, _ = exec.LookPath("git")
mut.Unlock()
if gitbinpath == "" {
panic("failed to find 'git' executable")
}

View File

@ -22,6 +22,10 @@ type Commit struct {
Message string
}
func (c *Commit) ParentHash() string {
return c.Hash + "^"
}
func (c *Commit) ShortMessage() string {
short, _, _ := strings.Cut(c.Message, "\n")
return short

View File

@ -32,8 +32,8 @@ func GeminiRouter(repodir string, overrides *template.Template) *sliderule.Route
repoRouter.Route("/tags", gmiTemplate(tmpl, "tag_list.gmi"))
repoRouter.Route("/refs/:ref/", gmiTemplate(tmpl, "ref.gmi"))
repoRouter.Route("/refs/:ref/tree/*path", geminiTreePath(tmpl))
repoRouter.Route("/diffstat/:fromref/:toref", runTemplate(tmpl, "diffstat.gmi", "text/plain"))
repoRouter.Route("/diff/:fromref/:toref", runTemplate(tmpl, "diff.gmi", "text/x-diff"))
repoRouter.Route("/diffstat/:fromref/:toref", runGemiTemplate(tmpl, "diffstat.gmi", "text/plain"))
repoRouter.Route("/diff/:fromref/:toref", runGemiTemplate(tmpl, "diff.gmi", "text/x-diff"))
router := &sliderule.Router{}
router.Route("/", geminiRoot(repodir, tmpl))
@ -104,10 +104,10 @@ func geminiTreePath(tmpl *template.Template) sliderule.Handler {
}
func gmiTemplate(tmpl *template.Template, name string) sliderule.Handler {
return runTemplate(tmpl, name, "text/gemini; charset=utf-8")
return runGemiTemplate(tmpl, name, "text/gemini; charset=utf-8")
}
func runTemplate(tmpl *template.Template, name, mimetype string) sliderule.Handler {
func runGemiTemplate(tmpl *template.Template, name, mimetype string) sliderule.Handler {
return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
obj := map[string]any{
"Ctx": ctx,

129
gopher.go Normal file
View File

@ -0,0 +1,129 @@
package syw
import (
"bytes"
"context"
"mime"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/gopher"
)
func GopherRouter(repodir string, overrides *template.Template) *sliderule.Router {
tmpl, err := addTemplates(gopherTemplate, overrides)
if err != nil {
panic(err)
}
repoRouter := &sliderule.Router{}
repoRouter.Use(assignRepo(repodir))
repoRouter.Route("/branches", runGopherTemplate(tmpl, "branch_list.gophermap", gopher.MenuType))
repoRouter.Route("/tags", runGopherTemplate(tmpl, "tag_list.gophermap", gopher.MenuType))
repoRouter.Route("/refs/:ref", runGopherTemplate(tmpl, "ref.gophermap", gopher.MenuType))
repoRouter.Route("/refs/:ref/tree", gopherTreePath(tmpl, false))
repoRouter.Route("/refs/:ref/tree/*path", gopherTreePath(tmpl, true))
repoRouter.Route("/diffstat/:fromref/:toref", runGopherTemplate(tmpl, "diffstat.gophertext", gopher.TextFileType))
repoRouter.Route("/diff/:fromref/:toref", runGopherTemplate(tmpl, "diff.gophertext", gopher.TextFileType))
router := &sliderule.Router{}
router.Route("/", gopherRoot(repodir, tmpl))
router.Route("/:"+reponamekey, assignRepo(repodir)(runGopherTemplate(tmpl, "repo_home.gophermap", gopher.MenuType)))
router.Mount("/:"+reponamekey, repoRouter)
return router
}
func gopherRoot(repodir string, tmpl *template.Template) sliderule.Handler {
return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
entries, err := os.ReadDir(repodir)
if err != nil {
return gopher.Error(err).Response()
}
names := []string{}
for _, item := range entries {
if Open(filepath.Join(repodir, item.Name())) != nil {
names = append(names, item.Name())
}
}
buf := &bytes.Buffer{}
obj := map[string]any{
"Repos": names,
"Host": request.Hostname(),
"Port": request.Port(),
"Selector": request.Path,
}
if err := tmpl.ExecuteTemplate(buf, "repo_root.gophermap", obj); err != nil {
return gopher.Error(err).Response()
}
return gopher.File(gopher.MenuType, buf)
})
}
func gopherTreePath(tmpl *template.Template, haspath bool) sliderule.Handler {
return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
repo := ctx.Value(repokey).(*Repository)
params := sliderule.RouteParams(ctx)
t := "tree"
if haspath {
var err error
t, err = repo.Type(ctx, params["ref"] + ":" + params["path"])
if err != nil {
return gopher.Error(err).Response()
}
}
if t != "blob" {
if !haspath {
params["path"] = ""
}
return runGopherTemplate(tmpl, "tree.gophermap", gopher.MenuType).Handle(ctx, request)
}
body, err := repo.Blob(ctx, params["ref"], params["path"])
if err != nil {
return gopher.Error(err).Response()
}
filetype := gopher.MenuType
ext := path.Ext(params["path"])
if ext != ".gophermap" && params["path"] != "gophermap" {
mtype := mime.TypeByExtension(ext)
if strings.HasPrefix(mtype, "text/") {
filetype = gopher.TextFileType
} else {
filetype = gopher.BinaryFileType
}
}
return gopher.File(filetype, bytes.NewBuffer(body))
})
}
func runGopherTemplate(tmpl *template.Template, name string, filetype sliderule.Status) sliderule.Handler {
return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
obj := map[string]any{
"Ctx": ctx,
"Repo": ctx.Value(repokey),
"Params": sliderule.RouteParams(ctx),
"Host": request.Hostname(),
"Port": request.Port(),
"Selector": request.Path,
}
buf := &bytes.Buffer{}
if err := tmpl.ExecuteTemplate(buf, name, obj); err != nil {
return gopher.Error(err).Response()
}
return gopher.File(filetype, buf)
})
}

View File

@ -46,6 +46,10 @@ func (r *Repository) Name() string {
return strings.TrimSuffix(name, ".git")
}
func (r *Repository) NameBytes() []byte {
return []byte(r.Name())
}
func (r *Repository) cmd(ctx context.Context, cmdname string, args ...string) (*cmdResult, error) {
args = append([]string{"--git-dir=" + string(*r), cmdname}, args...)
start := time.Now()

View File

@ -2,6 +2,7 @@ package syw
import (
"embed"
"net/url"
"text/template"
)
@ -11,6 +12,47 @@ var (
geminiTemplate = template.Must(template.ParseFS(geminiTemplateFS, "templates/*.gmi"))
)
var (
//go:embed templates/*.gophermap templates/*.gophertext
gopherTemplateFS embed.FS
gopherTemplate = template.Must(
template.New("gopher").Funcs(template.FuncMap{
"combine": gopherCombine,
"join": gopherJoin,
}).ParseFS(
gopherTemplateFS,
"templates/*.gophermap",
"templates/*.gophertext",
),
)
)
func gopherCombine(base string, relative ...string) (string, error) {
bu, err := url.Parse(base)
if err != nil {
return "", err
}
for _, rel := range relative {
ru, err := url.Parse(rel)
if err != nil {
return "", err
}
bu = bu.ResolveReference(ru)
}
return bu.String(), nil
}
func gopherJoin(base string, relative ...string) (string, error) {
bu, err := url.Parse(base)
if err != nil {
return "", err
}
return bu.JoinPath(relative...).Path, nil
}
func addTemplates(base *template.Template, additions *template.Template) (*template.Template, error) {
base, err := base.Clone()
if err != nil {

View File

@ -0,0 +1,9 @@
i{{.Repo.Name}} Branches ⌥ {{.Selector}} {{.Host}} {{.Port}}
i{{ range .Repo.NameBytes }}-{{ end }}----------- {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
{{ range .Repo.Refs .Ctx -}}
{{ if .IsBranch -}}
1{{.ShortName}} {{combine $.Selector "refs/" .Hash}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ end -}}
.

View File

@ -0,0 +1 @@
{{.Repo.Diff .Ctx .Params.fromref .Params.toref}}

View File

@ -0,0 +1 @@
{{.Repo.Diffstat .Ctx .Params.fromref .Params.toref}}

View File

@ -42,6 +42,6 @@
{{ with index .Parents 0 -}}
{{$.Repo.Diffstat $.Ctx . $.Params.ref}}
{{ end -}}
{{ end -}}
```
{{ end -}}
{{ end -}}

34
templates/ref.gophermap Normal file
View File

@ -0,0 +1,34 @@
{{ with .Repo.Commit .Ctx .Params.ref -}}
i{{.Repo.Name}} {{slice .Hash 0 8}} {{$.Selector}} {{$.Host}} {{$.Port}}
i{{ range .Repo.NameBytes }}-{{ end }}--------- {{$.Selector}} {{$.Host}} {{$.Port}}
i {{$.Selector}} {{$.Host}} {{$.Port}}
i{{.ShortMessage}} {{$.Selector}} {{$.Host}} {{$.Port}}
i {{$.Selector}} {{$.Host}} {{$.Port}}
{{ with .RestOfMessage -}}
{{ if ne . "" -}}
i{{.}} {{$.Selector}} {{$.Host}} {{$.Port}}
i {{$.Selector}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ end -}}
1🗂 Repository {{combine $.Selector ".."}} {{$.Host}} {{$.Port}}
1📄 Files {{$.Selector}}/tree {{$.Host}} {{$.Port}}
{{ if ne .Parents nil -}}
0🔩 Full Diff {{join $.Selector "../../diff" .ParentHash .Hash}} {{$.Host}} {{$.Port}}
{{ else -}}
0🔩 Full Diff {{combine $.Selector "../diff/4b825dc642cb6eb9a060e54bf8d69288fbee4904" .Hash}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ range .Parents -}}
1👤 Parent {{slice . 0 8}} {{combine $.Selector .}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ range .Repo.Refs $.Ctx -}}
{{ if .IsTag -}}
{{ if eq $.Params.ref .Hash -}}
1🏷 {{.ShortName}} ../{{.Hash}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ end -}}
{{ end -}}
i {{$.Selector}} {{$.Host}} {{$.Port}}
iAuthored by {{.AuthorName}}<{{.AuthorEmail}}> on {{.AuthorDate.Format "Mon Jan _2 15:04:05 MST 2006"}} {{$.Selector}} {{$.Host}} {{$.Port}}
iCommitted by {{.CommitterName}}<{{.CommitterEmail}}> on {{.CommitDate.Format "Mon Jan _2 15:04:05 MST 2006"}} {{$.Selector}} {{$.Host}} {{$.Port}}
{{ end -}}
.

View File

@ -0,0 +1,20 @@
i{{.Repo.Name}} {{.Selector}} {{.Host}} {{.Port}}
i{{ range .Repo.NameBytes }}-{{ end }} {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
i{{.Repo.Description}} {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
1⌥ Branches {{.Selector}}/branches {{.Host}} {{.Port}}
1🏷 Tags {{.Selector}}/tags {{.Host}} {{.Port}}
1📄 Files {{.Selector}}/refs/HEAD/tree {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
iLatest Commits: {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
{{ with .Repo.Commits .Ctx "HEAD" 5 -}}
{{ range . -}}
1{{.ShortMessage}} {{$.Selector}}/refs/{{.Hash}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ if len . | eq 0 -}}
i(no commits to show) {{$.Selector}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ end -}}
.

View File

@ -0,0 +1,7 @@
iRepositories {{.Selector}} {{.Host}} {{.Port}}
i------------ {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
{{ range .Repos -}}
1{{.}} {{.}}/ {{$.Host}} {{$.Port}}
{{ end -}}
.

View File

@ -0,0 +1,9 @@
i{{.Repo.Name}} Tags 🏷️ {{.Selector}} {{.Host}} {{.Port}}
i{{ range .Repo.NameBytes }}-{{ end }}-------- {{.Selector}} {{.Host}} {{.Port}}
i {{.Selector}} {{.Host}} {{.Port}}
{{ range .Repo.Refs .Ctx -}}
{{ if .IsTag -}}
1{{.ShortName}} {{combine $.Selector "refs/" .Hash}} {{$.Host}} {{$.Port}}
{{ end -}}
{{ end -}}
.

9
templates/tree.gophermap Normal file
View File

@ -0,0 +1,9 @@
{{ with .Repo.Commit .Ctx .Params.ref -}}
i{{slice .Hash 0 8}}:{{ if ne $.Params.path "" }}{{$.Params.path}}{{ else }}/{{ end }} {{$.Selector}} {{$.Host}} {{$.Port}}
i {{$.Selector}} {{$.Host}} {{$.Port}}
{{ end -}}
1{{ if ne .Params.path "" }}../{{ else }}Commit{{ end }} {{join $.Selector ".."}} {{$.Host}} {{$.Port}}
{{ range .Repo.Tree .Ctx $.Params.ref $.Params.path -}}
{{ if eq .Type "blob" }}0📄 {{ else }}1📂 {{ end }}{{.Path}} {{join $.Selector .Path}} {{$.Host}} {{$.Port}}
{{ end -}}
.