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, overrides *template.Template) *sliderule.Router { tmpl, err := addTemplates(geminiTemplate, overrides) if err != nil { panic(err) } 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", "text/plain")) repoRouter.Route("/diff/:fromref/:toref", runGemiTemplate(tmpl, "diff.gmi", "text/x-diff")) router := &sliderule.Router{} router.Route("/", geminiRoot(repodir, tmpl)) 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) } 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 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 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) }) }