~bigbes/sr-ht-compare

ref: 2ce47ce90a72fbe1f33bfd2ffb9eb76ea363cf42 sr-ht-compare/web/templates.go -rw-r--r-- 3.6 KiB
2ce47ce9 — bigbes ci: cache Go module and build dirs via cacher 13 days ago
                                                                                
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
package web

import (
	"bytes"
	"embed"
	"html/template"
	"net/http"
	"time"

	"github.com/sirupsen/logrus"
)

// tmplFS holds the page templates. Each page is parsed together with the shared
// layout into its own template set so that per-page "content"/"scripts" defines
// do not collide across pages.
//
//go:embed templates/*.html
var tmplFS embed.FS

// staticFS holds the built front-end assets (the hashed bundle.<hash>.js, the
// hashed stylesheet, logo.svg). It is served read-only under /static/.
//
//go:embed static
var staticFS embed.FS

// funcMap holds the template helpers shared by every page.
var funcMap = template.FuncMap{
	// shortsha abbreviates an object id to 8 hex chars.
	"shortsha": func(s string) string {
		if len(s) > 8 {
			return s[:8]
		}
		return s
	},
	// date formats a commit timestamp for display.
	"date": func(t time.Time) string {
		return t.UTC().Format("2006-01-02 15:04 MST")
	},
	// statusClass maps a git file-change status letter to a CSS modifier used by
	// the .diff-status badge (see the diff-status rules in layout.html). Anything
	// unrecognized falls back to the neutral "o".
	"statusClass": func(s string) string {
		if s == "" {
			return "o"
		}
		switch s[0] {
		case 'A', 'a':
			return "a"
		case 'M', 'm':
			return "m"
		case 'D', 'd':
			return "d"
		case 'R', 'r':
			return "r"
		default:
			return "o"
		}
	},
	// statusLabel spells out a status letter for the badge's tooltip.
	"statusLabel": func(s string) string {
		if s == "" {
			return "changed"
		}
		switch s[0] {
		case 'A', 'a':
			return "added"
		case 'M', 'm':
			return "modified"
		case 'D', 'd':
			return "deleted"
		case 'R', 'r':
			return "renamed"
		default:
			return "changed"
		}
	},
}

// pageNames are the content templates; each is parsed with layout.html.
var pageNames = []string{"index", "repo", "compare", "commit", "error"}

// pages maps a page name to its parsed template set (layout + that page).
var pages = func() map[string]*template.Template {
	m := make(map[string]*template.Template, len(pageNames))
	for _, name := range pageNames {
		t := template.New("layout.html").Funcs(funcMap)
		t = template.Must(t.ParseFS(tmplFS, "templates/layout.html", "templates/"+name+".html"))
		m[name] = t
	}
	return m
}()

// render executes a page into a buffer first, so a template error yields a clean
// 500 rather than a half-written response. On success it writes the status and
// the buffered HTML.
func (s *Server) render(w http.ResponseWriter, status int, page string, vd viewData) {
	t, ok := pages[page]
	if !ok {
		logrus.WithField("page", page).Error("web: unknown template page")
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	var buf bytes.Buffer
	if err := t.ExecuteTemplate(&buf, "layout.html", vd); err != nil {
		logrus.WithError(err).WithField("page", page).Error("web: template execution failed")
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(status)
	_, _ = buf.WriteTo(w)
}

// errorData is the payload of the error page.
type errorData struct {
	Status     int
	StatusText string
	Message    string
}

// renderError renders the chrome-wrapped error page. It never recurses into
// render on failure (render falls back to http.Error itself).
func (s *Server) renderError(w http.ResponseWriter, r *http.Request, status int, message string) {
	vd := s.chrome(r)
	vd.Title = http.StatusText(status)
	vd.Data = errorData{
		Status:     status,
		StatusText: http.StatusText(status),
		Message:    message,
	}
	s.render(w, status, "error", vd)
}