godoc: revert support for Go 1.8 aliases
Change-Id: Ibb3afede1121bd53567f3ff70b886b02dd81399f Reviewed-on: https://go-review.googlesource.com/32832 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
e2e4d7aa62
commit
188d338f0b
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
// This file contains the infrastructure to create an
|
// This file contains the infrastructure to create an
|
||||||
// identifier and full-text index for a set of Go files.
|
// identifier and full-text index for a set of Go files.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
1596
godoc/index18.go
1596
godoc/index18.go
File diff suppressed because it is too large
Load Diff
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
// This file implements LinkifyText which introduces
|
// This file implements LinkifyText which introduces
|
||||||
// links for identifiers pointing to their declarations.
|
// links for identifiers pointing to their declarations.
|
||||||
// The approach does not cover all cases because godoc
|
// The approach does not cover all cases because godoc
|
||||||
|
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
// 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.AliasSpec:
|
|
||||||
m[n.Name] = identVal
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
package godoc
|
package godoc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -1,768 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package godoc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/doc"
|
|
||||||
"go/token"
|
|
||||||
htmlpkg "html"
|
|
||||||
htmltemplate "html/template"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
pathpkg "path"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/tools/godoc/analysis"
|
|
||||||
"golang.org/x/tools/godoc/util"
|
|
||||||
"golang.org/x/tools/godoc/vfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handlerServer is a migration from an old godoc http Handler type.
|
|
||||||
// This should probably merge into something else.
|
|
||||||
type handlerServer struct {
|
|
||||||
p *Presentation
|
|
||||||
c *Corpus // copy of p.Corpus
|
|
||||||
pattern string // url pattern; e.g. "/pkg/"
|
|
||||||
stripPrefix string // prefix to strip from import path; e.g. "pkg/"
|
|
||||||
fsRoot string // file system root to which the pattern is mapped; e.g. "/src"
|
|
||||||
exclude []string // file system paths to exclude; e.g. "/src/cmd"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *handlerServer) registerWithMux(mux *http.ServeMux) {
|
|
||||||
mux.Handle(s.pattern, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPageInfo returns the PageInfo for a package directory abspath. If the
|
|
||||||
// parameter genAST is set, an AST containing only the package exports is
|
|
||||||
// computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
|
|
||||||
// is extracted from the AST. If there is no corresponding package in the
|
|
||||||
// directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub-
|
|
||||||
// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is
|
|
||||||
// set to the respective error but the error is not logged.
|
|
||||||
//
|
|
||||||
func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo {
|
|
||||||
info := &PageInfo{Dirname: abspath}
|
|
||||||
|
|
||||||
// Restrict to the package files that would be used when building
|
|
||||||
// the package on this system. This makes sure that if there are
|
|
||||||
// separate implementations for, say, Windows vs Unix, we don't
|
|
||||||
// jumble them all together.
|
|
||||||
// Note: If goos/goarch aren't set, the current binary's GOOS/GOARCH
|
|
||||||
// are used.
|
|
||||||
ctxt := build.Default
|
|
||||||
ctxt.IsAbsPath = pathpkg.IsAbs
|
|
||||||
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
|
|
||||||
f, err := h.c.fs.ReadDir(filepath.ToSlash(dir))
|
|
||||||
filtered := make([]os.FileInfo, 0, len(f))
|
|
||||||
for _, i := range f {
|
|
||||||
if mode&NoFiltering != 0 || i.Name() != "internal" {
|
|
||||||
filtered = append(filtered, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filtered, err
|
|
||||||
}
|
|
||||||
ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) {
|
|
||||||
data, err := vfs.ReadFile(h.c.fs, filepath.ToSlash(name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ioutil.NopCloser(bytes.NewReader(data)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if goos != "" {
|
|
||||||
ctxt.GOOS = goos
|
|
||||||
}
|
|
||||||
if goarch != "" {
|
|
||||||
ctxt.GOARCH = goarch
|
|
||||||
}
|
|
||||||
|
|
||||||
pkginfo, err := ctxt.ImportDir(abspath, 0)
|
|
||||||
// continue if there are no Go source files; we still want the directory info
|
|
||||||
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
|
|
||||||
info.Err = err
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect package files
|
|
||||||
pkgname := pkginfo.Name
|
|
||||||
pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
|
|
||||||
if len(pkgfiles) == 0 {
|
|
||||||
// Commands written in C have no .go files in the build.
|
|
||||||
// Instead, documentation may be found in an ignored file.
|
|
||||||
// The file may be ignored via an explicit +build ignore
|
|
||||||
// constraint (recommended), or by defining the package
|
|
||||||
// documentation (historic).
|
|
||||||
pkgname = "main" // assume package main since pkginfo.Name == ""
|
|
||||||
pkgfiles = pkginfo.IgnoredGoFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
// get package information, if any
|
|
||||||
if len(pkgfiles) > 0 {
|
|
||||||
// build package AST
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles)
|
|
||||||
if err != nil {
|
|
||||||
info.Err = err
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore any errors - they are due to unresolved identifiers
|
|
||||||
pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil)
|
|
||||||
|
|
||||||
// extract package documentation
|
|
||||||
info.FSet = fset
|
|
||||||
if mode&ShowSource == 0 {
|
|
||||||
// show extracted documentation
|
|
||||||
var m doc.Mode
|
|
||||||
if mode&NoFiltering != 0 {
|
|
||||||
m |= doc.AllDecls
|
|
||||||
}
|
|
||||||
if mode&AllMethods != 0 {
|
|
||||||
m |= doc.AllMethods
|
|
||||||
}
|
|
||||||
info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath
|
|
||||||
if mode&NoTypeAssoc != 0 {
|
|
||||||
for _, t := range info.PDoc.Types {
|
|
||||||
info.PDoc.Consts = append(info.PDoc.Consts, t.Consts...)
|
|
||||||
info.PDoc.Vars = append(info.PDoc.Vars, t.Vars...)
|
|
||||||
info.PDoc.Funcs = append(info.PDoc.Funcs, t.Funcs...)
|
|
||||||
t.Consts = nil
|
|
||||||
t.Vars = nil
|
|
||||||
t.Funcs = nil
|
|
||||||
}
|
|
||||||
// for now we cannot easily sort consts and vars since
|
|
||||||
// go/doc.Value doesn't export the order information
|
|
||||||
sort.Sort(funcsByName(info.PDoc.Funcs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect examples
|
|
||||||
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
|
|
||||||
files, err = h.c.parseFiles(fset, relpath, abspath, testfiles)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("parsing examples:", err)
|
|
||||||
}
|
|
||||||
info.Examples = collectExamples(h.c, pkg, files)
|
|
||||||
|
|
||||||
// collect any notes that we want to show
|
|
||||||
if info.PDoc.Notes != nil {
|
|
||||||
// could regexp.Compile only once per godoc, but probably not worth it
|
|
||||||
if rx := h.p.NotesRx; rx != nil {
|
|
||||||
for m, n := range info.PDoc.Notes {
|
|
||||||
if rx.MatchString(m) {
|
|
||||||
if info.Notes == nil {
|
|
||||||
info.Notes = make(map[string][]*doc.Note)
|
|
||||||
}
|
|
||||||
info.Notes[m] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// show source code
|
|
||||||
// TODO(gri) Consider eliminating export filtering in this mode,
|
|
||||||
// or perhaps eliminating the mode altogether.
|
|
||||||
if mode&NoFiltering == 0 {
|
|
||||||
packageExports(fset, pkg)
|
|
||||||
}
|
|
||||||
info.PAst = files
|
|
||||||
}
|
|
||||||
info.IsMain = pkgname == "main"
|
|
||||||
}
|
|
||||||
|
|
||||||
// get directory information, if any
|
|
||||||
var dir *Directory
|
|
||||||
var timestamp time.Time
|
|
||||||
if tree, ts := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil {
|
|
||||||
// directory tree is present; lookup respective directory
|
|
||||||
// (may still fail if the file system was updated and the
|
|
||||||
// new directory tree has not yet been computed)
|
|
||||||
dir = tree.(*Directory).lookup(abspath)
|
|
||||||
timestamp = ts
|
|
||||||
}
|
|
||||||
if dir == nil {
|
|
||||||
// no directory tree present (too early after startup or
|
|
||||||
// command-line mode); compute one level for this page
|
|
||||||
// note: cannot use path filter here because in general
|
|
||||||
// it doesn't contain the FSTree path
|
|
||||||
dir = h.c.newDirectory(abspath, 1)
|
|
||||||
timestamp = time.Now()
|
|
||||||
}
|
|
||||||
info.Dirs = dir.listing(true, func(path string) bool { return h.includePath(path, mode) })
|
|
||||||
info.DirTime = timestamp
|
|
||||||
info.DirFlat = mode&FlatDir != 0
|
|
||||||
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handlerServer) includePath(path string, mode PageInfoMode) (r bool) {
|
|
||||||
// if the path is under one of the exclusion paths, don't list.
|
|
||||||
for _, e := range h.exclude {
|
|
||||||
if strings.HasPrefix(path, e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the path includes 'internal', don't list unless we are in the NoFiltering mode.
|
|
||||||
if mode&NoFiltering != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.Contains(path, "internal") || strings.Contains(path, "vendor") {
|
|
||||||
for _, c := range strings.Split(filepath.Clean(path), string(os.PathSeparator)) {
|
|
||||||
if c == "internal" || c == "vendor" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcsByName []*doc.Func
|
|
||||||
|
|
||||||
func (s funcsByName) Len() int { return len(s) }
|
|
||||||
func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s funcsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
|
||||||
|
|
||||||
func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if redirect(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
relpath := pathpkg.Clean(r.URL.Path[len(h.stripPrefix)+1:])
|
|
||||||
abspath := pathpkg.Join(h.fsRoot, relpath)
|
|
||||||
mode := h.p.GetPageInfoMode(r)
|
|
||||||
if relpath == builtinPkgPath {
|
|
||||||
mode = NoFiltering | NoTypeAssoc
|
|
||||||
}
|
|
||||||
info := h.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
|
|
||||||
if info.Err != nil {
|
|
||||||
log.Print(info.Err)
|
|
||||||
h.p.ServeError(w, r, relpath, info.Err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode&NoHTML != 0 {
|
|
||||||
h.p.ServeText(w, applyTemplate(h.p.PackageText, "packageText", info))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var tabtitle, title, subtitle string
|
|
||||||
switch {
|
|
||||||
case info.PAst != nil:
|
|
||||||
for _, ast := range info.PAst {
|
|
||||||
tabtitle = ast.Name.Name
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case info.PDoc != nil:
|
|
||||||
tabtitle = info.PDoc.Name
|
|
||||||
default:
|
|
||||||
tabtitle = info.Dirname
|
|
||||||
title = "Directory "
|
|
||||||
if h.p.ShowTimestamps {
|
|
||||||
subtitle = "Last update: " + info.DirTime.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if title == "" {
|
|
||||||
if info.IsMain {
|
|
||||||
// assume that the directory name is the command name
|
|
||||||
_, tabtitle = pathpkg.Split(relpath)
|
|
||||||
title = "Command "
|
|
||||||
} else {
|
|
||||||
title = "Package "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
title += tabtitle
|
|
||||||
|
|
||||||
// special cases for top-level package/command directories
|
|
||||||
switch tabtitle {
|
|
||||||
case "/src":
|
|
||||||
title = "Packages"
|
|
||||||
tabtitle = "Packages"
|
|
||||||
case "/src/cmd":
|
|
||||||
title = "Commands"
|
|
||||||
tabtitle = "Commands"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit JSON array for type information.
|
|
||||||
pi := h.c.Analysis.PackageInfo(relpath)
|
|
||||||
info.CallGraphIndex = pi.CallGraphIndex
|
|
||||||
info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
|
|
||||||
info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
|
|
||||||
info.TypeInfoIndex = make(map[string]int)
|
|
||||||
for i, ti := range pi.Types {
|
|
||||||
info.TypeInfoIndex[ti.Name] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
info.Share = allowShare(r)
|
|
||||||
h.p.ServePage(w, Page{
|
|
||||||
Title: title,
|
|
||||||
Tabtitle: tabtitle,
|
|
||||||
Subtitle: subtitle,
|
|
||||||
Body: applyTemplate(h.p.PackageHTML, "packageHTML", info),
|
|
||||||
Share: info.Share,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type PageInfoMode uint
|
|
||||||
|
|
||||||
const (
|
|
||||||
NoFiltering PageInfoMode = 1 << iota // do not filter exports
|
|
||||||
AllMethods // show all embedded methods
|
|
||||||
ShowSource // show source code, do not extract documentation
|
|
||||||
NoHTML // show result in textual form, do not generate HTML
|
|
||||||
FlatDir // show directory in a flat (non-indented) manner
|
|
||||||
NoTypeAssoc // don't associate consts, vars, and factory functions with types
|
|
||||||
)
|
|
||||||
|
|
||||||
// modeNames defines names for each PageInfoMode flag.
|
|
||||||
var modeNames = map[string]PageInfoMode{
|
|
||||||
"all": NoFiltering,
|
|
||||||
"methods": AllMethods,
|
|
||||||
"src": ShowSource,
|
|
||||||
"text": NoHTML,
|
|
||||||
"flat": FlatDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPageInfoMode computes the PageInfoMode flags by analyzing the request
|
|
||||||
// URL form value "m". It is value is a comma-separated list of mode names
|
|
||||||
// as defined by modeNames (e.g.: m=src,text).
|
|
||||||
func (p *Presentation) GetPageInfoMode(r *http.Request) PageInfoMode {
|
|
||||||
var mode PageInfoMode
|
|
||||||
for _, k := range strings.Split(r.FormValue("m"), ",") {
|
|
||||||
if m, found := modeNames[strings.TrimSpace(k)]; found {
|
|
||||||
mode |= m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.AdjustPageInfoMode != nil {
|
|
||||||
mode = p.AdjustPageInfoMode(r, mode)
|
|
||||||
}
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// poorMansImporter returns a (dummy) package object named
|
|
||||||
// by the last path component of the provided package path
|
|
||||||
// (as is the convention for packages). This is sufficient
|
|
||||||
// to resolve package identifiers without doing an actual
|
|
||||||
// import. It never returns an error.
|
|
||||||
//
|
|
||||||
func poorMansImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
|
||||||
pkg := imports[path]
|
|
||||||
if pkg == nil {
|
|
||||||
// note that strings.LastIndex returns -1 if there is no "/"
|
|
||||||
pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
|
|
||||||
pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
|
|
||||||
imports[path] = pkg
|
|
||||||
}
|
|
||||||
return pkg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// globalNames returns a set of the names declared by all package-level
|
|
||||||
// declarations. Method names are returned in the form Receiver_Method.
|
|
||||||
func globalNames(pkg *ast.Package) map[string]bool {
|
|
||||||
names := make(map[string]bool)
|
|
||||||
for _, file := range pkg.Files {
|
|
||||||
for _, decl := range file.Decls {
|
|
||||||
addNames(names, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectExamples collects examples for pkg from testfiles.
|
|
||||||
func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
|
|
||||||
var files []*ast.File
|
|
||||||
for _, f := range testfiles {
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
var examples []*doc.Example
|
|
||||||
globals := globalNames(pkg)
|
|
||||||
for _, e := range doc.Examples(files...) {
|
|
||||||
name := stripExampleSuffix(e.Name)
|
|
||||||
if name == "" || globals[name] {
|
|
||||||
examples = append(examples, e)
|
|
||||||
} else if c.Verbose {
|
|
||||||
log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return examples
|
|
||||||
}
|
|
||||||
|
|
||||||
// addNames adds the names declared by decl to the names set.
|
|
||||||
// Method names are added in the form ReceiverTypeName_Method.
|
|
||||||
func addNames(names map[string]bool, decl ast.Decl) {
|
|
||||||
switch d := decl.(type) {
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
name := d.Name.Name
|
|
||||||
if d.Recv != nil {
|
|
||||||
var typeName string
|
|
||||||
switch r := d.Recv.List[0].Type.(type) {
|
|
||||||
case *ast.StarExpr:
|
|
||||||
typeName = r.X.(*ast.Ident).Name
|
|
||||||
case *ast.Ident:
|
|
||||||
typeName = r.Name
|
|
||||||
}
|
|
||||||
name = typeName + "_" + name
|
|
||||||
}
|
|
||||||
names[name] = true
|
|
||||||
case *ast.GenDecl:
|
|
||||||
for _, spec := range d.Specs {
|
|
||||||
switch s := spec.(type) {
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
names[s.Name.Name] = true
|
|
||||||
case *ast.AliasSpec:
|
|
||||||
names[s.Name.Name] = true
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
for _, id := range s.Names {
|
|
||||||
names[id.Name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packageExports is a local implementation of ast.PackageExports
|
|
||||||
// which correctly updates each package file's comment list.
|
|
||||||
// (The ast.PackageExports signature is frozen, hence the local
|
|
||||||
// implementation).
|
|
||||||
//
|
|
||||||
func packageExports(fset *token.FileSet, pkg *ast.Package) {
|
|
||||||
for _, src := range pkg.Files {
|
|
||||||
cmap := ast.NewCommentMap(fset, src, src.Comments)
|
|
||||||
ast.FileExports(src)
|
|
||||||
src.Comments = cmap.Filter(src).Comments()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := t.Execute(&buf, data); err != nil {
|
|
||||||
log.Printf("%s.Execute: %s", name, err)
|
|
||||||
}
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type writerCapturesErr struct {
|
|
||||||
w io.Writer
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *writerCapturesErr) Write(p []byte) (int, error) {
|
|
||||||
n, err := w.w.Write(p)
|
|
||||||
if err != nil {
|
|
||||||
w.err = err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyTemplateToResponseWriter uses an http.ResponseWriter as the io.Writer
|
|
||||||
// for the call to template.Execute. It uses an io.Writer wrapper to capture
|
|
||||||
// errors from the underlying http.ResponseWriter. Errors are logged only when
|
|
||||||
// they come from the template processing and not the Writer; this avoid
|
|
||||||
// polluting log files with error messages due to networking issues, such as
|
|
||||||
// client disconnects and http HEAD protocol violations.
|
|
||||||
func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template, data interface{}) {
|
|
||||||
w := &writerCapturesErr{w: rw}
|
|
||||||
err := t.Execute(w, data)
|
|
||||||
// There are some cases where template.Execute does not return an error when
|
|
||||||
// rw returns an error, and some where it does. So check w.err first.
|
|
||||||
if w.err == nil && err != nil {
|
|
||||||
// Log template errors.
|
|
||||||
log.Printf("%s.Execute: %s", t.Name(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
|
|
||||||
canonical := pathpkg.Clean(r.URL.Path)
|
|
||||||
if !strings.HasSuffix(canonical, "/") {
|
|
||||||
canonical += "/"
|
|
||||||
}
|
|
||||||
if r.URL.Path != canonical {
|
|
||||||
url := *r.URL
|
|
||||||
url.Path = canonical
|
|
||||||
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
|
|
||||||
redirected = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
|
|
||||||
c := pathpkg.Clean(r.URL.Path)
|
|
||||||
c = strings.TrimRight(c, "/")
|
|
||||||
if r.URL.Path != c {
|
|
||||||
url := *r.URL
|
|
||||||
url.Path = c
|
|
||||||
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
|
|
||||||
redirected = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
|
|
||||||
src, err := vfs.ReadFile(p.Corpus.fs, abspath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ReadFile: %s", err)
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.FormValue("m") == "text" {
|
|
||||||
p.ServeText(w, src)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h := r.FormValue("h")
|
|
||||||
s := RangeSelection(r.FormValue("s"))
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if pathpkg.Ext(abspath) == ".go" {
|
|
||||||
// Find markup links for this file (e.g. "/src/fmt/print.go").
|
|
||||||
fi := p.Corpus.Analysis.FileInfo(abspath)
|
|
||||||
buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
|
|
||||||
buf.Write(marshalJSON(fi.Data))
|
|
||||||
buf.WriteString(";</script>\n")
|
|
||||||
|
|
||||||
if status := p.Corpus.Analysis.Status(); status != "" {
|
|
||||||
buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
|
|
||||||
// TODO(adonovan): show analysis status at per-file granularity.
|
|
||||||
fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("<pre>")
|
|
||||||
formatGoSource(&buf, src, fi.Links, h, s)
|
|
||||||
buf.WriteString("</pre>")
|
|
||||||
} else {
|
|
||||||
buf.WriteString("<pre>")
|
|
||||||
FormatText(&buf, src, 1, false, h, s)
|
|
||||||
buf.WriteString("</pre>")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
|
|
||||||
|
|
||||||
p.ServePage(w, Page{
|
|
||||||
Title: title + " " + relpath,
|
|
||||||
Tabtitle: relpath,
|
|
||||||
Body: buf.Bytes(),
|
|
||||||
Share: allowShare(r),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatGoSource HTML-escapes Go source text and writes it to w,
|
|
||||||
// decorating it with the specified analysis links.
|
|
||||||
//
|
|
||||||
func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, pattern string, selection Selection) {
|
|
||||||
// Emit to a temp buffer so that we can add line anchors at the end.
|
|
||||||
saved, buf := buf, new(bytes.Buffer)
|
|
||||||
|
|
||||||
var i int
|
|
||||||
var link analysis.Link // shared state of the two funcs below
|
|
||||||
segmentIter := func() (seg Segment) {
|
|
||||||
if i < len(links) {
|
|
||||||
link = links[i]
|
|
||||||
i++
|
|
||||||
seg = Segment{link.Start(), link.End()}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
linkWriter := func(w io.Writer, offs int, start bool) {
|
|
||||||
link.Write(w, offs, start)
|
|
||||||
}
|
|
||||||
|
|
||||||
comments := tokenSelection(text, token.COMMENT)
|
|
||||||
var highlights Selection
|
|
||||||
if pattern != "" {
|
|
||||||
highlights = regexpSelection(text, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatSelections(buf, text, linkWriter, segmentIter, selectionTag, comments, highlights, selection)
|
|
||||||
|
|
||||||
// Now copy buf to saved, adding line anchors.
|
|
||||||
|
|
||||||
// The lineSelection mechanism can't be composed with our
|
|
||||||
// linkWriter, so we have to add line spans as another pass.
|
|
||||||
n := 1
|
|
||||||
for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) {
|
|
||||||
fmt.Fprintf(saved, "<span id=\"L%d\" class=\"ln\">%6d</span>\t", n, n)
|
|
||||||
n++
|
|
||||||
saved.Write(line)
|
|
||||||
saved.WriteByte('\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
|
||||||
if redirect(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := p.Corpus.fs.ReadDir(abspath)
|
|
||||||
if err != nil {
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ServePage(w, Page{
|
|
||||||
Title: "Directory " + relpath,
|
|
||||||
Tabtitle: relpath,
|
|
||||||
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
|
|
||||||
Share: allowShare(r),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
|
||||||
// get HTML body contents
|
|
||||||
src, err := vfs.ReadFile(p.Corpus.fs, abspath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ReadFile: %s", err)
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it begins with "<!DOCTYPE " assume it is standalone
|
|
||||||
// html that doesn't need the template wrapping.
|
|
||||||
if bytes.HasPrefix(src, doctype) {
|
|
||||||
w.Write(src)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it begins with a JSON blob, read in the metadata.
|
|
||||||
meta, src, err := extractMetadata(src)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("decoding metadata %s: %v", relpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
page := Page{
|
|
||||||
Title: meta.Title,
|
|
||||||
Subtitle: meta.Subtitle,
|
|
||||||
Share: allowShare(r),
|
|
||||||
}
|
|
||||||
|
|
||||||
// evaluate as template if indicated
|
|
||||||
if meta.Template {
|
|
||||||
tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("parsing template %s: %v", relpath, err)
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := tmpl.Execute(&buf, page); err != nil {
|
|
||||||
log.Printf("executing template %s: %v", relpath, err)
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
src = buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's the language spec, add tags to EBNF productions
|
|
||||||
if strings.HasSuffix(abspath, "go_spec.html") {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
Linkify(&buf, src)
|
|
||||||
src = buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
page.Body = src
|
|
||||||
p.ServePage(w, page)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
p.serveFile(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
relpath := r.URL.Path
|
|
||||||
|
|
||||||
// Check to see if we need to redirect or serve another file.
|
|
||||||
if m := p.Corpus.MetadataFor(relpath); m != nil {
|
|
||||||
if m.Path != relpath {
|
|
||||||
// Redirect to canonical path.
|
|
||||||
http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Serve from the actual filesystem path.
|
|
||||||
relpath = m.filePath
|
|
||||||
}
|
|
||||||
|
|
||||||
abspath := relpath
|
|
||||||
relpath = relpath[1:] // strip leading slash
|
|
||||||
|
|
||||||
switch pathpkg.Ext(relpath) {
|
|
||||||
case ".html":
|
|
||||||
if strings.HasSuffix(relpath, "/index.html") {
|
|
||||||
// We'll show index.html for the directory.
|
|
||||||
// Use the dir/ version as canonical instead of dir/index.html.
|
|
||||||
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.ServeHTMLDoc(w, r, abspath, relpath)
|
|
||||||
return
|
|
||||||
|
|
||||||
case ".go":
|
|
||||||
p.serveTextFile(w, r, abspath, relpath, "Source file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := p.Corpus.fs.Lstat(abspath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
p.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir != nil && dir.IsDir() {
|
|
||||||
if redirect(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(p.Corpus.fs, index) {
|
|
||||||
p.ServeHTMLDoc(w, r, index, index)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.serveDirectory(w, r, abspath, relpath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if util.IsTextFile(p.Corpus.fs, abspath) {
|
|
||||||
if redirectFile(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.serveTextFile(w, r, abspath, relpath, "Text file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.fileServer.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) {
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
w.Write(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalJSON(x interface{}) []byte {
|
|
||||||
var data []byte
|
|
||||||
var err error
|
|
||||||
const indentJSON = false // for easier debugging
|
|
||||||
if indentJSON {
|
|
||||||
data, err = json.MarshalIndent(x, "", " ")
|
|
||||||
} else {
|
|
||||||
data, err = json.Marshal(x)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("json.Marshal failed: %s", err))
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
// This file contains the infrastructure to create a code
|
// This file contains the infrastructure to create a code
|
||||||
// snippet for search results.
|
// snippet for search results.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
// Copyright 2009 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.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
// This file contains the infrastructure to create a code
|
|
||||||
// snippet for search results.
|
|
||||||
//
|
|
||||||
// Note: At the moment, this only creates HTML snippets.
|
|
||||||
|
|
||||||
package godoc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Snippet struct {
|
|
||||||
Line int
|
|
||||||
Text string // HTML-escaped
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
|
||||||
// TODO instead of pretty-printing the node, should use the original source instead
|
|
||||||
var buf1 bytes.Buffer
|
|
||||||
p.writeNode(&buf1, fset, decl)
|
|
||||||
// wrap text with <pre> tag
|
|
||||||
var buf2 bytes.Buffer
|
|
||||||
buf2.WriteString("<pre>")
|
|
||||||
FormatText(&buf2, buf1.Bytes(), -1, true, id.Name, nil)
|
|
||||||
buf2.WriteString("</pre>")
|
|
||||||
return &Snippet{fset.Position(id.Pos()).Line, buf2.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSpec(list []ast.Spec, id *ast.Ident) ast.Spec {
|
|
||||||
for _, spec := range list {
|
|
||||||
switch s := spec.(type) {
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
if s.Name == id {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
case *ast.AliasSpec:
|
|
||||||
if s.Name == id {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
for _, n := range s.Names {
|
|
||||||
if n == id {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
if s.Name == id {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
|
|
||||||
s := findSpec(d.Specs, id)
|
|
||||||
if s == nil {
|
|
||||||
return nil // declaration doesn't contain id - exit gracefully
|
|
||||||
}
|
|
||||||
|
|
||||||
// only use the spec containing the id for the snippet
|
|
||||||
dd := &ast.GenDecl{
|
|
||||||
Doc: d.Doc,
|
|
||||||
TokPos: d.Pos(),
|
|
||||||
Tok: d.Tok,
|
|
||||||
Lparen: d.Lparen,
|
|
||||||
Specs: []ast.Spec{s},
|
|
||||||
Rparen: d.Rparen,
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.newSnippet(fset, dd, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presentation) funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
|
|
||||||
if d.Name != id {
|
|
||||||
return nil // declaration doesn't contain id - exit gracefully
|
|
||||||
}
|
|
||||||
|
|
||||||
// only use the function signature for the snippet
|
|
||||||
dd := &ast.FuncDecl{
|
|
||||||
Doc: d.Doc,
|
|
||||||
Recv: d.Recv,
|
|
||||||
Name: d.Name,
|
|
||||||
Type: d.Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.newSnippet(fset, dd, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSnippet creates a text snippet from a declaration decl containing an
|
|
||||||
// identifier id. Parts of the declaration not containing the identifier
|
|
||||||
// may be removed for a more compact snippet.
|
|
||||||
func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
|
||||||
// TODO(bradfitz, adg): remove this function. But it's used by indexer, which
|
|
||||||
// doesn't have a *Presentation, and NewSnippet needs a TabWidth.
|
|
||||||
var p Presentation
|
|
||||||
p.TabWidth = 4
|
|
||||||
return p.NewSnippet(fset, decl, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSnippet creates a text snippet from a declaration decl containing an
|
|
||||||
// identifier id. Parts of the declaration not containing the identifier
|
|
||||||
// may be removed for a more compact snippet.
|
|
||||||
func (p *Presentation) NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
|
||||||
var s *Snippet
|
|
||||||
switch d := decl.(type) {
|
|
||||||
case *ast.GenDecl:
|
|
||||||
s = p.genSnippet(fset, d, id)
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
s = p.funcSnippet(fset, d, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle failure gracefully
|
|
||||||
if s == nil {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name)
|
|
||||||
s = &Snippet{fset.Position(id.Pos()).Line, buf.String()}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue