godoc: actually include files from previous CL
This stuff was deleted from cmd/godoc, and is
moving into pkg godoc.
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/11425043
diff --git a/godoc/godoc.go b/godoc/godoc.go
new file mode 100644
index 0000000..b1438ef
--- /dev/null
+++ b/godoc/godoc.go
@@ -0,0 +1,482 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/doc"
+ "go/format"
+ "go/printer"
+ "go/token"
+ "io"
+ "log"
+ "os"
+ pathpkg "path"
+ "regexp"
+ "strings"
+ "text/template"
+ "time"
+ "unicode"
+ "unicode/utf8"
+
+ "code.google.com/p/go.tools/godoc/util"
+ "code.google.com/p/go.tools/godoc/vfs"
+)
+
+// FS is the file system that godoc reads from and serves.
+// It is a virtual file system that operates on slash-separated paths,
+// and its root corresponds to the Go distribution root: /src/pkg
+// holds the source tree, and so on. This means that the URLs served by
+// the godoc server are the same as the paths in the virtual file
+// system, which helps keep things simple.
+var FS = vfs.NameSpace{}
+
+// Old flags
+var (
+ // DeclLinks controls whether identifers are linked to their declaration.
+ DeclLinks = true
+
+ // ShowExamples controls whether to show examples in command-line mode.
+ // TODO(bradfitz,adg): delete this flag
+ ShowExamples = false
+
+ // ShowPlayground controls whether to enable the playground in
+ // the web interface.
+ // TODO(bradfitz,adg): delete this flag
+ ShowPlayground = false
+
+ IndexEnabled = false
+
+ ShowTimestamps = false
+
+ Verbose = false
+
+ TabWidth = 4
+
+ // regular expression matching note markers to show
+ NotesRx = "BUG"
+)
+
+// SearchIndex is the search index in use.
+var SearchIndex util.RWValue
+
+// 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.
+var 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": infoLineFunc,
+ "infoSnippet_html": infoSnippet_htmlFunc,
+
+ // formatting of AST nodes
+ "node": nodeFunc,
+ "node_html": node_htmlFunc,
+ "comment_html": comment_htmlFunc,
+ "comment_text": comment_textFunc,
+
+ // support for URL attributes
+ "pkgLink": pkgLinkFunc,
+ "srcLink": srcLinkFunc,
+ "posLink_url": posLink_urlFunc,
+
+ // formatting of Examples
+ "example_html": example_htmlFunc,
+ "example_text": example_textFunc,
+ "example_name": example_nameFunc,
+ "example_suffix": example_suffixFunc,
+
+ // formatting of Notes
+ "noteTitle": noteTitle,
+}
+
+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 infoLineFunc(info SpotInfo) int {
+ line := info.Lori()
+ if info.IsIndex() {
+ index, _ := 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 infoSnippet_htmlFunc(info SpotInfo) string {
+ if info.IsIndex() {
+ index, _ := SearchIndex.Get()
+ // Snippet.Text was HTML-escaped when it was generated
+ return index.(*Index).Snippet(info.Lori()).Text
+ }
+ return `<span class="alert">no snippet text available</span>`
+}
+
+func nodeFunc(info *PageInfo, node interface{}) string {
+ var buf bytes.Buffer
+ writeNode(&buf, info.FSet, node)
+ return buf.String()
+}
+
+func node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
+ var buf1 bytes.Buffer
+ writeNode(&buf1, info.FSet, node)
+
+ var buf2 bytes.Buffer
+ 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()
+}
+
+func comment_htmlFunc(comment string) string {
+ var buf bytes.Buffer
+ // TODO(gri) Provide list of words (e.g. function parameters)
+ // to be emphasized by ToHTML.
+ doc.ToHTML(&buf, comment, nil) // does html-escaping
+ return buf.String()
+}
+
+// punchCardWidth is the number of columns of fixed-width
+// characters to assume when wrapping text. Very few people
+// use terminals or cards smaller than 80 characters, so 80 it is.
+// We do not try to sniff the environment or the tty to adapt to
+// the situation; instead, by using a constant we make sure that
+// godoc always produces the same output regardless of context,
+// a consistency that is lost otherwise. For example, if we sniffed
+// the environment or tty, then http://golang.org/pkg/math/?m=text
+// would depend on the width of the terminal where godoc started,
+// which is clearly bogus. More generally, the Unix tools that behave
+// differently when writing to a tty than when writing to a file have
+// a history of causing confusion (compare `ls` and `ls | cat`), and we
+// want to avoid that mistake here.
+const punchCardWidth = 80
+
+func comment_textFunc(comment, indent, preIndent string) string {
+ var buf bytes.Buffer
+ doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent))
+ return buf.String()
+}
+
+type PageInfo struct {
+ Dirname string // directory containing the package
+ 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][]*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
+ DirTime time.Time // directory time stamp
+ DirFlat bool // if set, show directory in a flat (non-indented) manner
+}
+
+func (info *PageInfo) IsEmpty() bool {
+ return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
+}
+
+func pkgLinkFunc(path string) string {
+ relpath := path[1:]
+ // because of the irregular mapping under goroot
+ // we need to correct certain relative paths
+ relpath = strings.TrimPrefix(relpath, "src/pkg/")
+ return PkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL
+}
+
+// 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 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
+ }
+
+ var buf bytes.Buffer
+ template.HTMLEscape(&buf, []byte(relpath))
+ // 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 {
+ return pathpkg.Clean("/" + s)
+}
+
+func example_textFunc(info *PageInfo, funcName, indent string) string {
+ if !ShowExamples {
+ return ""
+ }
+
+ var buf bytes.Buffer
+ first := true
+ for _, eg := range info.Examples {
+ name := stripExampleSuffix(eg.Name)
+ if name != funcName {
+ continue
+ }
+
+ if !first {
+ buf.WriteString("\n")
+ }
+ first = false
+
+ // print code
+ cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+ var buf1 bytes.Buffer
+ 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] == '}' {
+ // remove surrounding braces
+ code = code[1 : n-1]
+ // unindent
+ code = strings.Replace(code, "\n ", "\n", -1)
+ }
+ code = strings.Trim(code, "\n")
+ code = strings.Replace(code, "\n", "\n\t", -1)
+
+ buf.WriteString(indent)
+ buf.WriteString("Example:\n\t")
+ buf.WriteString(code)
+ buf.WriteString("\n")
+ }
+ return buf.String()
+}
+
+func example_htmlFunc(info *PageInfo, funcName string) string {
+ var buf bytes.Buffer
+ for _, eg := range info.Examples {
+ name := stripExampleSuffix(eg.Name)
+
+ if name != funcName {
+ continue
+ }
+
+ // print code
+ cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+ code := node_htmlFunc(info, cnode, true)
+ out := eg.Output
+ wholeFile := true
+
+ // Additional formatting if this is a function body.
+ if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
+ wholeFile = false
+ // remove surrounding braces
+ code = code[1 : n-1]
+ // unindent
+ code = strings.Replace(code, "\n ", "\n", -1)
+ // remove output comment
+ if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
+ code = strings.TrimSpace(code[:loc[0]])
+ }
+ }
+
+ // Write out the playground code in standard Go style
+ // (use tabs, no comment highlight, etc).
+ play := ""
+ if eg.Play != nil && ShowPlayground {
+ var buf bytes.Buffer
+ if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
+ log.Print(err)
+ } else {
+ play = buf.String()
+ }
+ }
+
+ // Drop output, as the output comment will appear in the code.
+ if wholeFile && play == "" {
+ out = ""
+ }
+
+ if ExampleHTML == nil {
+ out = ""
+ return ""
+ }
+
+ err := ExampleHTML.Execute(&buf, struct {
+ Name, Doc, Code, Play, Output string
+ }{eg.Name, eg.Doc, code, play, out})
+ if err != nil {
+ log.Print(err)
+ }
+ }
+ return buf.String()
+}
+
+// example_nameFunc takes an example function name and returns its display
+// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
+func example_nameFunc(s string) string {
+ name, suffix := splitExampleName(s)
+ // replace _ with . for method names
+ name = strings.Replace(name, "_", ".", 1)
+ // use "Package" if no name provided
+ if name == "" {
+ name = "Package"
+ }
+ return name + suffix
+}
+
+// example_suffixFunc takes an example function name and returns its suffix in
+// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
+func example_suffixFunc(name string) string {
+ _, suffix := splitExampleName(name)
+ return suffix
+}
+
+func noteTitle(note string) string {
+ return strings.Title(strings.ToLower(note))
+}
+
+func startsWithUppercase(s string) bool {
+ r, _ := utf8.DecodeRuneInString(s)
+ return unicode.IsUpper(r)
+}
+
+var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
+
+// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
+// while keeping uppercase Braz in Foo_Braz.
+func stripExampleSuffix(name string) string {
+ if i := strings.LastIndex(name, "_"); i != -1 {
+ if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
+ name = name[:i]
+ }
+ }
+ return name
+}
+
+func splitExampleName(s string) (name, suffix string) {
+ i := strings.LastIndex(s, "_")
+ if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
+ name = s[:i]
+ suffix = " (" + strings.Title(s[i+1:]) + ")"
+ return
+ }
+ name = s
+ return
+}
+
+// Write an AST node to w.
+func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
+ // convert trailing tabs into spaces using a tconv filter
+ // to ensure a good outcome in most browsers (there may still
+ // be tabs in comments and strings, but converting those into
+ // the right number of spaces is much harder)
+ //
+ // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
+ // with an another printer mode (which is more efficiently
+ // implemented in the printer than here with another layer)
+ mode := printer.TabIndent | printer.UseSpaces
+ err := (&printer.Config{Mode: mode, Tabwidth: TabWidth}).Fprint(&tconv{output: w}, fset, x)
+ if err != nil {
+ log.Print(err)
+ }
+}
+
+var WriteNode = writeNode