summaryrefslogtreecommitdiff
path: root/gopher.go
blob: 7d9bdece2ea2a976d65d93676e189cf9bc987360 (plain)
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
package syw

import (
	"bytes"
	"context"
	"path"
	"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:
//
//	/                                    .gph listing of the repositories in the directory
//	/:repository                         .gph overview of the repository
//	/:repository/branches                .gph list of branches/head
//	/:repository/tags                    .gph listing of tags
//	/:repository/refs/:ref               .gph overview of a ref
//	/:repository/refs/:ref/tree          .gph listing of a ref's root directory
//	/:repository/refs/:ref/tree/*path     for directories:.gph list of contents
//	                                      for files: raw files (guessed item type text/binary/image/etc)
//	/:repository/diffstat/:fromref/:toref text diffstat between two refs
//	/:repository/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.gph      gophermap at /
//	repo_home.gph      gophermap at /:repository
//	branch_list.gph    gophermap at /:repository/branches
//	tag_list.gph       gophermap at /:repository/tags
//	ref.gph            gophermap at /:repository/refs/:ref
//	tree.gph           gophermap at direcotry paths under /:repository/refs/:ref/tree/*path
//	                   (file paths return the raw files without any template involved)
//	diffstat.gph.txt   plain text diffstat at /:repository/diffstat/:fromref/:toref
//	diff.gph.txt       plain text diff at /:repository/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>/:repository
//	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.gph, 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", 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("/", rootDirHandler(gopherProto, repodir, tmpl, "repo_root.gph"))
	router.Route("/:"+reponamekey, assignRepo(repodir)(repoRouteHandler(gopherProto, tmpl, "repo_home.gph")))
	router.Mount("/:"+reponamekey, repoRouter)

	return router
}

type gopherProtocol struct{ sliderule.ServerProtocol }

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,
	}
}

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),
	}
}

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 {
			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 repoRouteHandler(gopherProto, tmpl, "tree.gph").Handle(ctx, request)
		}

		body, err := repo.Blob(ctx, params["ref"], params["path"])
		if err != nil {
			return gopher.Error(err).Response()
		}

		filetype := gopher.MenuType
		if path.Base(params["path"]) != "gophermap" {
			filetype = gopher.GuessItemType(params["path"])
		}
		return gopher.File(filetype, bytes.NewBuffer(body))
	})
}