From ce0def95f3924a10b0faceb72aa5df18bf813fb1 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Sun, 17 Sep 2023 14:00:03 -0600 Subject: GOPHER --- cmd.go | 5 ++ commit.go | 4 ++ gemini.go | 8 +-- gopher.go | 129 ++++++++++++++++++++++++++++++++++++++++ repo.go | 4 ++ templates.go | 42 +++++++++++++ templates/branch_list.gophermap | 9 +++ templates/diff.gophertext | 1 + templates/diffstat.gophertext | 1 + templates/ref.gmi | 2 +- templates/ref.gophermap | 34 +++++++++++ templates/repo_home.gophermap | 20 +++++++ templates/repo_root.gophermap | 7 +++ templates/tag_list.gophermap | 9 +++ templates/tree.gophermap | 9 +++ 15 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 gopher.go create mode 100644 templates/branch_list.gophermap create mode 100644 templates/diff.gophertext create mode 100644 templates/diffstat.gophertext create mode 100644 templates/ref.gophermap create mode 100644 templates/repo_home.gophermap create mode 100644 templates/repo_root.gophermap create mode 100644 templates/tag_list.gophermap create mode 100644 templates/tree.gophermap 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 -}} +. -- cgit v1.2.3