mirror of
https://github.com/golang/go
synced 2024-11-18 15:04:44 -07:00
705bb7ffce
The exported Server becomes handlerServer, and part of Presentation now. Presentation is also now an http.Handler with its own internal mux (a detail, which might go away). main.go becomes ever simpler. R=golang-dev, adg CC=golang-dev https://golang.org/cl/11505043
235 lines
6.1 KiB
Go
235 lines
6.1 KiB
Go
// 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.
|
|
|
|
// This file implements LinkifyText which introduces
|
|
// links for identifiers pointing to their declarations.
|
|
// The approach does not cover all cases because godoc
|
|
// doesn't have complete type information, but it's
|
|
// reasonably good for browsing.
|
|
|
|
package godoc
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"io"
|
|
"strconv"
|
|
)
|
|
|
|
// LinkifyText HTML-escapes source text and writes it to w.
|
|
// Identifiers that are in a "use" position (i.e., that are
|
|
// not being declared), are wrapped with HTML links pointing
|
|
// to the respective declaration, if possible. Comments are
|
|
// formatted the same way as with FormatText.
|
|
//
|
|
func LinkifyText(w io.Writer, text []byte, n ast.Node) {
|
|
links := linksFor(n)
|
|
|
|
i := 0 // links index
|
|
prev := "" // prev HTML tag
|
|
linkWriter := func(w io.Writer, _ int, start bool) {
|
|
// end tag
|
|
if !start {
|
|
if prev != "" {
|
|
fmt.Fprintf(w, `</%s>`, prev)
|
|
prev = ""
|
|
}
|
|
return
|
|
}
|
|
|
|
// start tag
|
|
prev = ""
|
|
if i < len(links) {
|
|
switch info := links[i]; {
|
|
case info.path != "" && info.name == "":
|
|
// package path
|
|
fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
|
|
prev = "a"
|
|
case info.path != "" && info.name != "":
|
|
// qualified identifier
|
|
fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
|
|
prev = "a"
|
|
case info.path == "" && info.name != "":
|
|
// local identifier
|
|
if info.mode == identVal {
|
|
fmt.Fprintf(w, `<span id="%s">`, info.name)
|
|
prev = "span"
|
|
} else if ast.IsExported(info.name) {
|
|
fmt.Fprintf(w, `<a href="#%s">`, info.name)
|
|
prev = "a"
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
idents := tokenSelection(text, token.IDENT)
|
|
comments := tokenSelection(text, token.COMMENT)
|
|
FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
|
|
}
|
|
|
|
// A link describes the (HTML) link information for an identifier.
|
|
// The zero value of a link represents "no link".
|
|
//
|
|
type link struct {
|
|
mode identMode
|
|
path, name string // package path, identifier name
|
|
}
|
|
|
|
// linksFor returns the list of links for the identifiers used
|
|
// by node in the same order as they appear in the source.
|
|
//
|
|
func linksFor(node ast.Node) (list []link) {
|
|
modes := identModesFor(node)
|
|
|
|
// NOTE: We are expecting ast.Inspect to call the
|
|
// callback function in source text order.
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.Ident:
|
|
m := modes[n]
|
|
info := link{mode: m}
|
|
switch m {
|
|
case identUse:
|
|
if n.Obj == nil && predeclared[n.Name] {
|
|
info.path = builtinPkgPath
|
|
}
|
|
info.name = n.Name
|
|
case identDef:
|
|
// any declaration expect const or var - empty link
|
|
case identVal:
|
|
// const or var declaration
|
|
info.name = n.Name
|
|
}
|
|
list = append(list, info)
|
|
return false
|
|
case *ast.SelectorExpr:
|
|
// Detect qualified identifiers of the form pkg.ident.
|
|
// If anything fails we return true and collect individual
|
|
// identifiers instead.
|
|
if x, _ := n.X.(*ast.Ident); x != nil {
|
|
// x must be a package for a qualified identifier
|
|
if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
|
|
if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
|
|
// spec.Path.Value is the import path
|
|
if path, err := strconv.Unquote(spec.Path.Value); err == nil {
|
|
// Register two links, one for the package
|
|
// and one for the qualified identifier.
|
|
info := link{path: path}
|
|
list = append(list, info)
|
|
info.name = n.Sel.Name
|
|
list = append(list, info)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// The identMode describes how an identifier is "used" at its source location.
|
|
type identMode int
|
|
|
|
const (
|
|
identUse identMode = iota // identifier is used (must be zero value for identMode)
|
|
identDef // identifier is defined
|
|
identVal // identifier is defined in a const or var declaration
|
|
)
|
|
|
|
// identModesFor returns a map providing the identMode for each identifier used by node.
|
|
func identModesFor(node ast.Node) map[*ast.Ident]identMode {
|
|
m := make(map[*ast.Ident]identMode)
|
|
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.Field:
|
|
for _, n := range n.Names {
|
|
m[n] = identDef
|
|
}
|
|
case *ast.ImportSpec:
|
|
if name := n.Name; name != nil {
|
|
m[name] = identDef
|
|
}
|
|
case *ast.ValueSpec:
|
|
for _, n := range n.Names {
|
|
m[n] = identVal
|
|
}
|
|
case *ast.TypeSpec:
|
|
m[n.Name] = identDef
|
|
case *ast.FuncDecl:
|
|
m[n.Name] = identDef
|
|
case *ast.AssignStmt:
|
|
// Short variable declarations only show up if we apply
|
|
// this code to all source code (as opposed to exported
|
|
// declarations only).
|
|
if n.Tok == token.DEFINE {
|
|
// Some of the lhs variables may be re-declared,
|
|
// so technically they are not defs. We don't
|
|
// care for now.
|
|
for _, x := range n.Lhs {
|
|
// Each lhs expression should be an
|
|
// ident, but we are conservative and check.
|
|
if n, _ := x.(*ast.Ident); n != nil {
|
|
m[n] = identVal
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return m
|
|
}
|
|
|
|
// The predeclared map represents the set of all predeclared identifiers.
|
|
// TODO(gri) This information is also encoded in similar maps in go/doc,
|
|
// but not exported. Consider exporting an accessor and using
|
|
// it instead.
|
|
var predeclared = map[string]bool{
|
|
"bool": true,
|
|
"byte": true,
|
|
"complex64": true,
|
|
"complex128": true,
|
|
"error": true,
|
|
"float32": true,
|
|
"float64": true,
|
|
"int": true,
|
|
"int8": true,
|
|
"int16": true,
|
|
"int32": true,
|
|
"int64": true,
|
|
"rune": true,
|
|
"string": true,
|
|
"uint": true,
|
|
"uint8": true,
|
|
"uint16": true,
|
|
"uint32": true,
|
|
"uint64": true,
|
|
"uintptr": true,
|
|
"true": true,
|
|
"false": true,
|
|
"iota": true,
|
|
"nil": true,
|
|
"append": true,
|
|
"cap": true,
|
|
"close": true,
|
|
"complex": true,
|
|
"copy": true,
|
|
"delete": true,
|
|
"imag": true,
|
|
"len": true,
|
|
"make": true,
|
|
"new": true,
|
|
"panic": true,
|
|
"print": true,
|
|
"println": true,
|
|
"real": true,
|
|
"recover": true,
|
|
}
|