diff options
Diffstat (limited to 'src/cmd/godoc/godoc.go')
-rw-r--r-- | src/cmd/godoc/godoc.go | 131 |
1 files changed, 87 insertions, 44 deletions
diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index 872b0dc1e..26b0b97e1 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -66,6 +66,7 @@ var ( templateDir = flag.String("templates", "", "directory containing alternate template files") showPlayground = flag.Bool("play", false, "enable playground in web interface") showExamples = flag.Bool("ex", false, "show examples in command line mode") + declLinks = flag.Bool("links", true, "link identifiers to their declarations") // search index indexEnabled = flag.Bool("index", false, "enable search index") @@ -84,15 +85,11 @@ var ( cmdHandler docServer pkgHandler docServer - // which code 'Notes' to show - notes = flag.String("notes", "BUG", "comma separated list of Note markers as per pkg:go/doc") - // list of 'Notes' to show - notesToShow []string + // source code notes + notes = flag.String("notes", "BUG", "regular expression matching note markers to show") ) func initHandlers() { - notesToShow = strings.Split(*notes, ",") - fileServer = http.FileServer(&httpFS{fs}) cmdHandler = docServer{"/cmd/", "/src/cmd"} pkgHandler = docServer{"/pkg/", "/src/pkg"} @@ -276,17 +273,23 @@ func infoSnippet_htmlFunc(info SpotInfo) string { return `<span class="alert">no snippet text available</span>` } -func nodeFunc(node interface{}, fset *token.FileSet) string { +func nodeFunc(info *PageInfo, node interface{}) string { var buf bytes.Buffer - writeNode(&buf, fset, node) + writeNode(&buf, info.FSet, node) return buf.String() } -func node_htmlFunc(node interface{}, fset *token.FileSet) string { +func node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string { var buf1 bytes.Buffer - writeNode(&buf1, fset, node) + writeNode(&buf1, info.FSet, node) + var buf2 bytes.Buffer - FormatText(&buf2, buf1.Bytes(), -1, true, "", nil) + if n, _ := node.(ast.Node); n != nil && linkify && *declLinks { + LinkifyText(&buf2, buf1.Bytes(), n) + } else { + FormatText(&buf2, buf1.Bytes(), -1, true, "", nil) + } + return buf2.String() } @@ -337,14 +340,14 @@ func stripExampleSuffix(name string) string { return name } -func example_textFunc(funcName string, examples []*doc.Example, fset *token.FileSet, indent string) string { +func example_textFunc(info *PageInfo, funcName, indent string) string { if !*showExamples { return "" } var buf bytes.Buffer first := true - for _, eg := range examples { + for _, eg := range info.Examples { name := stripExampleSuffix(eg.Name) if name != funcName { continue @@ -358,7 +361,7 @@ func example_textFunc(funcName string, examples []*doc.Example, fset *token.File // print code cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} var buf1 bytes.Buffer - writeNode(&buf1, fset, cnode) + writeNode(&buf1, info.FSet, cnode) code := buf1.String() // Additional formatting if this is a function body. if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' { @@ -378,9 +381,9 @@ func example_textFunc(funcName string, examples []*doc.Example, fset *token.File return buf.String() } -func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { +func example_htmlFunc(info *PageInfo, funcName string) string { var buf bytes.Buffer - for _, eg := range examples { + for _, eg := range info.Examples { name := stripExampleSuffix(eg.Name) if name != funcName { @@ -389,7 +392,7 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File // print code cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} - code := node_htmlFunc(cnode, fset) + code := node_htmlFunc(info, cnode, true) out := eg.Output wholeFile := true @@ -411,7 +414,7 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File play := "" if eg.Play != nil && *showPlayground { var buf bytes.Buffer - if err := format.Node(&buf, fset, eg.Play); err != nil { + if err := format.Node(&buf, info.FSet, eg.Play); err != nil { log.Print(err) } else { play = buf.String() @@ -476,19 +479,33 @@ func pkgLinkFunc(path string) string { return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL } -func posLink_urlFunc(node ast.Node, fset *token.FileSet) string { +// n must be an ast.Node or a *doc.Note +func posLink_urlFunc(info *PageInfo, n interface{}) string { + 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 + var low, high int // selection offset range - if p := node.Pos(); p.IsValid() { - pos := fset.Position(p) - relpath = pos.Filename - line = pos.Line - low = pos.Offset + if pos.IsValid() { + p := info.FSet.Position(pos) + relpath = p.Filename + line = p.Line + low = p.Offset } - if p := node.End(); p.IsValid() { - high = fset.Position(p).Offset + if end.IsValid() { + high = info.FSet.Position(end).Offset } var buf bytes.Buffer @@ -525,7 +542,7 @@ var fmap = template.FuncMap{ "filename": filenameFunc, "repeat": strings.Repeat, - // accss to FileInfos (directory listings) + // access to FileInfos (directory listings) "fileInfoName": fileInfoNameFunc, "fileInfoTime": fileInfoTimeFunc, @@ -911,12 +928,12 @@ type PageInfo struct { Err error // error or nil // package info - FSet *token.FileSet // nil if no package documentation - PDoc *doc.Package // nil if no package documentation - Examples []*doc.Example // nil if no example code - Notes map[string][]string // nil if no package Notes - PAst *ast.File // nil if no AST with package exports - IsMain bool // true for package main + FSet *token.FileSet // nil if no package documentation + PDoc *doc.Package // nil if no package documentation + Examples []*doc.Example // nil if no example code + Notes map[string][]*doc.Note // nil if no package Notes + PAst *ast.File // nil if no AST with package exports + IsMain bool // true for package main // directory info Dirs *DirList // nil if no directory information @@ -1024,6 +1041,23 @@ func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Ex return examples } +// 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 +} + // 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) @@ -1032,8 +1066,8 @@ func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Ex // 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 *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (info PageInfo) { - info.Dirname = abspath +func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) *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 @@ -1050,7 +1084,7 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (inf // 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 + return info } // collect package files @@ -1073,9 +1107,11 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (inf files, err := parseFiles(fset, abspath, pkgfiles) if err != nil { info.Err = err - return + return info } - pkg := &ast.Package{Name: pkgname, Files: files} + + // ignore any errors - they are due to unresolved identifiers + pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil) // extract package documentation info.FSet = fset @@ -1100,10 +1136,15 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (inf // collect any notes that we want to show if info.PDoc.Notes != nil { - info.Notes = make(map[string][]string) - for _, m := range notesToShow { - if n := info.PDoc.Notes[m]; n != nil { - info.Notes[m] = n + // could regexp.Compile only once per godoc, but probably not worth it + if rx, err := regexp.Compile(*notes); err == 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 + } } } } @@ -1142,7 +1183,7 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (inf info.DirTime = timestamp info.DirFlat = mode&flatDir != 0 - return + return info } func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -1474,6 +1515,8 @@ func readIndex(filenames string) error { matches, err := filepath.Glob(filenames) if err != nil { return err + } else if matches == nil { + return fmt.Errorf("no index files match %q", filenames) } sort.Strings(matches) // make sure files are in the right order files := make([]io.Reader, 0, len(matches)) |