render.go

v1.3.3
Doc Versions Source
1
package dochtml
2
3
import (
4
	"embed"
5
	"html/template"
6
	"io"
7
	"io/fs"
8
	"net/http"
9
	"sort"
10
	"strings"
11
12
	"go.bigb.es/curator/internal/godoc"
13
)
14
15
//go:embed templates/* static/*
16
var content embed.FS
17
18
// StaticFS returns the embedded static assets filesystem.
19
func StaticFS() http.FileSystem {
20
	sub, _ := fs.Sub(content, "static")
21
	return http.FS(sub)
22
}
23
24
// IndexEntry represents an entry in the package index.
25
type IndexEntry struct {
26
	Anchor   string
27
	Text     string
28
	Children []IndexEntry // constructors and methods (for types)
29
}
30
31
// ModulePageData is the template data for module/package pages.
32
type ModulePageData struct {
33
	Host         string
34
	ImportPrefix string
35
	ModuleName   string
36
	VCS          string
37
	Repo         string
38
	Version      string
39
	SubPath      string
40
	Versions     []string
41
42
	// Package doc fields.
43
	Doc         template.HTML
44
	Synopsis    string
45
	PackageName string
46
	Index       []IndexEntry
47
	Consts      []ValueData
48
	Vars        []ValueData
49
	Funcs       []FuncData
50
	Types       []TypeData
51
	SubPackages []godoc.SubPkgSummary
52
	SourceFiles []string
53
}
54
55
// ValueData wraps godoc.ValueDoc for templates.
56
type ValueData struct {
57
	Names []string
58
	Doc   template.HTML
59
	Decl  string
60
}
61
62
// FuncData wraps godoc.FuncDoc for templates.
63
type FuncData struct {
64
	Name string
65
	Doc  template.HTML
66
	Decl string
67
	Recv string
68
}
69
70
// TypeData wraps godoc.TypeDoc for templates.
71
type TypeData struct {
72
	Name    string
73
	Doc     template.HTML
74
	Decl    string
75
	Consts  []ValueData
76
	Vars    []ValueData
77
	Funcs   []FuncData
78
	Methods []FuncData
79
}
80
81
// SourcePageData is the template data for source file view.
82
type SourcePageData struct {
83
	Host              string
84
	ImportPrefix      string
85
	ModuleName        string
86
	VCS               string
87
	Repo              string
88
	Version           string
89
	SubPath           string
90
	FileName          string
91
	HighlightedSource template.HTML
92
	SourceFiles       []string
93
}
94
95
// VersionsPageData is the template data for the versions list.
96
type VersionsPageData struct {
97
	Host           string
98
	ImportPrefix   string
99
	ModuleName     string
100
	VCS            string
101
	Repo           string
102
	Versions       []string
103
	CurrentVersion string
104
}
105
106
// Renderer handles HTML rendering of documentation pages.
107
type Renderer struct {
108
	moduleTmpl   *template.Template
109
	sourceTmpl   *template.Template
110
	versionsTmpl *template.Template
111
}
112
113
// NewRenderer creates a renderer with parsed templates.
114
func NewRenderer() (*Renderer, error) {
115
	base, err := template.New("base").Parse(mustRead("templates/base.html"))
116
	if err != nil {
117
		return nil, err
118
	}
119
120
	moduleTmpl, err := template.Must(base.Clone()).Parse(mustRead("templates/module.html"))
121
	if err != nil {
122
		return nil, err
123
	}
124
125
	sourceTmpl, err := template.Must(base.Clone()).Parse(mustRead("templates/source.html"))
126
	if err != nil {
127
		return nil, err
128
	}
129
130
	versionsTmpl, err := template.Must(base.Clone()).Parse(mustRead("templates/versions.html"))
131
	if err != nil {
132
		return nil, err
133
	}
134
135
	return &Renderer{
136
		moduleTmpl:   moduleTmpl,
137
		sourceTmpl:   sourceTmpl,
138
		versionsTmpl: versionsTmpl,
139
	}, nil
140
}
141
142
// RenderModule renders a module/package documentation page.
143
func (r *Renderer) RenderModule(w io.Writer, data *ModulePageData) error {
144
	return r.moduleTmpl.Execute(w, data)
145
}
146
147
// RenderSource renders a source code view page.
148
func (r *Renderer) RenderSource(w io.Writer, data *SourcePageData) error {
149
	return r.sourceTmpl.Execute(w, data)
150
}
151
152
// RenderVersions renders a version list page.
153
func (r *Renderer) RenderVersions(w io.Writer, data *VersionsPageData) error {
154
	return r.versionsTmpl.Execute(w, data)
155
}
156
157
// BuildModulePageData creates template data from a PackageDoc.
158
func BuildModulePageData(host, modName, version, subpath, vcs, repo string, doc *godoc.PackageDoc, versions []string, subPkgs []godoc.SubPkgSummary) *ModulePageData {
159
	data := &ModulePageData{
160
		Host:         host,
161
		ImportPrefix: host + "/" + modName,
162
		ModuleName:   modName,
163
		VCS:          vcs,
164
		Repo:         repo,
165
		Version:      version,
166
		SubPath:      subpath,
167
		Versions:     versions,
168
		SubPackages:  subPkgs,
169
	}
170
171
	if doc != nil {
172
		data.Doc = template.HTML(doc.Doc)
173
		data.Synopsis = doc.Synopsis
174
		data.PackageName = doc.Name
175
		data.SourceFiles = doc.Files
176
177
		// Build hierarchical index: funcs sorted, then types sorted
178
		// with constructors and methods as children.
179
		var funcIndex []IndexEntry
180
		for _, f := range doc.Funcs {
181
			funcIndex = append(funcIndex, IndexEntry{
182
				Anchor: f.Name,
183
				Text:   f.ShortDecl,
184
			})
185
		}
186
		sort.Slice(funcIndex, func(i, j int) bool {
187
			return funcIndex[i].Text < funcIndex[j].Text
188
		})
189
190
		var typeIndex []IndexEntry
191
		for _, t := range doc.Types {
192
			entry := IndexEntry{
193
				Anchor: t.Name,
194
				Text:   "type " + t.Name,
195
			}
196
197
			// Constructors (sorted).
198
			var constructors []IndexEntry
199
			for _, f := range t.Funcs {
200
				constructors = append(constructors, IndexEntry{
201
					Anchor: f.Name,
202
					Text:   f.ShortDecl,
203
				})
204
			}
205
			sort.Slice(constructors, func(i, j int) bool {
206
				return constructors[i].Text < constructors[j].Text
207
			})
208
			entry.Children = append(entry.Children, constructors...)
209
210
			// Type-associated constants (sorted).
211
			var consts []IndexEntry
212
			for _, c := range t.Consts {
213
				for _, name := range c.Names {
214
					consts = append(consts, IndexEntry{
215
						Anchor: name,
216
						Text:   "const " + name,
217
					})
218
				}
219
			}
220
			for _, v := range t.Vars {
221
				for _, name := range v.Names {
222
					consts = append(consts, IndexEntry{
223
						Anchor: name,
224
						Text:   "var " + name,
225
					})
226
				}
227
			}
228
			sort.Slice(consts, func(i, j int) bool {
229
				return consts[i].Text < consts[j].Text
230
			})
231
			entry.Children = append(entry.Children, consts...)
232
233
			// Methods (sorted).
234
			var methods []IndexEntry
235
			for _, m := range t.Methods {
236
				methods = append(methods, IndexEntry{
237
					Anchor: t.Name + "." + m.Name,
238
					Text:   m.ShortDecl,
239
				})
240
			}
241
			sort.Slice(methods, func(i, j int) bool {
242
				return methods[i].Text < methods[j].Text
243
			})
244
			entry.Children = append(entry.Children, methods...)
245
			typeIndex = append(typeIndex, entry)
246
		}
247
		sort.Slice(typeIndex, func(i, j int) bool {
248
			return typeIndex[i].Text < typeIndex[j].Text
249
		})
250
251
		data.Index = append(funcIndex, typeIndex...)
252
253
		// Convert godoc types to template types.
254
		for _, c := range doc.Consts {
255
			data.Consts = append(data.Consts, convertValue(c))
256
		}
257
		for _, v := range doc.Vars {
258
			data.Vars = append(data.Vars, convertValue(v))
259
		}
260
		for _, f := range doc.Funcs {
261
			data.Funcs = append(data.Funcs, convertFunc(f))
262
		}
263
		for _, t := range doc.Types {
264
			data.Types = append(data.Types, convertType(t))
265
		}
266
	}
267
268
	return data
269
}
270
271
func convertValue(v godoc.ValueDoc) ValueData {
272
	return ValueData{
273
		Names: v.Names,
274
		Doc:   template.HTML(v.Doc),
275
		Decl:  v.Decl,
276
	}
277
}
278
279
func convertFunc(f godoc.FuncDoc) FuncData {
280
	return FuncData{
281
		Name: f.Name,
282
		Doc:  template.HTML(f.Doc),
283
		Decl: f.Decl,
284
		Recv: f.Recv,
285
	}
286
}
287
288
func convertType(t godoc.TypeDoc) TypeData {
289
	td := TypeData{
290
		Name: t.Name,
291
		Doc:  template.HTML(t.Doc),
292
		Decl: t.Decl,
293
	}
294
	for _, c := range t.Consts {
295
		td.Consts = append(td.Consts, convertValue(c))
296
	}
297
	for _, v := range t.Vars {
298
		td.Vars = append(td.Vars, convertValue(v))
299
	}
300
	for _, f := range t.Funcs {
301
		td.Funcs = append(td.Funcs, convertFunc(f))
302
	}
303
	for _, m := range t.Methods {
304
		td.Methods = append(td.Methods, convertFunc(m))
305
	}
306
	return td
307
}
308
309
// TrustedHTML converts a string to template.HTML for pre-sanitized content.
310
func TrustedHTML(s string) template.HTML {
311
	return template.HTML(s)
312
}
313
314
func mustRead(name string) string {
315
	data, err := content.ReadFile(name)
316
	if err != nil {
317
		panic("dochtml: " + err.Error())
318
	}
319
	return strings.TrimSpace(string(data))
320
}
321

Source Files