GOPHER
This commit is contained in:
parent
ebea6fe755
commit
ce0def95f3
5
cmd.go
5
cmd.go
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
4
repo.go
4
repo.go
|
@ -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()
|
||||
|
|
42
templates.go
42
templates.go
|
@ -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 {
|
||||
|
|
|
@ -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 -}}
|
||||
.
|
|
@ -0,0 +1 @@
|
|||
{{.Repo.Diff .Ctx .Params.fromref .Params.toref}}
|
|
@ -0,0 +1 @@
|
|||
{{.Repo.Diffstat .Ctx .Params.fromref .Params.toref}}
|
|
@ -42,6 +42,6 @@
|
|||
{{ with index .Parents 0 -}}
|
||||
{{$.Repo.Diffstat $.Ctx . $.Params.ref}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
```
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
|
|
@ -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 -}}
|
||||
.
|
|
@ -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 -}}
|
||||
.
|
|
@ -0,0 +1,7 @@
|
|||
iRepositories {{.Selector}} {{.Host}} {{.Port}}
|
||||
i------------ {{.Selector}} {{.Host}} {{.Port}}
|
||||
i {{.Selector}} {{.Host}} {{.Port}}
|
||||
{{ range .Repos -}}
|
||||
1{{.}} {{.}}/ {{$.Host}} {{$.Port}}
|
||||
{{ end -}}
|
||||
.
|
|
@ -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 -}}
|
||||
.
|
|
@ -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 -}}
|
||||
.
|
Loading…
Reference in New Issue