// 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.
// Package godoc is a work-in-progress (2013-07-17) package to
// begin splitting up the godoc binary into multiple pieces.
//
// This package comment will evolve over time as this package splits
// into smaller pieces.
package godoc // import "golang.org/x/tools/godoc"
import (
"bytes"
"fmt"
"go/ast"
"go/doc"
"go/format"
"go/printer"
"go/token"
htmltemplate "html/template"
"io"
"log"
"os"
pathpkg "path"
"regexp"
"strconv"
"strings"
"text/template"
"time"
"unicode"
"unicode/utf8"
)
// Fake relative package path for built-ins. Documentation for all globals
// (not just exported ones) will be shown for packages in this directory.
const builtinPkgPath = "builtin"
// 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,
"repeat": strings.Repeat,
// access to FileInfos (directory listings)
"fileInfoName": fileInfoNameFunc,
"fileInfoTime": fileInfoTimeFunc,
// access to search result information
"infoKind_html": infoKind_htmlFunc,
"infoLine": p.infoLineFunc,
"infoSnippet_html": p.infoSnippet_htmlFunc,
// formatting of AST nodes
"node": p.nodeFunc,
"node_html": p.node_htmlFunc,
"comment_html": comment_htmlFunc,
"comment_text": comment_textFunc,
"sanitize": sanitizeFunc,
// support for URL attributes
"pkgLink": pkgLinkFunc,
"srcLink": srcLinkFunc,
"posLink_url": newPosLink_urlFunc(srcPosLinkFunc),
"docLink": docLinkFunc,
"queryLink": queryLinkFunc,
"srcBreadcrumb": srcBreadcrumbFunc,
"srcToPkgLink": srcToPkgLinkFunc,
// formatting of Examples
"example_html": p.example_htmlFunc,
"example_text": p.example_textFunc,
"example_name": p.example_nameFunc,
"example_suffix": p.example_suffixFunc,
// formatting of analysis information
"callgraph_html": p.callgraph_htmlFunc,
"implements_html": p.implements_htmlFunc,
"methodset_html": p.methodset_htmlFunc,
// formatting of Notes
"noteTitle": noteTitle,
// Number operation
"multiply": multiply,
// formatting of PageInfoMode query string
"modeQueryString": modeQueryString,
}
if p.URLForSrc != nil {
p.funcMap["srcLink"] = p.URLForSrc
}
if p.URLForSrcPos != nil {
p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
}
if p.URLForSrcQuery != nil {
p.funcMap["queryLink"] = p.URLForSrcQuery
}
}
func multiply(a, b int) int { return a * b }
func filenameFunc(path string) string {
_, localname := pathpkg.Split(path)
return localname
}
func fileInfoNameFunc(fi os.FileInfo) string {
name := fi.Name()
if fi.IsDir() {
name += "/"
}
return name
}
func fileInfoTimeFunc(fi os.FileInfo) string {
if t := fi.ModTime(); t.Unix() != 0 {
return t.Local().String()
}
return "" // don't return epoch if time is obviously not set
}
// The strings in infoKinds must be properly html-escaped.
var infoKinds = [nKinds]string{
PackageClause: "package clause",
ImportDecl: "import decl",
ConstDecl: "const decl",
TypeDecl: "type decl",
VarDecl: "var decl",
FuncDecl: "func decl",
MethodDecl: "method decl",
Use: "use",
}
func infoKind_htmlFunc(info SpotInfo) string {
return infoKinds[info.Kind()] // infoKind entries are html-escaped
}
func (p *Presentation) infoLineFunc(info SpotInfo) int {
line := info.Lori()
if info.IsIndex() {
index, _ := p.Corpus.searchIndex.Get()
if index != nil {
line = index.(*Index).Snippet(line).Line
} else {
// no line information available because
// we don't have an index - this should
// never happen; be conservative and don't
// crash
line = 0
}
}
return line
}
func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
if info.IsIndex() {
index, _ := p.Corpus.searchIndex.Get()
// Snippet.Text was HTML-escaped when it was generated
return index.(*Index).Snippet(info.Lori()).Text
}
return `no snippet text available`
}
func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
var buf bytes.Buffer
p.writeNode(&buf, info.FSet, node)
return buf.String()
}
func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
var buf1 bytes.Buffer
p.writeNode(&buf1, info.FSet, node)
var buf2 bytes.Buffer
if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
LinkifyText(&buf2, buf1.Bytes(), n)
if st, name := isStructTypeDecl(n); st != nil {
addStructFieldIDAttributes(&buf2, name, st)
}
} else {
FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
}
return buf2.String()
}
// isStructTypeDecl checks whether n is a struct declaration.
// It either returns a non-nil StructType and its name, or zero values.
func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
gd, ok := n.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
return nil, ""
}
if gd.Lparen > 0 {
// Parenthesized type. Who does that, anyway?
// TODO: Reportedly gri does. Fix this to handle that too.
return nil, ""
}
if len(gd.Specs) != 1 {
return nil, ""
}
ts, ok := gd.Specs[0].(*ast.TypeSpec)
if !ok {
return nil, ""
}
st, ok = ts.Type.(*ast.StructType)
if !ok {
return nil, ""
}
return st, ts.Name.Name
}
// addStructFieldIDAttributes modifies the contents of buf such that
// all struct fields of the named struct have
// in them, so people can link to /#Struct.Field.
func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
if st.Fields == nil {
return
}
// needsLink is a set of identifiers that still need to be
// linked, where value == key, to avoid an allocation in func
// linkedField.
needsLink := make(map[string]string)
for _, f := range st.Fields.List {
if len(f.Names) == 0 {
continue
}
fieldName := f.Names[0].Name
needsLink[fieldName] = fieldName
}
var newBuf bytes.Buffer
foreachLine(buf.Bytes(), func(line []byte) {
if fieldName := linkedField(line, needsLink); fieldName != "" {
fmt.Fprintf(&newBuf, ``, name, fieldName)
delete(needsLink, fieldName)
}
newBuf.Write(line)
})
buf.Reset()
buf.Write(newBuf.Bytes())
}
// foreachLine calls fn for each line of in, where a line includes
// the trailing "\n", except on the last line, if it doesn't exist.
func foreachLine(in []byte, fn func(line []byte)) {
for len(in) > 0 {
nl := bytes.IndexByte(in, '\n')
if nl == -1 {
fn(in)
return
}
fn(in[:nl+1])
in = in[nl+1:]
}
}
// commentPrefix is the line prefix for comments after they've been HTMLified.
var commentPrefix = []byte(`