File: html.go

package info (click to toggle)
panicparse 2.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 744 kB
  • sloc: sh: 13; makefile: 5
file content (246 lines) | stat: -rw-r--r-- 7,200 bytes parent folder | download | duplicates (2)
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// Copyright 2017 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

//go:generate go run regen.go

package stack

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"net/url"
	"regexp"
	"runtime"
	"strings"
	"time"
)

// ToHTML formats the aggregated buckets as HTML to the writer.
//
// Use footer to add custom HTML at the bottom of the page.
func (a *Aggregated) ToHTML(w io.Writer, footer template.HTML) error {
	data := map[string]interface{}{
		"Aggregated": a,
		"Footer":     footer,
		"Snapshot":   a.Snapshot,
	}
	return toHTML(w, data)
}

// ToHTML formats the snapshot as HTML to the writer.
//
// Use footer to add custom HTML at the bottom of the page.
func (s *Snapshot) ToHTML(w io.Writer, footer template.HTML) error {
	data := map[string]interface{}{
		"Footer":   footer,
		"Snapshot": s,
	}
	return toHTML(w, data)
}

// Private stuff.

func toHTML(w io.Writer, data map[string]interface{}) error {
	m := template.FuncMap{
		"funcClass": funcClass,
		"minus":     minus,
		"pkgURL":    pkgURL,
		"srcURL":    srcURL,
		"symbol":    symbol,
	}
	data["Favicon"] = favicon
	data["GOMAXPROCS"] = runtime.GOMAXPROCS(0)
	data["Now"] = time.Now().Truncate(time.Second)
	data["Version"] = runtime.Version()
	t, err := template.New("t").Funcs(m).Parse(indexHTML)
	if err != nil {
		return err
	}
	return t.Execute(w, data)
}

var reMethodSymbol = regexp.MustCompile(`^\(\*?([^)]+)\)(\..+)$`)

func funcClass(c *Call) template.HTML {
	if c.Func.IsPkgMain {
		return "FuncMain Exported"
	}
	s := c.Location.String()
	if c.Func.IsExported {
		s += " Exported"
	}
	/* #nosec G203 */
	return template.HTML("Func") + template.HTML(template.HTMLEscapeString(s))
}

func minus(i, j int) int {
	return i - j
}

// pkgURL returns a link to the godoc for the call.
func pkgURL(c *Call) template.URL {
	imp := c.ImportPath
	// Check for vendored code first.
	if i := strings.Index(imp, "/vendor/"); i != -1 {
		imp = imp[i+8:]
	}
	ip := escape(imp)
	if ip == "" {
		return ""
	}
	url := template.URL("")
	if c.Location == Stdlib {
		// This always links to the latest release, past releases are not online.
		// That's somewhat unfortunate.
		url = "https://golang.org/pkg/"
	} else {
		// TODO(maruel): Leverage Location.
		// Use pkg.go.dev when there's a version (go module) and godoc.org when
		// there's none (implies branch master).
		_, branch := getSrcBranchURL(c)
		if branch == "master" || branch == "" {
			url = "https://godoc.org/"
		} else {
			url = "https://pkg.go.dev/"
		}
	}
	if c.Func.IsExported {
		return url + ip + template.URL("#") + symbol(&c.Func)
	}
	return url + ip
}

// srcURL returns an URL to the sources.
//
// TODO(maruel): Support custom local godoc server as it serves files too.
func srcURL(c *Call) template.URL {
	url, _ := getSrcBranchURL(c)
	return url
}

func escape(s string) template.URL {
	// That's the only way I found to get the kind of escaping I wanted, where
	// '/' is not escaped.
	u := url.URL{Path: s}
	/* #nosec G203 */
	return template.URL(u.EscapedPath())
}

// getSrcBranchURL returns a link to the source on the web and the tag name for
// the package version, if possible.
func getSrcBranchURL(c *Call) (template.URL, template.URL) {
	tag := ""
	if c.Location == Stdlib {
		// TODO(maruel): This is not strictly speaking correct. The remote could be
		// running a different Go version from the current executable.
		ver := runtime.Version()
		const devel = "devel +"
		if strings.HasPrefix(ver, devel) {
			ver = ver[len(devel) : len(devel)+10]
		}
		tag = url.QueryEscape(ver)
		/* #nosec G203 */
		return template.URL(fmt.Sprintf("https://github.com/golang/go/blob/%s/src/%s#L%d", tag, escape(c.RelSrcPath), c.Line)), template.URL(tag)
	}
	// TODO(maruel): Leverage Location.
	if rel := c.RelSrcPath; rel != "" {
		// Check for vendored code first.
		if i := strings.Index(rel, "/vendor/"); i != -1 {
			rel = rel[i+8:]
		}
		// Specialized support for github and golang.org. This will cover a fair
		// share of the URLs, but it'd be nice to support others too. Please submit
		// a PR (including a unit test that I was too lazy to add yet).
		switch host, rest := splitHost(rel); host {
		case "github.com":
			if parts := strings.SplitN(rest, "/", 3); len(parts) == 3 {
				p, srcTag, tag := splitTag(parts[1])
				url := fmt.Sprintf("https://github.com/%s/%s/blob/%s/%s#L%d", escape(parts[0]), p, srcTag, escape(parts[2]), c.Line)
				/* #nosec G203 */
				return template.URL(url), tag
			}
			log.Printf("problematic github.com URL: %q", rel)
		case "golang.org":
			// https://github.com/golang/build/blob/HEAD/repos/repos.go lists all
			// the golang.org/x/<foo> packages.
			if parts := strings.SplitN(rest, "/", 3); len(parts) == 3 && parts[0] == "x" {
				// parts is: "x", <project@version>, <path inside the repo>.
				p, srcTag, tag := splitTag(parts[1])
				// The source of truth is are actually go.googlesource.com, but
				// github.com has nicer syntax highlighting.
				url := fmt.Sprintf("https://github.com/golang/%s/blob/%s/%s#L%d", p, srcTag, escape(parts[2]), c.Line)
				/* #nosec G203 */
				return template.URL(url), tag
			}
			log.Printf("problematic golang.org URL: %q", rel)
		default:
			// For example gopkg.in. In this case there's no known way to find the
			// link to the source files, but we can still try to extract the version
			// if fetched from a go module.
			// Do a best effort to find a version by searching for a '@'.
			if i := strings.IndexByte(rel, '@'); i != -1 {
				if j := strings.IndexByte(rel[i:], '/'); j != -1 {
					tag = rel[i+1 : i+j]
				}
			}
		}
	}

	if c.LocalSrcPath != "" {
		/* #nosec G203 */
		return template.URL("file:///" + escape(c.LocalSrcPath)), template.URL(tag)
	}
	if c.RemoteSrcPath != "" {
		/* #nosec G203 */
		return template.URL("file:///" + escape(c.RemoteSrcPath)), template.URL(tag)
	}
	return "", ""
}

func splitHost(s string) (string, string) {
	parts := strings.SplitN(s, "/", 2)
	if len(parts) != 2 {
		return parts[0], ""
	}
	return parts[0], parts[1]
}

// "v0.0.0-20200223170610-d5e6a3e2c0ae"
var reVersion = regexp.MustCompile(`v\d+\.\d+\.\d+\-\d+\-([a-f0-9]+)`)

func splitTag(s string) (string, string, template.URL) {
	// Default to branch master for non-versioned dependencies. It's not
	// optimal but it's better than nothing?
	// TODO(maruel): Replace with HEAD.
	i := strings.IndexByte(s, '@')
	if i == -1 {
		// No tag was found.
		return s, "master", "master"
	}
	// We got a versioned go module.
	tag := s[i+1:]
	srcTag := tag
	if m := reVersion.FindStringSubmatch(tag); len(m) != 0 {
		srcTag = m[1]
	}
	/* #nosec G203 */
	return s[:i], url.QueryEscape(srcTag), template.URL(url.QueryEscape(tag))
}

// symbol is the hashtag to use to refer to the symbol when looking at
// documentation.
//
// All of godoc/gddo, pkg.go.dev and golang.org/godoc use the same symbol
// reference format.
func symbol(f *Func) template.URL {
	s := f.Name
	if reMethodSymbol.MatchString(s) {
		// Transform the method form.
		s = reMethodSymbol.ReplaceAllString(s, "$1$2")
	}
	/* #nosec G203 */
	return template.URL(url.QueryEscape(s))
}