summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-09-17 14:00:03 -0600
committertjpcc <tjp@ctrl-c.club>2023-09-17 14:00:03 -0600
commitce0def95f3924a10b0faceb72aa5df18bf813fb1 (patch)
tree2f0300d84738343ea2fb9f13db1af1e649b41917
parentebea6fe755fb052b79cbe05b6280611337e1e5f6 (diff)
GOPHER
-rw-r--r--cmd.go5
-rw-r--r--commit.go4
-rw-r--r--gemini.go8
-rw-r--r--gopher.go129
-rw-r--r--repo.go4
-rw-r--r--templates.go42
-rw-r--r--templates/branch_list.gophermap9
-rw-r--r--templates/diff.gophertext1
-rw-r--r--templates/diffstat.gophertext1
-rw-r--r--templates/ref.gmi2
-rw-r--r--templates/ref.gophermap34
-rw-r--r--templates/repo_home.gophermap20
-rw-r--r--templates/repo_root.gophermap7
-rw-r--r--templates/tag_list.gophermap9
-rw-r--r--templates/tree.gophermap9
15 files changed, 279 insertions, 5 deletions
diff --git a/cmd.go b/cmd.go
index fca36cb..45cb18d 100644
--- a/cmd.go
+++ b/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")
}
diff --git a/commit.go b/commit.go
index d10dc6d..c7a102d 100644
--- a/commit.go
+++ b/commit.go
@@ -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
diff --git a/gemini.go b/gemini.go
index e6e936f..7e106b7 100644
--- a/gemini.go
+++ b/gemini.go
@@ -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,
diff --git a/gopher.go b/gopher.go
new file mode 100644
index 0000000..c725d68
--- /dev/null
+++ b/gopher.go
@@ -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)
+ })
+}
diff --git a/repo.go b/repo.go
index 820e8b2..72291b6 100644
--- a/repo.go
+++ b/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()
diff --git a/templates.go b/templates.go
index 3f8cf42..4c226f6 100644
--- a/templates.go
+++ b/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 {
diff --git a/templates/branch_list.gophermap b/templates/branch_list.gophermap
new file mode 100644
index 0000000..cd238ad
--- /dev/null
+++ b/templates/branch_list.gophermap
@@ -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 -}}
+.
diff --git a/templates/diff.gophertext b/templates/diff.gophertext
new file mode 100644
index 0000000..f2b795b
--- /dev/null
+++ b/templates/diff.gophertext
@@ -0,0 +1 @@
+{{.Repo.Diff .Ctx .Params.fromref .Params.toref}}
diff --git a/templates/diffstat.gophertext b/templates/diffstat.gophertext
new file mode 100644
index 0000000..a51e06b
--- /dev/null
+++ b/templates/diffstat.gophertext
@@ -0,0 +1 @@
+{{.Repo.Diffstat .Ctx .Params.fromref .Params.toref}}
diff --git a/templates/ref.gmi b/templates/ref.gmi
index 61296b8..07c7602 100644
--- a/templates/ref.gmi
+++ b/templates/ref.gmi
@@ -42,6 +42,6 @@
{{ with index .Parents 0 -}}
{{$.Repo.Diffstat $.Ctx . $.Params.ref}}
{{ end -}}
-```
{{ end -}}
+```
{{ end -}}
diff --git a/templates/ref.gophermap b/templates/ref.gophermap
new file mode 100644
index 0000000..3e8d6f9
--- /dev/null
+++ b/templates/ref.gophermap
@@ -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 -}}
+.
diff --git a/templates/repo_home.gophermap b/templates/repo_home.gophermap
new file mode 100644
index 0000000..17f06f7
--- /dev/null
+++ b/templates/repo_home.gophermap
@@ -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 -}}
+.
diff --git a/templates/repo_root.gophermap b/templates/repo_root.gophermap
new file mode 100644
index 0000000..1e32e76
--- /dev/null
+++ b/templates/repo_root.gophermap
@@ -0,0 +1,7 @@
+iRepositories {{.Selector}} {{.Host}} {{.Port}}
+i------------ {{.Selector}} {{.Host}} {{.Port}}
+i {{.Selector}} {{.Host}} {{.Port}}
+{{ range .Repos -}}
+1{{.}} {{.}}/ {{$.Host}} {{$.Port}}
+{{ end -}}
+.
diff --git a/templates/tag_list.gophermap b/templates/tag_list.gophermap
new file mode 100644
index 0000000..ea5ee84
--- /dev/null
+++ b/templates/tag_list.gophermap
@@ -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 -}}
+.
diff --git a/templates/tree.gophermap b/templates/tree.gophermap
new file mode 100644
index 0000000..ed9c1e7
--- /dev/null
+++ b/templates/tree.gophermap
@@ -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 -}}
+.