diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | gemini.go | 116 | ||||
-rw-r--r-- | gopher.go | 86 | ||||
-rw-r--r-- | handlers.go | 86 | ||||
-rw-r--r-- | protocol.go | 14 | ||||
-rw-r--r-- | spartan.go | 102 | ||||
-rw-r--r-- | templates/repo_root.gmi | 2 |
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 @@ -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} @@ -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 +} @@ -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 }} |