1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
package syw
import (
"bytes"
"context"
"mime"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/gopher"
)
// GopherRouter builds a router that will handle gopher requests in a directory of git repositories.
//
// The routes it defines are:
// / gophermap listing of the repositories in the directory
// /:syw_reponame gophermap overview of the repository
// /:syw_reponame/branches gophermap list of branches/head
// /:syw_reponame/tags gophermap listing of tags
// /:syw_reponame/refs/:ref gophermap overview of a ref
// /:syw_reponame/refs/:ref/tree gophermap listing of a ref's root directory
// /:syw_reponame/refs/:ref/tree/*path for directories: gophermap list of contents
// for files: raw files (guessed item type text/binary/image/etc)
// /:syw_reponame/diffstat/:fromref/:toref text diffstat between two refs
// /:syw_reponame/diff/:fromref/:toref text diff between two refs
//
// The overrides argument can provide templates to define the behavior of nearly all of the above routes.
// All of them have default implementations, so the argument can be nil, but otherwise the template names
// used are:
// repo_root.gophermap gophermap at /
// repo_home.gophermap gophermap at /:syw_reponame
// branch_list.gophermap gophermap at /:syw_reponame/branches
// tag_list.gophermap gophermap at /:syw_reponame/tags
// ref.gophermap gophermap at /:syw_reponame/refs/:ref
// tree.gophermap gophermap at direcotry paths under /:syw_reponame/refs/:ref/tree/*path
// (file paths return the raw files without any template involved)
// diffstat.gophertext plain text diffstat at /:syw_reponame/diffstat/:fromref/:toref
// diff.gophertext plain text diff at /:syw_reponame/diff/:fromref/:toref
//
// Most of the templates above are rendered with an object with 6 fields:
// Ctx: the context.Context from the request
// Repo: a *syw.Repository corresponding to <repodir>/:syw_reponame
// Params: the map[string]string of the route parameters
// Host: the hostname of the running server
// Port: the port number of the running server
// Selector: the selector in the current request
//
// The only exception is repo_root.gophermap, which is instead rendered with a slice of the repo names.
//
// All templates have 3 additional functions made available to them:
// combine: func(string, ...string) string - successively combines paths using url.URL.ResolveReference
// join: func(string, ...string) string - successively joins path segments
// rawtext: func(selector, host, port, text string) string renders text lines as gopher info-message lines.
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)
})
}
|