render.go

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

Source Files