summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortjp <tjp@ctrl-c.club>2023-11-14 15:44:00 -0700
committertjp <tjp@ctrl-c.club>2023-11-14 15:44:00 -0700
commit105022b22d4b297057a7a946e3d6e1202bc6d1d5 (patch)
tree840eea2a699f28aac1443b2275f9a05abb098fb5
parente8122362a0af5a499dcba182d6e7b41bd4b3eccf (diff)
Protocol refactor
-rw-r--r--.gitignore2
-rw-r--r--gemini.go116
-rw-r--r--gopher.go86
-rw-r--r--handlers.go86
-rw-r--r--protocol.go14
-rw-r--r--spartan.go102
-rw-r--r--templates/repo_root.gmi2
7 files changed, 169 insertions, 239 deletions
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index d8c0000..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-tmp/
-TODO.md
diff --git a/gemini.go b/gemini.go
index 878b2b8..390a0fc 100644
--- a/gemini.go
+++ b/gemini.go
@@ -1,24 +1,13 @@
package syw
import (
- "bytes"
"context"
- "mime"
- "os"
- "path"
- "path/filepath"
- "strings"
"text/template"
"tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/gemini"
)
-const (
- repokey = "syw_repo"
- reponamekey = "repository"
-)
-
// GeminiRouter builds a router that will handle requests into a directory of git repositories.
//
// The routes it defines are:
@@ -52,7 +41,8 @@ const (
// Repo: a *syw.Repository object corresponding to <repodir>/:repository
// Params: a map[string]string of the route parameters
//
-// The only exception is repo_root.gmi, which is rendered with a slice of the repo names instead.
+// The only exception is repo_root.gmi, which is rendered with an object containing a single name
+// "Repos", which is a slice of the repository names.
func GeminiRouter(repodir string, overrides *template.Template) *sliderule.Router {
tmpl, err := addTemplates(geminiTemplate, overrides)
if err != nil {
@@ -61,99 +51,33 @@ func GeminiRouter(repodir string, overrides *template.Template) *sliderule.Route
repoRouter := &sliderule.Router{}
repoRouter.Use(assignRepo(repodir))
- repoRouter.Route("/", gmiTemplate(tmpl, "repo_home.gmi"))
- repoRouter.Route("/branches", gmiTemplate(tmpl, "branch_list.gmi"))
- 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", runGemiTemplate(tmpl, "diffstat.gmi.txt", "text/plain"))
- repoRouter.Route("/diff/:fromref/:toref", runGemiTemplate(tmpl, "diff.gmi.txt", "text/x-diff"))
+ repoRouter.Route("/", repoRouteHandler(geminiProto, tmpl, "repo_home.gmi"))
+ repoRouter.Route("/branches", repoRouteHandler(geminiProto, tmpl, "branch_list.gmi"))
+ repoRouter.Route("/tags", repoRouteHandler(geminiProto, tmpl, "tag_list.gmi"))
+ repoRouter.Route("/refs/:ref/", repoRouteHandler(geminiProto, tmpl, "ref.gmi"))
+ repoRouter.Route("/refs/:ref/tree/*path", treePathHandler(geminiProto, tmpl, "tree.gmi"))
+ repoRouter.Route("/diffstat/:fromref/:toref", repoRouteHandler(geminiProto, tmpl, "diffstat.gmi.txt"))
+ repoRouter.Route("/diff/:fromref/:toref", repoRouteHandler(geminiProto, tmpl, "diff.gmi.txt"))
router := &sliderule.Router{}
- router.Route("/", geminiRoot(repodir, tmpl))
+ router.Route("/", rootDirHandler(geminiProto, repodir, tmpl, "repo_root.gmi"))
router.Mount("/:"+reponamekey, repoRouter)
return router
}
-func assignRepo(repodir string) sliderule.Middleware {
- return func(h sliderule.Handler) sliderule.Handler {
- return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
- repo := Open(filepath.Join(repodir, sliderule.RouteParams(ctx)[reponamekey]))
- return h.Handle(context.WithValue(ctx, repokey, repo), request)
- })
- }
-}
-
-func geminiRoot(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 gemini.Failure(err)
- }
+type geminiProtocol struct{ sliderule.ServerProtocol }
- names := []string{}
- for _, item := range entries {
- if Open(filepath.Join(repodir, item.Name())) != nil {
- names = append(names, item.Name())
- }
- }
-
- buf := &bytes.Buffer{}
- if err := tmpl.ExecuteTemplate(buf, "repo_root.gmi", names); err != nil {
- return gemini.Failure(err)
- }
-
- return gemini.Success("text/gemini; charset=utf-8", buf)
- })
+func (geminiProtocol) TemplateBaseData(_ context.Context, _ *sliderule.Request) map[string]any {
+ return map[string]any{}
}
-func geminiTreePath(tmpl *template.Template) sliderule.Handler {
- return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
- params := sliderule.RouteParams(ctx)
- if params["path"] == "" || strings.HasSuffix(params["path"], "/") {
- return gmiTemplate(tmpl, "tree.gmi").Handle(ctx, request)
- }
-
- repo := ctx.Value(repokey).(*Repository)
-
- body, err := repo.Blob(ctx, params["ref"], params["path"])
- if err != nil {
- return gemini.Failure(err)
- }
-
- mediaType := ""
- ext := path.Ext(params["path"])
- if ext == ".gmi" {
- mediaType = "text/gemini; charset=utf-8"
- } else {
- mediaType = mime.TypeByExtension(ext)
- }
- if mediaType == "" {
- mediaType = "application/octet-stream"
- }
-
- return gemini.Success(mediaType, bytes.NewBuffer(body))
- })
-}
-
-func gmiTemplate(tmpl *template.Template, name string) sliderule.Handler {
- return runGemiTemplate(tmpl, name, "text/gemini; charset=utf-8")
+func (geminiProtocol) TemplateRepoData(ctx context.Context, request *sliderule.Request) map[string]any {
+ return map[string]any{
+ "Ctx": ctx,
+ "Repo": ctx.Value(repokey),
+ "Params": sliderule.RouteParams(ctx),
+ }
}
-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,
- "Repo": ctx.Value(repokey),
- "Params": sliderule.RouteParams(ctx),
- }
- buf := &bytes.Buffer{}
-
- if err := tmpl.ExecuteTemplate(buf, name, obj); err != nil {
- return gemini.Failure(err)
- }
-
- return gemini.Success(mimetype, buf)
- })
-}
+var geminiProto = geminiProtocol{gemini.ServerProtocol}
diff --git a/gopher.go b/gopher.go
index d715641..7d9bdec 100644
--- a/gopher.go
+++ b/gopher.go
@@ -3,9 +3,7 @@ package syw
import (
"bytes"
"context"
- "os"
"path"
- "path/filepath"
"text/template"
"tildegit.org/tjp/sliderule"
@@ -65,55 +63,47 @@ func GopherRouter(repodir string, overrides *template.Template) *sliderule.Route
repoRouter := &sliderule.Router{}
repoRouter.Use(assignRepo(repodir))
- repoRouter.Route("/branches", runGopherTemplate(tmpl, "branch_list.gph", gopher.MenuType))
- repoRouter.Route("/tags", runGopherTemplate(tmpl, "tag_list.gph", gopher.MenuType))
- repoRouter.Route("/refs/:ref", runGopherTemplate(tmpl, "ref.gph", 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.gph.txt", gopher.TextFileType))
- repoRouter.Route("/diff/:fromref/:toref", runGopherTemplate(tmpl, "diff.gph.txt", gopher.TextFileType))
+ repoRouter.Route("/branches", repoRouteHandler(gopherProto, tmpl, "branch_list.gph"))
+ repoRouter.Route("/tags", repoRouteHandler(gopherProto, tmpl, "tag_list.gph"))
+ repoRouter.Route("/refs/:ref", repoRouteHandler(gopherProto, tmpl, "ref.gph"))
+ repoRouter.Route("/refs/:ref/tree", gopherTreePath(tmpl))
+ repoRouter.Route("/refs/:ref/tree/*path", gopherTreePath(tmpl))
+ repoRouter.Route("/diffstat/:fromref/:toref", repoRouteHandler(gopherProto, tmpl, "diffstat.gph.txt"))
+ repoRouter.Route("/diff/:fromref/:toref", repoRouteHandler(gopherProto, tmpl, "diff.gph.txt"))
router := &sliderule.Router{}
- router.Route("/", gopherRoot(repodir, tmpl))
- router.Route("/:"+reponamekey, assignRepo(repodir)(runGopherTemplate(tmpl, "repo_home.gph", gopher.MenuType)))
+ router.Route("/", rootDirHandler(gopherProto, repodir, tmpl, "repo_root.gph"))
+ router.Route("/:"+reponamekey, assignRepo(repodir)(repoRouteHandler(gopherProto, tmpl, "repo_home.gph")))
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())
- }
- }
+type gopherProtocol struct{ sliderule.ServerProtocol }
- 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.gph", obj); err != nil {
- return gopher.Error(err).Response()
- }
+func (gopherProtocol) TemplateBaseData(ctx context.Context, request *sliderule.Request) map[string]any {
+ return map[string]any{
+ "Host": request.Hostname(),
+ "Port": request.Port(),
+ "Selector": request.Path,
+ }
+}
- return gopher.File(gopher.MenuType, buf)
- })
+func (gopherProtocol) TemplateRepoData(ctx context.Context, request *sliderule.Request) map[string]any {
+ return map[string]any{
+ "Ctx": ctx,
+ "Repo": ctx.Value(repokey),
+ "Params": sliderule.RouteParams(ctx),
+ }
}
-func gopherTreePath(tmpl *template.Template, haspath bool) sliderule.Handler {
+var gopherProto = gopherProtocol{gopher.ServerProtocol}
+
+func gopherTreePath(tmpl *template.Template) sliderule.Handler {
return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
repo := ctx.Value(repokey).(*Repository)
params := sliderule.RouteParams(ctx)
+ _, haspath := params["path"]
t := "tree"
if haspath {
@@ -128,7 +118,7 @@ func gopherTreePath(tmpl *template.Template, haspath bool) sliderule.Handler {
if !haspath {
params["path"] = ""
}
- return runGopherTemplate(tmpl, "tree.gph", gopher.MenuType).Handle(ctx, request)
+ return repoRouteHandler(gopherProto, tmpl, "tree.gph").Handle(ctx, request)
}
body, err := repo.Blob(ctx, params["ref"], params["path"])
@@ -143,23 +133,3 @@ func gopherTreePath(tmpl *template.Template, haspath bool) sliderule.Handler {
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/handlers.go b/handlers.go
new file mode 100644
index 0000000..4b71b28
--- /dev/null
+++ b/handlers.go
@@ -0,0 +1,86 @@
+package syw
+
+import (
+ "bytes"
+ "context"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "text/template"
+
+ "tildegit.org/tjp/sliderule"
+)
+
+const (
+ repokey = "syw_repo"
+ reponamekey = "repository"
+)
+
+func rootDirHandler(proto protocol, repodir string, tmpl *template.Template, tmplname string) sliderule.Handler {
+ return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
+ entries, err := os.ReadDir(repodir)
+ if err != nil {
+ return proto.TemporaryServerError(err)
+ }
+
+ names := []string{}
+ for _, item := range entries {
+ if Open(filepath.Join(repodir, item.Name())) != nil {
+ names = append(names, item.Name())
+ }
+ }
+
+ data := proto.TemplateBaseData(ctx, request)
+ data["Repos"] = names
+ buf := &bytes.Buffer{}
+ if err := tmpl.ExecuteTemplate(buf, tmplname, data); err != nil {
+ return proto.TemporaryServerError(err)
+ }
+
+ return proto.Success(tmplname, buf)
+ })
+}
+
+func repoRouteHandler(proto protocol, tmpl *template.Template, tmplname string) sliderule.Handler {
+ return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
+ data := repoTmplData(proto, ctx, request)
+ buf := &bytes.Buffer{}
+ if err := tmpl.ExecuteTemplate(buf, tmplname, data); err != nil {
+ return proto.TemporaryServerError(err)
+ }
+ return proto.Success(tmplname, buf)
+ })
+}
+
+func treePathHandler(proto protocol, tmpl *template.Template, tmplname string) sliderule.Handler {
+ return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
+ params := sliderule.RouteParams(ctx)
+ if params["path"] == "" || strings.HasSuffix(params["path"], "/") {
+ return repoRouteHandler(proto, tmpl, tmplname).Handle(ctx, request)
+ }
+
+ body, err := ctx.Value(repokey).(*Repository).Blob(ctx, params["ref"], params["path"])
+ if err != nil {
+ return proto.TemporaryServerError(err)
+ }
+ return proto.Success(path.Base(params["path"]), bytes.NewBuffer(body))
+ })
+}
+
+func repoTmplData(proto protocol, ctx context.Context, request *sliderule.Request) map[string]any {
+ data := proto.TemplateBaseData(ctx, request)
+ for k, v := range proto.TemplateRepoData(ctx, request) {
+ data[k] = v
+ }
+ return data
+}
+
+func assignRepo(repodir string) sliderule.Middleware {
+ return func(h sliderule.Handler) sliderule.Handler {
+ return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
+ repo := Open(filepath.Join(repodir, sliderule.RouteParams(ctx)[reponamekey]))
+ return h.Handle(context.WithValue(ctx, repokey, repo), request) //nolint:staticcheck
+ })
+ }
+}
diff --git a/protocol.go b/protocol.go
new file mode 100644
index 0000000..dc2abbc
--- /dev/null
+++ b/protocol.go
@@ -0,0 +1,14 @@
+package syw
+
+import (
+ "context"
+
+ "tildegit.org/tjp/sliderule"
+)
+
+type protocol interface {
+ sliderule.ServerProtocol
+
+ TemplateBaseData(context.Context, *sliderule.Request) map[string]any
+ TemplateRepoData(context.Context, *sliderule.Request) map[string]any
+}
diff --git a/spartan.go b/spartan.go
index beb001e..69db47d 100644
--- a/spartan.go
+++ b/spartan.go
@@ -1,13 +1,7 @@
package syw
import (
- "bytes"
"context"
- "mime"
- "os"
- "path"
- "path/filepath"
- "strings"
"text/template"
"tildegit.org/tjp/sliderule"
@@ -47,7 +41,8 @@ import (
// Repo: a *syw.Repository object corresponding to <repodir>/:repository
// Params: a map[string]string of the route parameters
//
-// The only exception is repo_root.gmi, which is rendered with a slice of the repo names instead.
+// The only exception is repo_root.gmi, which is rendered with an object containing a single name
+// "Repos", a slice of the string repository names.
func SpartanRouter(repodir string, overrides *template.Template) *sliderule.Router {
tmpl, err := addTemplates(geminiTemplate, overrides)
if err != nil {
@@ -56,90 +51,33 @@ func SpartanRouter(repodir string, overrides *template.Template) *sliderule.Rout
repoRouter := &sliderule.Router{}
repoRouter.Use(assignRepo(repodir))
- repoRouter.Route("/", sgmiTemplate(tmpl, "repo_home.gmi"))
- repoRouter.Route("/branches", sgmiTemplate(tmpl, "branch_list.gmi"))
- repoRouter.Route("/tags", sgmiTemplate(tmpl, "tag_list.gmi"))
- repoRouter.Route("/refs/:ref/", sgmiTemplate(tmpl, "ref.gmi"))
- repoRouter.Route("/refs/:ref/tree/*path", spartanTreePath(tmpl))
- repoRouter.Route("/diffstat/:fromref/:toref", runSpartanTemplate(tmpl, "diffstat.gmi.txt", "text/plain"))
- repoRouter.Route("/diff/:fromref/:toref", runSpartanTemplate(tmpl, "diff.gmi.txt", "text/x-diff"))
+ repoRouter.Route("/", repoRouteHandler(spartanProto, tmpl, "repo_home.gmi"))
+ repoRouter.Route("/branches", repoRouteHandler(spartanProto, tmpl, "branch_list.gmi"))
+ repoRouter.Route("/tags", repoRouteHandler(spartanProto, tmpl, "tag_list.gmi"))
+ repoRouter.Route("/refs/:ref/", repoRouteHandler(spartanProto, tmpl, "ref.gmi"))
+ repoRouter.Route("/refs/:ref/tree/*path", treePathHandler(spartanProto, tmpl, "tree.gmi"))
+ repoRouter.Route("/diffstat/:fromref/:toref", repoRouteHandler(spartanProto, tmpl, "diffstat.gmi.txt"))
+ repoRouter.Route("/diff/:fromref/:toref", repoRouteHandler(spartanProto, tmpl, "diff.gmi.txt"))
router := &sliderule.Router{}
- router.Route("/", spartanRoot(repodir, tmpl))
+ router.Route("/", rootDirHandler(spartanProto, repodir, tmpl, "repo_root.gmi"))
router.Mount("/:"+reponamekey, repoRouter)
return router
}
-func spartanRoot(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 spartan.ServerError(err)
- }
+type spartanProtocol struct{ sliderule.ServerProtocol }
- names := []string{}
- for _, item := range entries {
- if Open(filepath.Join(repodir, item.Name())) != nil {
- names = append(names, item.Name())
- }
- }
-
- buf := &bytes.Buffer{}
- if err := tmpl.ExecuteTemplate(buf, "repo_root.gmi", names); err != nil {
- return spartan.ServerError(err)
- }
-
- return spartan.Success("text/gemini; charset=utf-8", buf)
- })
-}
-
-func spartanTreePath(tmpl *template.Template) sliderule.Handler {
- return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
- params := sliderule.RouteParams(ctx)
- if params["path"] == "" || strings.HasSuffix(params["path"], "/") {
- return sgmiTemplate(tmpl, "tree.gmi").Handle(ctx, request)
- }
-
- repo := ctx.Value(repokey).(*Repository)
-
- body, err := repo.Blob(ctx, params["ref"], params["path"])
- if err != nil {
- return spartan.ServerError(err)
- }
-
- mediaType := ""
- ext := path.Ext(params["path"])
- if ext == ".gmi" {
- mediaType = "text/gemini; charset=utf-8"
- } else {
- mediaType = mime.TypeByExtension(ext)
- }
- if mediaType == "" {
- mediaType = "application/octet-stream"
- }
-
- return spartan.Success(mediaType, bytes.NewBuffer(body))
- })
+func (spartanProtocol) TemplateBaseData(_ context.Context, _ *sliderule.Request) map[string]any {
+ return map[string]any{}
}
-func sgmiTemplate(tmpl *template.Template, name string) sliderule.Handler {
- return runSpartanTemplate(tmpl, name, "text/gemini; charset=utf-8")
+func (spartanProtocol) TemplateRepoData(ctx context.Context, request *sliderule.Request) map[string]any {
+ return map[string]any{
+ "Ctx": ctx,
+ "Repo": ctx.Value(repokey),
+ "Params": sliderule.RouteParams(ctx),
+ }
}
-func runSpartanTemplate(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,
- "Repo": ctx.Value(repokey),
- "Params": sliderule.RouteParams(ctx),
- }
- buf := &bytes.Buffer{}
-
- if err := tmpl.ExecuteTemplate(buf, name, obj); err != nil {
- return spartan.ServerError(err)
- }
-
- return spartan.Success(mimetype, buf)
- })
-}
+var spartanProto = spartanProtocol{spartan.ServerProtocol}
diff --git a/templates/repo_root.gmi b/templates/repo_root.gmi
index 68b230b..ac82038 100644
--- a/templates/repo_root.gmi
+++ b/templates/repo_root.gmi
@@ -1,5 +1,5 @@
# Repositories
-{{ range . -}}
+{{ range .Repos -}}
=> ./{{.}}/ {{.}}
{{ end }}