diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-09-16 20:43:19 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-09-16 20:43:19 -0600 |
commit | 90fa2795ad177b9add2fe46382576993e96ece4b (patch) | |
tree | 0e2847f8694fa66a914680329c4515101b5a45ab /gemini.go |
Initial commit
Gemini repository browsing
Diffstat (limited to 'gemini.go')
-rw-r--r-- | gemini.go | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/gemini.go b/gemini.go new file mode 100644 index 0000000..6222684 --- /dev/null +++ b/gemini.go @@ -0,0 +1,118 @@ +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 = "syw_reponame" +) + +func GeminiRouter(repodir string) *sliderule.Router { + repoRouter := &sliderule.Router{} + repoRouter.Use(assignRepo(repodir)) + repoRouter.Route("/", gmiTemplate(geminiTemplate, "repo_home.gmi")) + repoRouter.Route("/branches", gmiTemplate(geminiTemplate, "branch_list.gmi")) + repoRouter.Route("/tags", gmiTemplate(geminiTemplate, "tag_list.gmi")) + repoRouter.Route("/refs/:ref/", gmiTemplate(geminiTemplate, "ref.gmi")) + repoRouter.Route("/refs/:ref/tree/*path", sliderule.HandlerFunc(geminiTreePath)) + repoRouter.Route("/diffstat/:fromref/:toref", runTemplate(geminiTemplate, "diffstat.gmi", "text/plain")) + repoRouter.Route("/diff/:fromref/:toref", runTemplate(geminiTemplate, "diff.gmi", "text/x-diff")) + + router := &sliderule.Router{} + router.Route("/", geminiRoot(repodir)) + 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) 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) + } + + names := []string{} + for _, item := range entries { + if Open(filepath.Join(repodir, item.Name())) != nil { + names = append(names, item.Name()) + } + } + + buf := &bytes.Buffer{} + if err := geminiTemplate.ExecuteTemplate(buf, "repo_root.gmi", names); err != nil { + return gemini.Failure(err) + } + + return gemini.Success("text/gemini; charset=utf-8", buf) + }) +} + +func geminiTreePath(ctx context.Context, request *sliderule.Request) *sliderule.Response { + params := sliderule.RouteParams(ctx) + if params["path"] == "" || strings.HasSuffix(params["path"], "/") { + return gmiTemplate(geminiTemplate, "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 runTemplate(tmpl, name, "text/gemini; charset=utf-8") +} + +func runTemplate(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) + }) +} |