blob: 3747b0343321112aa2eee98f6d648cc5af210cef [file] [log] [blame]
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
package godoc
import (
"bytes"
"fmt"
"go/ast"
"go/doc"
"go/token"
"path"
"strconv"
"strings"
"text/template"
"golang.org/x/website/internal/pkgdoc"
)
// FuncMap defines template functions used in godoc templates.
//
// Convention: template function names ending in "_html" or "_url" produce
// HTML- or URL-escaped strings; all other function results may
// require explicit escaping in the template.
func (p *Presentation) FuncMap() template.FuncMap {
p.initFuncMapOnce.Do(p.initFuncMap)
return p.funcMap
}
func (p *Presentation) TemplateFuncs() template.FuncMap {
p.initFuncMapOnce.Do(p.initFuncMap)
return p.templateFuncs
}
func (p *Presentation) initFuncMap() {
if p.Corpus == nil {
panic("nil Presentation.Corpus")
}
p.templateFuncs = template.FuncMap{
"code": p.code,
}
p.funcMap = template.FuncMap{
// various helpers
"filename": filenameFunc,
"since": p.Corpus.pkgAPIInfo.Func,
// formatting of AST nodes
"node": p.nodeFunc,
"node_html": p.node_htmlFunc,
"comment_html": comment_htmlFunc,
"sanitize": sanitizeFunc,
// support for URL attributes
"pkgLink": pkgLinkFunc,
"srcLink": srcLinkFunc,
"posLink_url": posLink_urlFunc,
"docLink": docLinkFunc,
"queryLink": queryLinkFunc,
"srcBreadcrumb": srcBreadcrumbFunc,
"srcToPkgLink": srcToPkgLinkFunc,
// formatting of Examples
"example_html": p.example_htmlFunc,
"example_name": p.example_nameFunc,
"example_suffix": p.example_suffixFunc,
// Number operation
"multiply": multiply,
// formatting of PageInfoMode query string
"modeQueryString": modeQueryString,
}
}
func multiply(a, b int) int { return a * b }
func filenameFunc(name string) string {
_, localname := path.Split(name)
return localname
}
func pkgLinkFunc(path string) string {
// because of the irregular mapping under goroot
// we need to correct certain relative paths
path = strings.TrimPrefix(path, "/")
path = strings.TrimPrefix(path, "src/")
path = strings.TrimPrefix(path, "pkg/")
return "pkg/" + path
}
// srcToPkgLinkFunc builds an <a> tag linking to the package
// documentation of relpath.
func srcToPkgLinkFunc(relpath string) string {
relpath = pkgLinkFunc(relpath)
relpath = path.Dir(relpath)
if relpath == "pkg" {
return `<a href="/pkg">Index</a>`
}
return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
}
// srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
// Each segment links to its corresponding src directories.
func srcBreadcrumbFunc(relpath string) string {
segments := strings.Split(relpath, "/")
var buf bytes.Buffer
var selectedSegment string
var selectedIndex int
if strings.HasSuffix(relpath, "/") {
// relpath is a directory ending with a "/".
// Selected segment is the segment before the last slash.
selectedIndex = len(segments) - 2
selectedSegment = segments[selectedIndex] + "/"
} else {
selectedIndex = len(segments) - 1
selectedSegment = segments[selectedIndex]
}
for i := range segments[:selectedIndex] {
buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
strings.Join(segments[:i+1], "/"),
segments[i],
))
}
buf.WriteString(`<span class="text-muted">`)
buf.WriteString(selectedSegment)
buf.WriteString(`</span>`)
return buf.String()
}
func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string {
// n must be an ast.Node or a *doc.Note
var pos, end token.Pos
switch n := n.(type) {
case ast.Node:
pos = n.Pos()
end = n.End()
case *doc.Note:
pos = n.Pos
end = n.End
default:
panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
}
var relpath string
var line int
var low, high int // selection offset range
if pos.IsValid() {
p := info.FSet.Position(pos)
relpath = p.Filename
line = p.Line
low = p.Offset
}
if end.IsValid() {
high = info.FSet.Position(end).Offset
}
return srcPosLinkFunc(relpath, line, low, high)
}
func srcPosLinkFunc(s string, line, low, high int) string {
s = srcLinkFunc(s)
var buf bytes.Buffer
template.HTMLEscape(&buf, []byte(s))
// selection ranges are of form "s=low:high"
if low < high {
fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
// if we have a selection, position the page
// such that the selection is a bit below the top
line -= 10
if line < 1 {
line = 1
}
}
// line id's in html-printed source are of the
// form "L%d" where %d stands for the line number
if line > 0 {
fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
}
return buf.String()
}
func srcLinkFunc(s string) string {
s = path.Clean("/" + s)
if !strings.HasPrefix(s, "/src/") {
s = "/src" + s
}
return s
}
// queryLinkFunc returns a URL for a line in a source file with a highlighted
// query term.
// s is expected to be a path to a source file.
// query is expected to be a string that has already been appropriately escaped
// for use in a URL query.
func queryLinkFunc(s, query string, line int) string {
url := path.Clean("/"+s) + "?h=" + query
if line > 0 {
url += "#L" + strconv.Itoa(line)
}
return url
}
func docLinkFunc(s string, ident string) string {
return path.Clean("/pkg/"+s) + "/#" + ident
}