github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/godoc/godoc.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"flag"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/build"
    14  	"go/doc"
    15  	"go/format"
    16  	"go/printer"
    17  	"go/token"
    18  	htmlpkg "html"
    19  	"io"
    20  	"io/ioutil"
    21  	"log"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	pathpkg "path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"runtime"
    29  	"sort"
    30  	"strings"
    31  	"text/template"
    32  	"time"
    33  	"unicode"
    34  	"unicode/utf8"
    35  )
    36  
    37  // ----------------------------------------------------------------------------
    38  // Globals
    39  
    40  type delayTime struct {
    41  	RWValue
    42  }
    43  
    44  func (dt *delayTime) backoff(max time.Duration) {
    45  	dt.mutex.Lock()
    46  	v := dt.value.(time.Duration) * 2
    47  	if v > max {
    48  		v = max
    49  	}
    50  	dt.value = v
    51  	// don't change dt.timestamp - calling backoff indicates an error condition
    52  	dt.mutex.Unlock()
    53  }
    54  
    55  var (
    56  	verbose = flag.Bool("v", false, "verbose mode")
    57  
    58  	// file system roots
    59  	// TODO(gri) consider the invariant that goroot always end in '/'
    60  	goroot  = flag.String("goroot", runtime.GOROOT(), "Go root directory")
    61  	testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
    62  
    63  	// layout control
    64  	tabwidth       = flag.Int("tabwidth", 4, "tab width")
    65  	showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
    66  	templateDir    = flag.String("templates", "", "directory containing alternate template files")
    67  	showPlayground = flag.Bool("play", false, "enable playground in web interface")
    68  	showExamples   = flag.Bool("ex", false, "show examples in command line mode")
    69  	declLinks      = flag.Bool("links", true, "link identifiers to their declarations")
    70  
    71  	// search index
    72  	indexEnabled = flag.Bool("index", false, "enable search index")
    73  	indexFiles   = flag.String("index_files", "", "glob pattern specifying index files;"+
    74  		"if not empty, the index is read from these files in sorted order")
    75  	maxResults    = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
    76  	indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
    77  
    78  	// file system information
    79  	fsTree      RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
    80  	fsModified  RWValue // timestamp of last call to invalidateIndex
    81  	docMetadata RWValue // mapping from paths to *Metadata
    82  
    83  	// http handlers
    84  	fileServer http.Handler // default file server
    85  	cmdHandler docServer
    86  	pkgHandler docServer
    87  
    88  	// source code notes
    89  	notes = flag.String("notes", "BUG", "regular expression matching note markers to show")
    90  )
    91  
    92  func initHandlers() {
    93  	fileServer = http.FileServer(&httpFS{fs})
    94  	cmdHandler = docServer{"/cmd/", "/src/cmd"}
    95  	pkgHandler = docServer{"/pkg/", "/src/pkg"}
    96  }
    97  
    98  func registerPublicHandlers(mux *http.ServeMux) {
    99  	mux.Handle(cmdHandler.pattern, &cmdHandler)
   100  	mux.Handle(pkgHandler.pattern, &pkgHandler)
   101  	mux.HandleFunc("/doc/codewalk/", codewalk)
   102  	mux.Handle("/doc/play/", fileServer)
   103  	mux.HandleFunc("/search", search)
   104  	mux.Handle("/robots.txt", fileServer)
   105  	mux.HandleFunc("/opensearch.xml", serveSearchDesc)
   106  	mux.HandleFunc("/", serveFile)
   107  }
   108  
   109  func initFSTree() {
   110  	dir := newDirectory(pathpkg.Join("/", *testDir), -1)
   111  	if dir == nil {
   112  		log.Println("Warning: FSTree is nil")
   113  		return
   114  	}
   115  	fsTree.set(dir)
   116  	invalidateIndex()
   117  }
   118  
   119  // ----------------------------------------------------------------------------
   120  // Tab conversion
   121  
   122  var spaces = []byte("                                ") // 32 spaces seems like a good number
   123  
   124  const (
   125  	indenting = iota
   126  	collecting
   127  )
   128  
   129  // A tconv is an io.Writer filter for converting leading tabs into spaces.
   130  type tconv struct {
   131  	output io.Writer
   132  	state  int // indenting or collecting
   133  	indent int // valid if state == indenting
   134  }
   135  
   136  func (p *tconv) writeIndent() (err error) {
   137  	i := p.indent
   138  	for i >= len(spaces) {
   139  		i -= len(spaces)
   140  		if _, err = p.output.Write(spaces); err != nil {
   141  			return
   142  		}
   143  	}
   144  	// i < len(spaces)
   145  	if i > 0 {
   146  		_, err = p.output.Write(spaces[0:i])
   147  	}
   148  	return
   149  }
   150  
   151  func (p *tconv) Write(data []byte) (n int, err error) {
   152  	if len(data) == 0 {
   153  		return
   154  	}
   155  	pos := 0 // valid if p.state == collecting
   156  	var b byte
   157  	for n, b = range data {
   158  		switch p.state {
   159  		case indenting:
   160  			switch b {
   161  			case '\t':
   162  				p.indent += *tabwidth
   163  			case '\n':
   164  				p.indent = 0
   165  				if _, err = p.output.Write(data[n : n+1]); err != nil {
   166  					return
   167  				}
   168  			case ' ':
   169  				p.indent++
   170  			default:
   171  				p.state = collecting
   172  				pos = n
   173  				if err = p.writeIndent(); err != nil {
   174  					return
   175  				}
   176  			}
   177  		case collecting:
   178  			if b == '\n' {
   179  				p.state = indenting
   180  				p.indent = 0
   181  				if _, err = p.output.Write(data[pos : n+1]); err != nil {
   182  					return
   183  				}
   184  			}
   185  		}
   186  	}
   187  	n = len(data)
   188  	if pos < n && p.state == collecting {
   189  		_, err = p.output.Write(data[pos:])
   190  	}
   191  	return
   192  }
   193  
   194  // ----------------------------------------------------------------------------
   195  // Templates
   196  
   197  // Write an AST node to w.
   198  func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
   199  	// convert trailing tabs into spaces using a tconv filter
   200  	// to ensure a good outcome in most browsers (there may still
   201  	// be tabs in comments and strings, but converting those into
   202  	// the right number of spaces is much harder)
   203  	//
   204  	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
   205  	//           with an another printer mode (which is more efficiently
   206  	//           implemented in the printer than here with another layer)
   207  	mode := printer.TabIndent | printer.UseSpaces
   208  	err := (&printer.Config{Mode: mode, Tabwidth: *tabwidth}).Fprint(&tconv{output: w}, fset, x)
   209  	if err != nil {
   210  		log.Print(err)
   211  	}
   212  }
   213  
   214  func filenameFunc(path string) string {
   215  	_, localname := pathpkg.Split(path)
   216  	return localname
   217  }
   218  
   219  func fileInfoNameFunc(fi os.FileInfo) string {
   220  	name := fi.Name()
   221  	if fi.IsDir() {
   222  		name += "/"
   223  	}
   224  	return name
   225  }
   226  
   227  func fileInfoTimeFunc(fi os.FileInfo) string {
   228  	if t := fi.ModTime(); t.Unix() != 0 {
   229  		return t.Local().String()
   230  	}
   231  	return "" // don't return epoch if time is obviously not set
   232  }
   233  
   234  // The strings in infoKinds must be properly html-escaped.
   235  var infoKinds = [nKinds]string{
   236  	PackageClause: "package&nbsp;clause",
   237  	ImportDecl:    "import&nbsp;decl",
   238  	ConstDecl:     "const&nbsp;decl",
   239  	TypeDecl:      "type&nbsp;decl",
   240  	VarDecl:       "var&nbsp;decl",
   241  	FuncDecl:      "func&nbsp;decl",
   242  	MethodDecl:    "method&nbsp;decl",
   243  	Use:           "use",
   244  }
   245  
   246  func infoKind_htmlFunc(info SpotInfo) string {
   247  	return infoKinds[info.Kind()] // infoKind entries are html-escaped
   248  }
   249  
   250  func infoLineFunc(info SpotInfo) int {
   251  	line := info.Lori()
   252  	if info.IsIndex() {
   253  		index, _ := searchIndex.get()
   254  		if index != nil {
   255  			line = index.(*Index).Snippet(line).Line
   256  		} else {
   257  			// no line information available because
   258  			// we don't have an index - this should
   259  			// never happen; be conservative and don't
   260  			// crash
   261  			line = 0
   262  		}
   263  	}
   264  	return line
   265  }
   266  
   267  func infoSnippet_htmlFunc(info SpotInfo) string {
   268  	if info.IsIndex() {
   269  		index, _ := searchIndex.get()
   270  		// Snippet.Text was HTML-escaped when it was generated
   271  		return index.(*Index).Snippet(info.Lori()).Text
   272  	}
   273  	return `<span class="alert">no snippet text available</span>`
   274  }
   275  
   276  func nodeFunc(info *PageInfo, node interface{}) string {
   277  	var buf bytes.Buffer
   278  	writeNode(&buf, info.FSet, node)
   279  	return buf.String()
   280  }
   281  
   282  func node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
   283  	var buf1 bytes.Buffer
   284  	writeNode(&buf1, info.FSet, node)
   285  
   286  	var buf2 bytes.Buffer
   287  	if n, _ := node.(ast.Node); n != nil && linkify && *declLinks {
   288  		LinkifyText(&buf2, buf1.Bytes(), n)
   289  	} else {
   290  		FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
   291  	}
   292  
   293  	return buf2.String()
   294  }
   295  
   296  func comment_htmlFunc(comment string) string {
   297  	var buf bytes.Buffer
   298  	// TODO(gri) Provide list of words (e.g. function parameters)
   299  	//           to be emphasized by ToHTML.
   300  	doc.ToHTML(&buf, comment, nil) // does html-escaping
   301  	return buf.String()
   302  }
   303  
   304  // punchCardWidth is the number of columns of fixed-width
   305  // characters to assume when wrapping text.  Very few people
   306  // use terminals or cards smaller than 80 characters, so 80 it is.
   307  // We do not try to sniff the environment or the tty to adapt to
   308  // the situation; instead, by using a constant we make sure that
   309  // godoc always produces the same output regardless of context,
   310  // a consistency that is lost otherwise.  For example, if we sniffed
   311  // the environment or tty, then http://golang.org/pkg/math/?m=text
   312  // would depend on the width of the terminal where godoc started,
   313  // which is clearly bogus.  More generally, the Unix tools that behave
   314  // differently when writing to a tty than when writing to a file have
   315  // a history of causing confusion (compare `ls` and `ls | cat`), and we
   316  // want to avoid that mistake here.
   317  const punchCardWidth = 80
   318  
   319  func comment_textFunc(comment, indent, preIndent string) string {
   320  	var buf bytes.Buffer
   321  	doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent))
   322  	return buf.String()
   323  }
   324  
   325  func startsWithUppercase(s string) bool {
   326  	r, _ := utf8.DecodeRuneInString(s)
   327  	return unicode.IsUpper(r)
   328  }
   329  
   330  var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
   331  
   332  // stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
   333  // while keeping uppercase Braz in Foo_Braz.
   334  func stripExampleSuffix(name string) string {
   335  	if i := strings.LastIndex(name, "_"); i != -1 {
   336  		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
   337  			name = name[:i]
   338  		}
   339  	}
   340  	return name
   341  }
   342  
   343  func example_textFunc(info *PageInfo, funcName, indent string) string {
   344  	if !*showExamples {
   345  		return ""
   346  	}
   347  
   348  	var buf bytes.Buffer
   349  	first := true
   350  	for _, eg := range info.Examples {
   351  		name := stripExampleSuffix(eg.Name)
   352  		if name != funcName {
   353  			continue
   354  		}
   355  
   356  		if !first {
   357  			buf.WriteString("\n")
   358  		}
   359  		first = false
   360  
   361  		// print code
   362  		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
   363  		var buf1 bytes.Buffer
   364  		writeNode(&buf1, info.FSet, cnode)
   365  		code := buf1.String()
   366  		// Additional formatting if this is a function body.
   367  		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
   368  			// remove surrounding braces
   369  			code = code[1 : n-1]
   370  			// unindent
   371  			code = strings.Replace(code, "\n    ", "\n", -1)
   372  		}
   373  		code = strings.Trim(code, "\n")
   374  		code = strings.Replace(code, "\n", "\n\t", -1)
   375  
   376  		buf.WriteString(indent)
   377  		buf.WriteString("Example:\n\t")
   378  		buf.WriteString(code)
   379  		buf.WriteString("\n")
   380  	}
   381  	return buf.String()
   382  }
   383  
   384  func example_htmlFunc(info *PageInfo, funcName string) string {
   385  	var buf bytes.Buffer
   386  	for _, eg := range info.Examples {
   387  		name := stripExampleSuffix(eg.Name)
   388  
   389  		if name != funcName {
   390  			continue
   391  		}
   392  
   393  		// print code
   394  		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
   395  		code := node_htmlFunc(info, cnode, true)
   396  		out := eg.Output
   397  		wholeFile := true
   398  
   399  		// Additional formatting if this is a function body.
   400  		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
   401  			wholeFile = false
   402  			// remove surrounding braces
   403  			code = code[1 : n-1]
   404  			// unindent
   405  			code = strings.Replace(code, "\n    ", "\n", -1)
   406  			// remove output comment
   407  			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
   408  				code = strings.TrimSpace(code[:loc[0]])
   409  			}
   410  		}
   411  
   412  		// Write out the playground code in standard Go style
   413  		// (use tabs, no comment highlight, etc).
   414  		play := ""
   415  		if eg.Play != nil && *showPlayground {
   416  			var buf bytes.Buffer
   417  			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
   418  				log.Print(err)
   419  			} else {
   420  				play = buf.String()
   421  			}
   422  		}
   423  
   424  		// Drop output, as the output comment will appear in the code.
   425  		if wholeFile && play == "" {
   426  			out = ""
   427  		}
   428  
   429  		err := exampleHTML.Execute(&buf, struct {
   430  			Name, Doc, Code, Play, Output string
   431  		}{eg.Name, eg.Doc, code, play, out})
   432  		if err != nil {
   433  			log.Print(err)
   434  		}
   435  	}
   436  	return buf.String()
   437  }
   438  
   439  // example_nameFunc takes an example function name and returns its display
   440  // name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
   441  func example_nameFunc(s string) string {
   442  	name, suffix := splitExampleName(s)
   443  	// replace _ with . for method names
   444  	name = strings.Replace(name, "_", ".", 1)
   445  	// use "Package" if no name provided
   446  	if name == "" {
   447  		name = "Package"
   448  	}
   449  	return name + suffix
   450  }
   451  
   452  // example_suffixFunc takes an example function name and returns its suffix in
   453  // parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
   454  func example_suffixFunc(name string) string {
   455  	_, suffix := splitExampleName(name)
   456  	return suffix
   457  }
   458  
   459  func noteTitle(note string) string {
   460  	return strings.Title(strings.ToLower(note))
   461  }
   462  
   463  func splitExampleName(s string) (name, suffix string) {
   464  	i := strings.LastIndex(s, "_")
   465  	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
   466  		name = s[:i]
   467  		suffix = " (" + strings.Title(s[i+1:]) + ")"
   468  		return
   469  	}
   470  	name = s
   471  	return
   472  }
   473  
   474  func pkgLinkFunc(path string) string {
   475  	relpath := path[1:]
   476  	// because of the irregular mapping under goroot
   477  	// we need to correct certain relative paths
   478  	relpath = strings.TrimPrefix(relpath, "src/pkg/")
   479  	return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL
   480  }
   481  
   482  // n must be an ast.Node or a *doc.Note
   483  func posLink_urlFunc(info *PageInfo, n interface{}) string {
   484  	var pos, end token.Pos
   485  
   486  	switch n := n.(type) {
   487  	case ast.Node:
   488  		pos = n.Pos()
   489  		end = n.End()
   490  	case *doc.Note:
   491  		pos = n.Pos
   492  		end = n.End
   493  	default:
   494  		panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
   495  	}
   496  
   497  	var relpath string
   498  	var line int
   499  	var low, high int // selection offset range
   500  
   501  	if pos.IsValid() {
   502  		p := info.FSet.Position(pos)
   503  		relpath = p.Filename
   504  		line = p.Line
   505  		low = p.Offset
   506  	}
   507  	if end.IsValid() {
   508  		high = info.FSet.Position(end).Offset
   509  	}
   510  
   511  	var buf bytes.Buffer
   512  	template.HTMLEscape(&buf, []byte(relpath))
   513  	// selection ranges are of form "s=low:high"
   514  	if low < high {
   515  		fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
   516  		// if we have a selection, position the page
   517  		// such that the selection is a bit below the top
   518  		line -= 10
   519  		if line < 1 {
   520  			line = 1
   521  		}
   522  	}
   523  	// line id's in html-printed source are of the
   524  	// form "L%d" where %d stands for the line number
   525  	if line > 0 {
   526  		fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
   527  	}
   528  
   529  	return buf.String()
   530  }
   531  
   532  func srcLinkFunc(s string) string {
   533  	return pathpkg.Clean("/" + s)
   534  }
   535  
   536  // fmap describes the template functions installed with all godoc templates.
   537  // Convention: template function names ending in "_html" or "_url" produce
   538  //             HTML- or URL-escaped strings; all other function results may
   539  //             require explicit escaping in the template.
   540  var fmap = template.FuncMap{
   541  	// various helpers
   542  	"filename": filenameFunc,
   543  	"repeat":   strings.Repeat,
   544  
   545  	// access to FileInfos (directory listings)
   546  	"fileInfoName": fileInfoNameFunc,
   547  	"fileInfoTime": fileInfoTimeFunc,
   548  
   549  	// access to search result information
   550  	"infoKind_html":    infoKind_htmlFunc,
   551  	"infoLine":         infoLineFunc,
   552  	"infoSnippet_html": infoSnippet_htmlFunc,
   553  
   554  	// formatting of AST nodes
   555  	"node":         nodeFunc,
   556  	"node_html":    node_htmlFunc,
   557  	"comment_html": comment_htmlFunc,
   558  	"comment_text": comment_textFunc,
   559  
   560  	// support for URL attributes
   561  	"pkgLink":     pkgLinkFunc,
   562  	"srcLink":     srcLinkFunc,
   563  	"posLink_url": posLink_urlFunc,
   564  
   565  	// formatting of Examples
   566  	"example_html":   example_htmlFunc,
   567  	"example_text":   example_textFunc,
   568  	"example_name":   example_nameFunc,
   569  	"example_suffix": example_suffixFunc,
   570  
   571  	// formatting of Notes
   572  	"noteTitle": noteTitle,
   573  }
   574  
   575  func readTemplate(name string) *template.Template {
   576  	path := "lib/godoc/" + name
   577  
   578  	// use underlying file system fs to read the template file
   579  	// (cannot use template ParseFile functions directly)
   580  	data, err := ReadFile(fs, path)
   581  	if err != nil {
   582  		log.Fatal("readTemplate: ", err)
   583  	}
   584  	// be explicit with errors (for app engine use)
   585  	t, err := template.New(name).Funcs(fmap).Parse(string(data))
   586  	if err != nil {
   587  		log.Fatal("readTemplate: ", err)
   588  	}
   589  	return t
   590  }
   591  
   592  var (
   593  	codewalkHTML,
   594  	codewalkdirHTML,
   595  	dirlistHTML,
   596  	errorHTML,
   597  	exampleHTML,
   598  	godocHTML,
   599  	packageHTML,
   600  	packageText,
   601  	searchHTML,
   602  	searchText,
   603  	searchDescXML *template.Template
   604  )
   605  
   606  func readTemplates() {
   607  	// have to delay until after flags processing since paths depend on goroot
   608  	codewalkHTML = readTemplate("codewalk.html")
   609  	codewalkdirHTML = readTemplate("codewalkdir.html")
   610  	dirlistHTML = readTemplate("dirlist.html")
   611  	errorHTML = readTemplate("error.html")
   612  	exampleHTML = readTemplate("example.html")
   613  	godocHTML = readTemplate("godoc.html")
   614  	packageHTML = readTemplate("package.html")
   615  	packageText = readTemplate("package.txt")
   616  	searchHTML = readTemplate("search.html")
   617  	searchText = readTemplate("search.txt")
   618  	searchDescXML = readTemplate("opensearch.xml")
   619  }
   620  
   621  // ----------------------------------------------------------------------------
   622  // Generic HTML wrapper
   623  
   624  // Page describes the contents of the top-level godoc webpage.
   625  type Page struct {
   626  	Title    string
   627  	Tabtitle string
   628  	Subtitle string
   629  	Query    string
   630  	Body     []byte
   631  
   632  	// filled in by servePage
   633  	SearchBox  bool
   634  	Playground bool
   635  	Version    string
   636  }
   637  
   638  func servePage(w http.ResponseWriter, page Page) {
   639  	if page.Tabtitle == "" {
   640  		page.Tabtitle = page.Title
   641  	}
   642  	page.SearchBox = *indexEnabled
   643  	page.Playground = *showPlayground
   644  	page.Version = runtime.Version()
   645  	if err := godocHTML.Execute(w, page); err != nil {
   646  		log.Printf("godocHTML.Execute: %s", err)
   647  	}
   648  }
   649  
   650  func serveText(w http.ResponseWriter, text []byte) {
   651  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   652  	w.Write(text)
   653  }
   654  
   655  // ----------------------------------------------------------------------------
   656  // Files
   657  
   658  var (
   659  	doctype   = []byte("<!DOCTYPE ")
   660  	jsonStart = []byte("<!--{")
   661  	jsonEnd   = []byte("}-->")
   662  )
   663  
   664  func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
   665  	// get HTML body contents
   666  	src, err := ReadFile(fs, abspath)
   667  	if err != nil {
   668  		log.Printf("ReadFile: %s", err)
   669  		serveError(w, r, relpath, err)
   670  		return
   671  	}
   672  
   673  	// if it begins with "<!DOCTYPE " assume it is standalone
   674  	// html that doesn't need the template wrapping.
   675  	if bytes.HasPrefix(src, doctype) {
   676  		w.Write(src)
   677  		return
   678  	}
   679  
   680  	// if it begins with a JSON blob, read in the metadata.
   681  	meta, src, err := extractMetadata(src)
   682  	if err != nil {
   683  		log.Printf("decoding metadata %s: %v", relpath, err)
   684  	}
   685  
   686  	// evaluate as template if indicated
   687  	if meta.Template {
   688  		tmpl, err := template.New("main").Funcs(templateFuncs).Parse(string(src))
   689  		if err != nil {
   690  			log.Printf("parsing template %s: %v", relpath, err)
   691  			serveError(w, r, relpath, err)
   692  			return
   693  		}
   694  		var buf bytes.Buffer
   695  		if err := tmpl.Execute(&buf, nil); err != nil {
   696  			log.Printf("executing template %s: %v", relpath, err)
   697  			serveError(w, r, relpath, err)
   698  			return
   699  		}
   700  		src = buf.Bytes()
   701  	}
   702  
   703  	// if it's the language spec, add tags to EBNF productions
   704  	if strings.HasSuffix(abspath, "go_spec.html") {
   705  		var buf bytes.Buffer
   706  		Linkify(&buf, src)
   707  		src = buf.Bytes()
   708  	}
   709  
   710  	servePage(w, Page{
   711  		Title:    meta.Title,
   712  		Subtitle: meta.Subtitle,
   713  		Body:     src,
   714  	})
   715  }
   716  
   717  func applyTemplate(t *template.Template, name string, data interface{}) []byte {
   718  	var buf bytes.Buffer
   719  	if err := t.Execute(&buf, data); err != nil {
   720  		log.Printf("%s.Execute: %s", name, err)
   721  	}
   722  	return buf.Bytes()
   723  }
   724  
   725  func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
   726  	canonical := pathpkg.Clean(r.URL.Path)
   727  	if !strings.HasSuffix(canonical, "/") {
   728  		canonical += "/"
   729  	}
   730  	if r.URL.Path != canonical {
   731  		url := *r.URL
   732  		url.Path = canonical
   733  		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
   734  		redirected = true
   735  	}
   736  	return
   737  }
   738  
   739  func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
   740  	c := pathpkg.Clean(r.URL.Path)
   741  	c = strings.TrimRight(c, "/")
   742  	if r.URL.Path != c {
   743  		url := *r.URL
   744  		url.Path = c
   745  		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
   746  		redirected = true
   747  	}
   748  	return
   749  }
   750  
   751  func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
   752  	src, err := ReadFile(fs, abspath)
   753  	if err != nil {
   754  		log.Printf("ReadFile: %s", err)
   755  		serveError(w, r, relpath, err)
   756  		return
   757  	}
   758  
   759  	if r.FormValue("m") == "text" {
   760  		serveText(w, src)
   761  		return
   762  	}
   763  
   764  	var buf bytes.Buffer
   765  	buf.WriteString("<pre>")
   766  	FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
   767  	buf.WriteString("</pre>")
   768  	fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
   769  
   770  	servePage(w, Page{
   771  		Title:    title + " " + relpath,
   772  		Tabtitle: relpath,
   773  		Body:     buf.Bytes(),
   774  	})
   775  }
   776  
   777  func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
   778  	if redirect(w, r) {
   779  		return
   780  	}
   781  
   782  	list, err := fs.ReadDir(abspath)
   783  	if err != nil {
   784  		serveError(w, r, relpath, err)
   785  		return
   786  	}
   787  
   788  	servePage(w, Page{
   789  		Title:    "Directory " + relpath,
   790  		Tabtitle: relpath,
   791  		Body:     applyTemplate(dirlistHTML, "dirlistHTML", list),
   792  	})
   793  }
   794  
   795  func serveFile(w http.ResponseWriter, r *http.Request) {
   796  	relpath := r.URL.Path
   797  
   798  	// Check to see if we need to redirect or serve another file.
   799  	if m := metadataFor(relpath); m != nil {
   800  		if m.Path != relpath {
   801  			// Redirect to canonical path.
   802  			http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
   803  			return
   804  		}
   805  		// Serve from the actual filesystem path.
   806  		relpath = m.filePath
   807  	}
   808  
   809  	abspath := relpath
   810  	relpath = relpath[1:] // strip leading slash
   811  
   812  	switch pathpkg.Ext(relpath) {
   813  	case ".html":
   814  		if strings.HasSuffix(relpath, "/index.html") {
   815  			// We'll show index.html for the directory.
   816  			// Use the dir/ version as canonical instead of dir/index.html.
   817  			http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
   818  			return
   819  		}
   820  		serveHTMLDoc(w, r, abspath, relpath)
   821  		return
   822  
   823  	case ".go":
   824  		serveTextFile(w, r, abspath, relpath, "Source file")
   825  		return
   826  	}
   827  
   828  	dir, err := fs.Lstat(abspath)
   829  	if err != nil {
   830  		log.Print(err)
   831  		serveError(w, r, relpath, err)
   832  		return
   833  	}
   834  
   835  	if dir != nil && dir.IsDir() {
   836  		if redirect(w, r) {
   837  			return
   838  		}
   839  		if index := pathpkg.Join(abspath, "index.html"); isTextFile(index) {
   840  			serveHTMLDoc(w, r, index, index)
   841  			return
   842  		}
   843  		serveDirectory(w, r, abspath, relpath)
   844  		return
   845  	}
   846  
   847  	if isTextFile(abspath) {
   848  		if redirectFile(w, r) {
   849  			return
   850  		}
   851  		serveTextFile(w, r, abspath, relpath, "Text file")
   852  		return
   853  	}
   854  
   855  	fileServer.ServeHTTP(w, r)
   856  }
   857  
   858  func serveSearchDesc(w http.ResponseWriter, r *http.Request) {
   859  	w.Header().Set("Content-Type", "application/opensearchdescription+xml")
   860  	data := map[string]interface{}{
   861  		"BaseURL": fmt.Sprintf("http://%s", r.Host),
   862  	}
   863  	if err := searchDescXML.Execute(w, &data); err != nil {
   864  		log.Printf("searchDescXML.Execute: %s", err)
   865  	}
   866  }
   867  
   868  // ----------------------------------------------------------------------------
   869  // Packages
   870  
   871  // Fake relative package path for built-ins. Documentation for all globals
   872  // (not just exported ones) will be shown for packages in this directory.
   873  const builtinPkgPath = "builtin"
   874  
   875  type PageInfoMode uint
   876  
   877  const (
   878  	noFiltering PageInfoMode = 1 << iota // do not filter exports
   879  	allMethods                           // show all embedded methods
   880  	showSource                           // show source code, do not extract documentation
   881  	noHtml                               // show result in textual form, do not generate HTML
   882  	flatDir                              // show directory in a flat (non-indented) manner
   883  )
   884  
   885  // modeNames defines names for each PageInfoMode flag.
   886  var modeNames = map[string]PageInfoMode{
   887  	"all":     noFiltering,
   888  	"methods": allMethods,
   889  	"src":     showSource,
   890  	"text":    noHtml,
   891  	"flat":    flatDir,
   892  }
   893  
   894  // getPageInfoMode computes the PageInfoMode flags by analyzing the request
   895  // URL form value "m". It is value is a comma-separated list of mode names
   896  // as defined by modeNames (e.g.: m=src,text).
   897  func getPageInfoMode(r *http.Request) PageInfoMode {
   898  	var mode PageInfoMode
   899  	for _, k := range strings.Split(r.FormValue("m"), ",") {
   900  		if m, found := modeNames[strings.TrimSpace(k)]; found {
   901  			mode |= m
   902  		}
   903  	}
   904  	return adjustPageInfoMode(r, mode)
   905  }
   906  
   907  // Specialized versions of godoc may adjust the PageInfoMode by overriding
   908  // this variable.
   909  var adjustPageInfoMode = func(_ *http.Request, mode PageInfoMode) PageInfoMode {
   910  	return mode
   911  }
   912  
   913  // remoteSearchURL returns the search URL for a given query as needed by
   914  // remoteSearch. If html is set, an html result is requested; otherwise
   915  // the result is in textual form.
   916  // Adjust this function as necessary if modeNames or FormValue parameters
   917  // change.
   918  func remoteSearchURL(query string, html bool) string {
   919  	s := "/search?m=text&q="
   920  	if html {
   921  		s = "/search?q="
   922  	}
   923  	return s + url.QueryEscape(query)
   924  }
   925  
   926  type PageInfo struct {
   927  	Dirname string // directory containing the package
   928  	Err     error  // error or nil
   929  
   930  	// package info
   931  	FSet     *token.FileSet         // nil if no package documentation
   932  	PDoc     *doc.Package           // nil if no package documentation
   933  	Examples []*doc.Example         // nil if no example code
   934  	Notes    map[string][]*doc.Note // nil if no package Notes
   935  	PAst     *ast.File              // nil if no AST with package exports
   936  	IsMain   bool                   // true for package main
   937  
   938  	// directory info
   939  	Dirs    *DirList  // nil if no directory information
   940  	DirTime time.Time // directory time stamp
   941  	DirFlat bool      // if set, show directory in a flat (non-indented) manner
   942  }
   943  
   944  func (info *PageInfo) IsEmpty() bool {
   945  	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
   946  }
   947  
   948  type docServer struct {
   949  	pattern string // url pattern; e.g. "/pkg/"
   950  	fsRoot  string // file system root to which the pattern is mapped
   951  }
   952  
   953  // fsReadDir implements ReadDir for the go/build package.
   954  func fsReadDir(dir string) ([]os.FileInfo, error) {
   955  	return fs.ReadDir(filepath.ToSlash(dir))
   956  }
   957  
   958  // fsOpenFile implements OpenFile for the go/build package.
   959  func fsOpenFile(name string) (r io.ReadCloser, err error) {
   960  	data, err := ReadFile(fs, filepath.ToSlash(name))
   961  	if err != nil {
   962  		return nil, err
   963  	}
   964  	return ioutil.NopCloser(bytes.NewReader(data)), nil
   965  }
   966  
   967  // packageExports is a local implementation of ast.PackageExports
   968  // which correctly updates each package file's comment list.
   969  // (The ast.PackageExports signature is frozen, hence the local
   970  // implementation).
   971  //
   972  func packageExports(fset *token.FileSet, pkg *ast.Package) {
   973  	for _, src := range pkg.Files {
   974  		cmap := ast.NewCommentMap(fset, src, src.Comments)
   975  		ast.FileExports(src)
   976  		src.Comments = cmap.Filter(src).Comments()
   977  	}
   978  }
   979  
   980  // addNames adds the names declared by decl to the names set.
   981  // Method names are added in the form ReceiverTypeName_Method.
   982  func addNames(names map[string]bool, decl ast.Decl) {
   983  	switch d := decl.(type) {
   984  	case *ast.FuncDecl:
   985  		name := d.Name.Name
   986  		if d.Recv != nil {
   987  			var typeName string
   988  			switch r := d.Recv.List[0].Type.(type) {
   989  			case *ast.StarExpr:
   990  				typeName = r.X.(*ast.Ident).Name
   991  			case *ast.Ident:
   992  				typeName = r.Name
   993  			}
   994  			name = typeName + "_" + name
   995  		}
   996  		names[name] = true
   997  	case *ast.GenDecl:
   998  		for _, spec := range d.Specs {
   999  			switch s := spec.(type) {
  1000  			case *ast.TypeSpec:
  1001  				names[s.Name.Name] = true
  1002  			case *ast.ValueSpec:
  1003  				for _, id := range s.Names {
  1004  					names[id.Name] = true
  1005  				}
  1006  			}
  1007  		}
  1008  	}
  1009  }
  1010  
  1011  // globalNames returns a set of the names declared by all package-level
  1012  // declarations. Method names are returned in the form Receiver_Method.
  1013  func globalNames(pkg *ast.Package) map[string]bool {
  1014  	names := make(map[string]bool)
  1015  	for _, file := range pkg.Files {
  1016  		for _, decl := range file.Decls {
  1017  			addNames(names, decl)
  1018  		}
  1019  	}
  1020  	return names
  1021  }
  1022  
  1023  // collectExamples collects examples for pkg from testfiles.
  1024  func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
  1025  	var files []*ast.File
  1026  	for _, f := range testfiles {
  1027  		files = append(files, f)
  1028  	}
  1029  
  1030  	var examples []*doc.Example
  1031  	globals := globalNames(pkg)
  1032  	for _, e := range doc.Examples(files...) {
  1033  		name := stripExampleSuffix(e.Name)
  1034  		if name == "" || globals[name] {
  1035  			examples = append(examples, e)
  1036  		} else {
  1037  			log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name)
  1038  		}
  1039  	}
  1040  
  1041  	return examples
  1042  }
  1043  
  1044  // poorMansImporter returns a (dummy) package object named
  1045  // by the last path component of the provided package path
  1046  // (as is the convention for packages). This is sufficient
  1047  // to resolve package identifiers without doing an actual
  1048  // import. It never returns an error.
  1049  //
  1050  func poorMansImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
  1051  	pkg := imports[path]
  1052  	if pkg == nil {
  1053  		// note that strings.LastIndex returns -1 if there is no "/"
  1054  		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
  1055  		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
  1056  		imports[path] = pkg
  1057  	}
  1058  	return pkg, nil
  1059  }
  1060  
  1061  // getPageInfo returns the PageInfo for a package directory abspath. If the
  1062  // parameter genAST is set, an AST containing only the package exports is
  1063  // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
  1064  // is extracted from the AST. If there is no corresponding package in the
  1065  // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub-
  1066  // directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is
  1067  // set to the respective error but the error is not logged.
  1068  //
  1069  func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
  1070  	info := &PageInfo{Dirname: abspath}
  1071  
  1072  	// Restrict to the package files that would be used when building
  1073  	// the package on this system.  This makes sure that if there are
  1074  	// separate implementations for, say, Windows vs Unix, we don't
  1075  	// jumble them all together.
  1076  	// Note: Uses current binary's GOOS/GOARCH.
  1077  	// To use different pair, such as if we allowed the user to choose,
  1078  	// set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir.
  1079  	ctxt := build.Default
  1080  	ctxt.IsAbsPath = pathpkg.IsAbs
  1081  	ctxt.ReadDir = fsReadDir
  1082  	ctxt.OpenFile = fsOpenFile
  1083  	pkginfo, err := ctxt.ImportDir(abspath, 0)
  1084  	// continue if there are no Go source files; we still want the directory info
  1085  	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
  1086  		info.Err = err
  1087  		return info
  1088  	}
  1089  
  1090  	// collect package files
  1091  	pkgname := pkginfo.Name
  1092  	pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
  1093  	if len(pkgfiles) == 0 {
  1094  		// Commands written in C have no .go files in the build.
  1095  		// Instead, documentation may be found in an ignored file.
  1096  		// The file may be ignored via an explicit +build ignore
  1097  		// constraint (recommended), or by defining the package
  1098  		// documentation (historic).
  1099  		pkgname = "main" // assume package main since pkginfo.Name == ""
  1100  		pkgfiles = pkginfo.IgnoredGoFiles
  1101  	}
  1102  
  1103  	// get package information, if any
  1104  	if len(pkgfiles) > 0 {
  1105  		// build package AST
  1106  		fset := token.NewFileSet()
  1107  		files, err := parseFiles(fset, abspath, pkgfiles)
  1108  		if err != nil {
  1109  			info.Err = err
  1110  			return info
  1111  		}
  1112  
  1113  		// ignore any errors - they are due to unresolved identifiers
  1114  		pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil)
  1115  
  1116  		// extract package documentation
  1117  		info.FSet = fset
  1118  		if mode&showSource == 0 {
  1119  			// show extracted documentation
  1120  			var m doc.Mode
  1121  			if mode&noFiltering != 0 {
  1122  				m = doc.AllDecls
  1123  			}
  1124  			if mode&allMethods != 0 {
  1125  				m |= doc.AllMethods
  1126  			}
  1127  			info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath
  1128  
  1129  			// collect examples
  1130  			testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
  1131  			files, err = parseFiles(fset, abspath, testfiles)
  1132  			if err != nil {
  1133  				log.Println("parsing examples:", err)
  1134  			}
  1135  			info.Examples = collectExamples(pkg, files)
  1136  
  1137  			// collect any notes that we want to show
  1138  			if info.PDoc.Notes != nil {
  1139  				// could regexp.Compile only once per godoc, but probably not worth it
  1140  				if rx, err := regexp.Compile(*notes); err == nil {
  1141  					for m, n := range info.PDoc.Notes {
  1142  						if rx.MatchString(m) {
  1143  							if info.Notes == nil {
  1144  								info.Notes = make(map[string][]*doc.Note)
  1145  							}
  1146  							info.Notes[m] = n
  1147  						}
  1148  					}
  1149  				}
  1150  			}
  1151  
  1152  		} else {
  1153  			// show source code
  1154  			// TODO(gri) Consider eliminating export filtering in this mode,
  1155  			//           or perhaps eliminating the mode altogether.
  1156  			if mode&noFiltering == 0 {
  1157  				packageExports(fset, pkg)
  1158  			}
  1159  			info.PAst = ast.MergePackageFiles(pkg, 0)
  1160  		}
  1161  		info.IsMain = pkgname == "main"
  1162  	}
  1163  
  1164  	// get directory information, if any
  1165  	var dir *Directory
  1166  	var timestamp time.Time
  1167  	if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil {
  1168  		// directory tree is present; lookup respective directory
  1169  		// (may still fail if the file system was updated and the
  1170  		// new directory tree has not yet been computed)
  1171  		dir = tree.(*Directory).lookup(abspath)
  1172  		timestamp = ts
  1173  	}
  1174  	if dir == nil {
  1175  		// no directory tree present (too early after startup or
  1176  		// command-line mode); compute one level for this page
  1177  		// note: cannot use path filter here because in general
  1178  		//       it doesn't contain the fsTree path
  1179  		dir = newDirectory(abspath, 1)
  1180  		timestamp = time.Now()
  1181  	}
  1182  	info.Dirs = dir.listing(true)
  1183  	info.DirTime = timestamp
  1184  	info.DirFlat = mode&flatDir != 0
  1185  
  1186  	return info
  1187  }
  1188  
  1189  func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  1190  	if redirect(w, r) {
  1191  		return
  1192  	}
  1193  
  1194  	relpath := pathpkg.Clean(r.URL.Path[len(h.pattern):])
  1195  	abspath := pathpkg.Join(h.fsRoot, relpath)
  1196  	mode := getPageInfoMode(r)
  1197  	if relpath == builtinPkgPath {
  1198  		mode = noFiltering
  1199  	}
  1200  	info := h.getPageInfo(abspath, relpath, mode)
  1201  	if info.Err != nil {
  1202  		log.Print(info.Err)
  1203  		serveError(w, r, relpath, info.Err)
  1204  		return
  1205  	}
  1206  
  1207  	if mode&noHtml != 0 {
  1208  		serveText(w, applyTemplate(packageText, "packageText", info))
  1209  		return
  1210  	}
  1211  
  1212  	var tabtitle, title, subtitle string
  1213  	switch {
  1214  	case info.PAst != nil:
  1215  		tabtitle = info.PAst.Name.Name
  1216  	case info.PDoc != nil:
  1217  		tabtitle = info.PDoc.Name
  1218  	default:
  1219  		tabtitle = info.Dirname
  1220  		title = "Directory "
  1221  		if *showTimestamps {
  1222  			subtitle = "Last update: " + info.DirTime.String()
  1223  		}
  1224  	}
  1225  	if title == "" {
  1226  		if info.IsMain {
  1227  			// assume that the directory name is the command name
  1228  			_, tabtitle = pathpkg.Split(relpath)
  1229  			title = "Command "
  1230  		} else {
  1231  			title = "Package "
  1232  		}
  1233  	}
  1234  	title += tabtitle
  1235  
  1236  	// special cases for top-level package/command directories
  1237  	switch tabtitle {
  1238  	case "/src/pkg":
  1239  		tabtitle = "Packages"
  1240  	case "/src/cmd":
  1241  		tabtitle = "Commands"
  1242  	}
  1243  
  1244  	servePage(w, Page{
  1245  		Title:    title,
  1246  		Tabtitle: tabtitle,
  1247  		Subtitle: subtitle,
  1248  		Body:     applyTemplate(packageHTML, "packageHTML", info),
  1249  	})
  1250  }
  1251  
  1252  // ----------------------------------------------------------------------------
  1253  // Search
  1254  
  1255  var searchIndex RWValue
  1256  
  1257  type SearchResult struct {
  1258  	Query string
  1259  	Alert string // error or warning message
  1260  
  1261  	// identifier matches
  1262  	Pak HitList       // packages matching Query
  1263  	Hit *LookupResult // identifier matches of Query
  1264  	Alt *AltWords     // alternative identifiers to look for
  1265  
  1266  	// textual matches
  1267  	Found    int         // number of textual occurrences found
  1268  	Textual  []FileLines // textual matches of Query
  1269  	Complete bool        // true if all textual occurrences of Query are reported
  1270  }
  1271  
  1272  func lookup(query string) (result SearchResult) {
  1273  	result.Query = query
  1274  
  1275  	index, timestamp := searchIndex.get()
  1276  	if index != nil {
  1277  		index := index.(*Index)
  1278  
  1279  		// identifier search
  1280  		var err error
  1281  		result.Pak, result.Hit, result.Alt, err = index.Lookup(query)
  1282  		if err != nil && *maxResults <= 0 {
  1283  			// ignore the error if full text search is enabled
  1284  			// since the query may be a valid regular expression
  1285  			result.Alert = "Error in query string: " + err.Error()
  1286  			return
  1287  		}
  1288  
  1289  		// full text search
  1290  		if *maxResults > 0 && query != "" {
  1291  			rx, err := regexp.Compile(query)
  1292  			if err != nil {
  1293  				result.Alert = "Error in query regular expression: " + err.Error()
  1294  				return
  1295  			}
  1296  			// If we get maxResults+1 results we know that there are more than
  1297  			// maxResults results and thus the result may be incomplete (to be
  1298  			// precise, we should remove one result from the result set, but
  1299  			// nobody is going to count the results on the result page).
  1300  			result.Found, result.Textual = index.LookupRegexp(rx, *maxResults+1)
  1301  			result.Complete = result.Found <= *maxResults
  1302  			if !result.Complete {
  1303  				result.Found-- // since we looked for maxResults+1
  1304  			}
  1305  		}
  1306  	}
  1307  
  1308  	// is the result accurate?
  1309  	if *indexEnabled {
  1310  		if _, ts := fsModified.get(); timestamp.Before(ts) {
  1311  			// The index is older than the latest file system change under godoc's observation.
  1312  			result.Alert = "Indexing in progress: result may be inaccurate"
  1313  		}
  1314  	} else {
  1315  		result.Alert = "Search index disabled: no results available"
  1316  	}
  1317  
  1318  	return
  1319  }
  1320  
  1321  func search(w http.ResponseWriter, r *http.Request) {
  1322  	query := strings.TrimSpace(r.FormValue("q"))
  1323  	result := lookup(query)
  1324  
  1325  	if getPageInfoMode(r)&noHtml != 0 {
  1326  		serveText(w, applyTemplate(searchText, "searchText", result))
  1327  		return
  1328  	}
  1329  
  1330  	var title string
  1331  	if result.Hit != nil || len(result.Textual) > 0 {
  1332  		title = fmt.Sprintf(`Results for query %q`, query)
  1333  	} else {
  1334  		title = fmt.Sprintf(`No results found for query %q`, query)
  1335  	}
  1336  
  1337  	servePage(w, Page{
  1338  		Title:    title,
  1339  		Tabtitle: query,
  1340  		Query:    query,
  1341  		Body:     applyTemplate(searchHTML, "searchHTML", result),
  1342  	})
  1343  }
  1344  
  1345  // ----------------------------------------------------------------------------
  1346  // Documentation Metadata
  1347  
  1348  type Metadata struct {
  1349  	Title    string
  1350  	Subtitle string
  1351  	Template bool   // execute as template
  1352  	Path     string // canonical path for this page
  1353  	filePath string // filesystem path relative to goroot
  1354  }
  1355  
  1356  // extractMetadata extracts the Metadata from a byte slice.
  1357  // It returns the Metadata value and the remaining data.
  1358  // If no metadata is present the original byte slice is returned.
  1359  //
  1360  func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
  1361  	tail = b
  1362  	if !bytes.HasPrefix(b, jsonStart) {
  1363  		return
  1364  	}
  1365  	end := bytes.Index(b, jsonEnd)
  1366  	if end < 0 {
  1367  		return
  1368  	}
  1369  	b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
  1370  	if err = json.Unmarshal(b, &meta); err != nil {
  1371  		return
  1372  	}
  1373  	tail = tail[end+len(jsonEnd):]
  1374  	return
  1375  }
  1376  
  1377  // updateMetadata scans $GOROOT/doc for HTML files, reads their metadata,
  1378  // and updates the docMetadata map.
  1379  //
  1380  func updateMetadata() {
  1381  	metadata := make(map[string]*Metadata)
  1382  	var scan func(string) // scan is recursive
  1383  	scan = func(dir string) {
  1384  		fis, err := fs.ReadDir(dir)
  1385  		if err != nil {
  1386  			log.Println("updateMetadata:", err)
  1387  			return
  1388  		}
  1389  		for _, fi := range fis {
  1390  			name := pathpkg.Join(dir, fi.Name())
  1391  			if fi.IsDir() {
  1392  				scan(name) // recurse
  1393  				continue
  1394  			}
  1395  			if !strings.HasSuffix(name, ".html") {
  1396  				continue
  1397  			}
  1398  			// Extract metadata from the file.
  1399  			b, err := ReadFile(fs, name)
  1400  			if err != nil {
  1401  				log.Printf("updateMetadata %s: %v", name, err)
  1402  				continue
  1403  			}
  1404  			meta, _, err := extractMetadata(b)
  1405  			if err != nil {
  1406  				log.Printf("updateMetadata: %s: %v", name, err)
  1407  				continue
  1408  			}
  1409  			// Store relative filesystem path in Metadata.
  1410  			meta.filePath = name
  1411  			if meta.Path == "" {
  1412  				// If no Path, canonical path is actual path.
  1413  				meta.Path = meta.filePath
  1414  			}
  1415  			// Store under both paths.
  1416  			metadata[meta.Path] = &meta
  1417  			metadata[meta.filePath] = &meta
  1418  		}
  1419  	}
  1420  	scan("/doc")
  1421  	docMetadata.set(metadata)
  1422  }
  1423  
  1424  // Send a value on this channel to trigger a metadata refresh.
  1425  // It is buffered so that if a signal is not lost if sent during a refresh.
  1426  //
  1427  var refreshMetadataSignal = make(chan bool, 1)
  1428  
  1429  // refreshMetadata sends a signal to update docMetadata. If a refresh is in
  1430  // progress the metadata will be refreshed again afterward.
  1431  //
  1432  func refreshMetadata() {
  1433  	select {
  1434  	case refreshMetadataSignal <- true:
  1435  	default:
  1436  	}
  1437  }
  1438  
  1439  // refreshMetadataLoop runs forever, updating docMetadata when the underlying
  1440  // file system changes. It should be launched in a goroutine by main.
  1441  //
  1442  func refreshMetadataLoop() {
  1443  	for {
  1444  		<-refreshMetadataSignal
  1445  		updateMetadata()
  1446  		time.Sleep(10 * time.Second) // at most once every 10 seconds
  1447  	}
  1448  }
  1449  
  1450  // metadataFor returns the *Metadata for a given relative path or nil if none
  1451  // exists.
  1452  //
  1453  func metadataFor(relpath string) *Metadata {
  1454  	if m, _ := docMetadata.get(); m != nil {
  1455  		meta := m.(map[string]*Metadata)
  1456  		// If metadata for this relpath exists, return it.
  1457  		if p := meta[relpath]; p != nil {
  1458  			return p
  1459  		}
  1460  		// Try with or without trailing slash.
  1461  		if strings.HasSuffix(relpath, "/") {
  1462  			relpath = relpath[:len(relpath)-1]
  1463  		} else {
  1464  			relpath = relpath + "/"
  1465  		}
  1466  		return meta[relpath]
  1467  	}
  1468  	return nil
  1469  }
  1470  
  1471  // ----------------------------------------------------------------------------
  1472  // Indexer
  1473  
  1474  // invalidateIndex should be called whenever any of the file systems
  1475  // under godoc's observation change so that the indexer is kicked on.
  1476  //
  1477  func invalidateIndex() {
  1478  	fsModified.set(nil)
  1479  	refreshMetadata()
  1480  }
  1481  
  1482  // indexUpToDate() returns true if the search index is not older
  1483  // than any of the file systems under godoc's observation.
  1484  //
  1485  func indexUpToDate() bool {
  1486  	_, fsTime := fsModified.get()
  1487  	_, siTime := searchIndex.get()
  1488  	return !fsTime.After(siTime)
  1489  }
  1490  
  1491  // feedDirnames feeds the directory names of all directories
  1492  // under the file system given by root to channel c.
  1493  //
  1494  func feedDirnames(root *RWValue, c chan<- string) {
  1495  	if dir, _ := root.get(); dir != nil {
  1496  		for d := range dir.(*Directory).iter(false) {
  1497  			c <- d.Path
  1498  		}
  1499  	}
  1500  }
  1501  
  1502  // fsDirnames() returns a channel sending all directory names
  1503  // of all the file systems under godoc's observation.
  1504  //
  1505  func fsDirnames() <-chan string {
  1506  	c := make(chan string, 256) // buffered for fewer context switches
  1507  	go func() {
  1508  		feedDirnames(&fsTree, c)
  1509  		close(c)
  1510  	}()
  1511  	return c
  1512  }
  1513  
  1514  func readIndex(filenames string) error {
  1515  	matches, err := filepath.Glob(filenames)
  1516  	if err != nil {
  1517  		return err
  1518  	} else if matches == nil {
  1519  		return fmt.Errorf("no index files match %q", filenames)
  1520  	}
  1521  	sort.Strings(matches) // make sure files are in the right order
  1522  	files := make([]io.Reader, 0, len(matches))
  1523  	for _, filename := range matches {
  1524  		f, err := os.Open(filename)
  1525  		if err != nil {
  1526  			return err
  1527  		}
  1528  		defer f.Close()
  1529  		files = append(files, f)
  1530  	}
  1531  	x := new(Index)
  1532  	if err := x.Read(io.MultiReader(files...)); err != nil {
  1533  		return err
  1534  	}
  1535  	searchIndex.set(x)
  1536  	return nil
  1537  }
  1538  
  1539  func updateIndex() {
  1540  	if *verbose {
  1541  		log.Printf("updating index...")
  1542  	}
  1543  	start := time.Now()
  1544  	index := NewIndex(fsDirnames(), *maxResults > 0, *indexThrottle)
  1545  	stop := time.Now()
  1546  	searchIndex.set(index)
  1547  	if *verbose {
  1548  		secs := stop.Sub(start).Seconds()
  1549  		stats := index.Stats()
  1550  		log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)",
  1551  			secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots)
  1552  	}
  1553  	memstats := new(runtime.MemStats)
  1554  	runtime.ReadMemStats(memstats)
  1555  	log.Printf("before GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
  1556  	runtime.GC()
  1557  	runtime.ReadMemStats(memstats)
  1558  	log.Printf("after  GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
  1559  }
  1560  
  1561  func indexer() {
  1562  	// initialize the index from disk if possible
  1563  	if *indexFiles != "" {
  1564  		if err := readIndex(*indexFiles); err != nil {
  1565  			log.Printf("error reading index: %s", err)
  1566  		}
  1567  	}
  1568  
  1569  	// repeatedly update the index when it goes out of date
  1570  	for {
  1571  		if !indexUpToDate() {
  1572  			// index possibly out of date - make a new one
  1573  			updateIndex()
  1574  		}
  1575  		delay := 60 * time.Second // by default, try every 60s
  1576  		if *testDir != "" {
  1577  			// in test mode, try once a second for fast startup
  1578  			delay = 1 * time.Second
  1579  		}
  1580  		time.Sleep(delay)
  1581  	}
  1582  }