From: Andrew Gerrand
-
-{{range .}}
-
diff --git a/lib/godoc/dirlist.html b/lib/godoc/dirlist.html
deleted file mode 100644
index a3e1a2fa88..0000000000
--- a/lib/godoc/dirlist.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- {{$name_html := html .Name}}
-
-{{end}}
-{{$name_html}}
-
- {{html .Title}}
-
-
-
-
-File
-
- Bytes
-
- Modified
-
-
-{{range .}}
-..
-
- {{$name_html := fileInfoName . | html}}
-
-{{end}}
-
-{{$name_html}}
-
- {{html .Size}}
-
- {{fileInfoTime . | html}}
-
-{{html .}} -
diff --git a/lib/godoc/example.html b/lib/godoc/example.html deleted file mode 100644 index cda2a8491e..0000000000 --- a/lib/godoc/example.html +++ /dev/null @@ -1,28 +0,0 @@ -â¹ Example{{example_suffix .Name}}
-{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{end}} - {{end}} - {{with .Vars}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{end}} - {{end}} - {{range .Funcs}} - {{/* Name is a string - no need for FSet */}} - {{$name_html := html .Name}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{example_html $ .Name}} - {{end}} - {{range .Types}} - {{$tname := .Name}} - {{$tname_html := html .Name}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - - {{range .Consts}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{end}} - - {{range .Vars}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{end}} - - {{example_html $ $tname}} - - {{range .Funcs}} - {{$name_html := html .Name}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{example_html $ .Name}} - {{end}} - - {{range .Methods}} - {{$name_html := html .Name}} -
{{node_html $ .Decl true}}- {{comment_html .Doc}} - {{$name := printf "%s_%s" $tname .Name}} - {{example_html $ $name}} - {{end}} - {{end}} - {{end}} - - {{with $.Notes}} - {{range $marker, $content := .}} -
{{node_html $ . false}}-{{end}} - -{{with .Dirs}} - {{/* DirList entries are numbers and strings - no need for FSet */}} - {{if $.PDoc}} -
Name | -- | Synopsis | -
---|---|---|
.. | -||
{{html .Path}} | -- | {{html .Synopsis}} | -
{{repeat ` ` .Depth}}{{html .Name}} | -- | {{html .Synopsis}} | -
Need more packages? Take a look at the Go Projects wiki page.
- {{end}} -{{end}} diff --git a/lib/godoc/package.txt b/lib/godoc/package.txt deleted file mode 100644 index d191621c00..0000000000 --- a/lib/godoc/package.txt +++ /dev/null @@ -1,80 +0,0 @@ -{{with .PAst}}{{node $ .}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with .PDoc}}{{if $.IsMain}}COMMAND DOCUMENTATION - -{{comment_text .Doc " " "\t"}} -{{else}}PACKAGE DOCUMENTATION - -package {{.Name}} - import "{{.ImportPath}}" - -{{comment_text .Doc " " "\t"}} -{{example_text $ "" " "}}{{/* - ---------------------------------------- - -*/}}{{with .Consts}} -CONSTANTS - -{{range .}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{end}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with .Vars}} -VARIABLES - -{{range .}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{end}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with .Funcs}} -FUNCTIONS - -{{range .}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{example_text $ .Name " "}}{{end}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with .Types}} -TYPES - -{{range .}}{{$tname := .Name}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{range .Consts}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{end}}{{range .Vars}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{end}}{{example_text $ .Name " "}} -{{range .Funcs}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{example_text $ .Name " "}} -{{end}}{{range .Methods}}{{node $ .Decl}} -{{comment_text .Doc " " "\t"}} -{{$name := printf "%s_%s" $tname .Name}}{{example_text $ $name " "}}{{end}} -{{end}}{{end}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with $.Notes}} -{{range $marker, $content := .}} -{{$marker}}S - -{{range $content}}{{comment_text .Body " " "\t"}} -{{end}}{{end}}{{end}}{{end}}{{/* - ---------------------------------------- - -*/}}{{with .Dirs}} -SUBDIRECTORIES -{{if $.DirFlat}}{{range .List}}{{if .HasPkg}} - {{.Path}}{{end}}{{end}} -{{else}}{{range .List}} - {{repeat `. ` .Depth}}{{.Name}}{{end}} -{{end}}{{end}} diff --git a/lib/godoc/search.html b/lib/godoc/search.html deleted file mode 100644 index 5b54d71267..0000000000 --- a/lib/godoc/search.html +++ /dev/null @@ -1,109 +0,0 @@ - -{{$query_url := urlquery .Query}} -{{with .Alert}} -- {{html .}} -
-{{end}} -{{with .Alt}} -- Did you mean: - {{range .Alts}} - {{html .}} - {{end}} -
-{{end}} -{{with .Pak}} --
{{$pkg_html}} |
- | {{index . 0 | infoKind_html}} | -- | - {{range .}} - {{infoLine .}} - {{end}} - | -
---|
- Not all files or lines containing "{{html $.Query}}" are shown. -
- {{end}} --
- {{$src_html}}: - | -- | {{len .Lines}} | -- | - {{range .Lines}} - {{html .}} - {{end}} - {{if not $.Complete}} - ... - {{end}} - | -
---|---|---|---|---|
... |
`) - template.HTMLEscape(w, data[0:mark]) - io.WriteString(w, "") - template.HTMLEscape(w, data[mark:lo]) - if lo < hi { - io.WriteString(w, "") -} - -// addrToByte evaluates the given address starting at offset start in data. -// It returns the lo and hi byte offset of the matched region within data. -// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II -// for details on the syntax. -func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) { - var ( - dir byte - prevc byte - charOffset bool - ) - lo = start - hi = start - for addr != "" && err == nil { - c := addr[0] - switch c { - default: - err = errors.New("invalid address syntax near " + string(c)) - case ',': - if len(addr) == 1 { - hi = len(data) - } else { - _, hi, err = addrToByteRange(addr[1:], hi, data) - } - return - - case '+', '-': - if prevc == '+' || prevc == '-' { - lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset) - } - dir = c - - case '$': - lo = len(data) - hi = len(data) - if len(addr) > 1 { - dir = '+' - } - - case '#': - charOffset = true - - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - var i int - for i = 1; i < len(addr); i++ { - if addr[i] < '0' || addr[i] > '9' { - break - } - } - var n int - n, err = strconv.Atoi(addr[0:i]) - if err != nil { - break - } - lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset) - dir = 0 - charOffset = false - prevc = c - addr = addr[i:] - continue - - case '/': - var i, j int - Regexp: - for i = 1; i < len(addr); i++ { - switch addr[i] { - case '\\': - i++ - case '/': - j = i + 1 - break Regexp - } - } - if j == 0 { - j = i - } - pattern := addr[1:i] - lo, hi, err = addrRegexp(data, lo, hi, dir, pattern) - prevc = c - addr = addr[j:] - continue - } - prevc = c - addr = addr[1:] - } - - if err == nil && dir != 0 { - lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset) - } - if err != nil { - return 0, 0, err - } - return lo, hi, nil -} - -// addrNumber applies the given dir, n, and charOffset to the address lo, hi. -// dir is '+' or '-', n is the count, and charOffset is true if the syntax -// used was #n. Applying +n (or +#n) means to advance n lines -// (or characters) after hi. Applying -n (or -#n) means to back up n lines -// (or characters) before lo. -// The return value is the new lo, hi. -func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) { - switch dir { - case 0: - lo = 0 - hi = 0 - fallthrough - - case '+': - if charOffset { - pos := hi - for ; n > 0 && pos < len(data); n-- { - _, size := utf8.DecodeRune(data[pos:]) - pos += size - } - if n == 0 { - return pos, pos, nil - } - break - } - // find next beginning of line - if hi > 0 { - for hi < len(data) && data[hi-1] != '\n' { - hi++ - } - } - lo = hi - if n == 0 { - return lo, hi, nil - } - for ; hi < len(data); hi++ { - if data[hi] != '\n' { - continue - } - switch n--; n { - case 1: - lo = hi + 1 - case 0: - return lo, hi + 1, nil - } - } - - case '-': - if charOffset { - // Scan backward for bytes that are not UTF-8 continuation bytes. - pos := lo - for ; pos > 0 && n > 0; pos-- { - if data[pos]&0xc0 != 0x80 { - n-- - } - } - if n == 0 { - return pos, pos, nil - } - break - } - // find earlier beginning of line - for lo > 0 && data[lo-1] != '\n' { - lo-- - } - hi = lo - if n == 0 { - return lo, hi, nil - } - for ; lo >= 0; lo-- { - if lo > 0 && data[lo-1] != '\n' { - continue - } - switch n--; n { - case 1: - hi = lo - case 0: - return lo, hi, nil - } - } - } - - return 0, 0, errors.New("address out of range") -} - -// addrRegexp searches for pattern in the given direction starting at lo, hi. -// The direction dir is '+' (search forward from hi) or '-' (search backward from lo). -// Backward searches are unimplemented. -func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) { - re, err := regexp.Compile(pattern) - if err != nil { - return 0, 0, err - } - if dir == '-' { - // Could implement reverse search using binary search - // through file, but that seems like overkill. - return 0, 0, errors.New("reverse search not implemented") - } - m := re.FindIndex(data[hi:]) - if len(m) > 0 { - m[0] += hi - m[1] += hi - } else if hi > 0 { - // No match. Wrap to beginning of data. - m = re.FindIndex(data) - } - if len(m) == 0 { - return 0, 0, errors.New("no match for " + pattern) - } - return m[0], m[1], nil -} - -// lineToByte returns the byte index of the first byte of line n. -// Line numbers begin at 1. -func lineToByte(data []byte, n int) int { - if n <= 1 { - return 0 - } - n-- - for i, c := range data { - if c == '\n' { - if n--; n == 0 { - return i + 1 - } - } - } - return len(data) -} - -// byteToLine returns the number of the line containing the byte at index i. -func byteToLine(data []byte, i int) int { - l := 1 - for j, c := range data { - if j == i { - return l - } - if c == '\n' { - l++ - } - } - return l -} diff --git a/src/cmd/godoc/dirtrees.go b/src/cmd/godoc/dirtrees.go deleted file mode 100644 index fda7adce52..0000000000 --- a/src/cmd/godoc/dirtrees.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains the code dealing with package directory trees. - -package main - -import ( - "bytes" - "go/doc" - "go/parser" - "go/token" - "log" - "os" - pathpkg "path" - "strings" -) - -// Conventional name for directories containing test data. -// Excluded from directory trees. -// -const testdataDirName = "testdata" - -type Directory struct { - Depth int - Path string // directory path; includes Name - Name string // directory name - HasPkg bool // true if the directory contains at least one package - Synopsis string // package documentation, if any - Dirs []*Directory // subdirectories -} - -func isGoFile(fi os.FileInfo) bool { - name := fi.Name() - return !fi.IsDir() && - len(name) > 0 && name[0] != '.' && // ignore .files - pathpkg.Ext(name) == ".go" -} - -func isPkgFile(fi os.FileInfo) bool { - return isGoFile(fi) && - !strings.HasSuffix(fi.Name(), "_test.go") // ignore test files -} - -func isPkgDir(fi os.FileInfo) bool { - name := fi.Name() - return fi.IsDir() && len(name) > 0 && - name[0] != '_' && name[0] != '.' // ignore _files and .files -} - -type treeBuilder struct { - maxDepth int -} - -func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory { - if name == testdataDirName { - return nil - } - - if depth >= b.maxDepth { - // return a dummy directory so that the parent directory - // doesn't get discarded just because we reached the max - // directory depth - return &Directory{ - Depth: depth, - Path: path, - Name: name, - } - } - - list, _ := fs.ReadDir(path) - - // determine number of subdirectories and if there are package files - ndirs := 0 - hasPkgFiles := false - var synopses [3]string // prioritized package documentation (0 == highest priority) - for _, d := range list { - switch { - case isPkgDir(d): - ndirs++ - case isPkgFile(d): - // looks like a package file, but may just be a file ending in ".go"; - // don't just count it yet (otherwise we may end up with hasPkgFiles even - // though the directory doesn't contain any real package files - was bug) - if synopses[0] == "" { - // no "optimal" package synopsis yet; continue to collect synopses - file, err := parseFile(fset, pathpkg.Join(path, d.Name()), - parser.ParseComments|parser.PackageClauseOnly) - if err == nil { - hasPkgFiles = true - if file.Doc != nil { - // prioritize documentation - i := -1 - switch file.Name.Name { - case name: - i = 0 // normal case: directory name matches package name - case "main": - i = 1 // directory contains a main package - default: - i = 2 // none of the above - } - if 0 <= i && i < len(synopses) && synopses[i] == "" { - synopses[i] = doc.Synopsis(file.Doc.Text()) - } - } - } - } - } - } - - // create subdirectory tree - var dirs []*Directory - if ndirs > 0 { - dirs = make([]*Directory, ndirs) - i := 0 - for _, d := range list { - if isPkgDir(d) { - name := d.Name() - dd := b.newDirTree(fset, pathpkg.Join(path, name), name, depth+1) - if dd != nil { - dirs[i] = dd - i++ - } - } - } - dirs = dirs[0:i] - } - - // if there are no package files and no subdirectories - // containing package files, ignore the directory - if !hasPkgFiles && len(dirs) == 0 { - return nil - } - - // select the highest-priority synopsis for the directory entry, if any - synopsis := "" - for _, synopsis = range synopses { - if synopsis != "" { - break - } - } - - return &Directory{ - Depth: depth, - Path: path, - Name: name, - HasPkg: hasPkgFiles, - Synopsis: synopsis, - Dirs: dirs, - } -} - -// newDirectory creates a new package directory tree with at most maxDepth -// levels, anchored at root. The result tree is pruned such that it only -// contains directories that contain package files or that contain -// subdirectories containing package files (transitively). If a non-nil -// pathFilter is provided, directory paths additionally must be accepted -// by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is -// provided for maxDepth, nodes at larger depths are pruned as well; they -// are assumed to contain package files even if their contents are not known -// (i.e., in this case the tree may contain directories w/o any package files). -// -func newDirectory(root string, maxDepth int) *Directory { - // The root could be a symbolic link so use Stat not Lstat. - d, err := fs.Stat(root) - // If we fail here, report detailed error messages; otherwise - // is is hard to see why a directory tree was not built. - switch { - case err != nil: - log.Printf("newDirectory(%s): %s", root, err) - return nil - case !isPkgDir(d): - log.Printf("newDirectory(%s): not a package directory", root) - return nil - } - if maxDepth < 0 { - maxDepth = 1e6 // "infinity" - } - b := treeBuilder{maxDepth} - // the file set provided is only for local parsing, no position - // information escapes and thus we don't need to save the set - return b.newDirTree(token.NewFileSet(), root, d.Name(), 0) -} - -func (dir *Directory) writeLeafs(buf *bytes.Buffer) { - if dir != nil { - if len(dir.Dirs) == 0 { - buf.WriteString(dir.Path) - buf.WriteByte('\n') - return - } - - for _, d := range dir.Dirs { - d.writeLeafs(buf) - } - } -} - -func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) { - if dir != nil { - if !skipRoot { - c <- dir - } - for _, d := range dir.Dirs { - d.walk(c, false) - } - } -} - -func (dir *Directory) iter(skipRoot bool) <-chan *Directory { - c := make(chan *Directory) - go func() { - dir.walk(c, skipRoot) - close(c) - }() - return c -} - -func (dir *Directory) lookupLocal(name string) *Directory { - for _, d := range dir.Dirs { - if d.Name == name { - return d - } - } - return nil -} - -func splitPath(p string) []string { - p = strings.TrimPrefix(p, "/") - if p == "" { - return nil - } - return strings.Split(p, "/") -} - -// lookup looks for the *Directory for a given path, relative to dir. -func (dir *Directory) lookup(path string) *Directory { - d := splitPath(dir.Path) - p := splitPath(path) - i := 0 - for i < len(d) { - if i >= len(p) || d[i] != p[i] { - return nil - } - i++ - } - for dir != nil && i < len(p) { - dir = dir.lookupLocal(p[i]) - i++ - } - return dir -} - -// DirEntry describes a directory entry. The Depth and Height values -// are useful for presenting an entry in an indented fashion. -// -type DirEntry struct { - Depth int // >= 0 - Height int // = DirList.MaxHeight - Depth, > 0 - Path string // directory path; includes Name, relative to DirList root - Name string // directory name - HasPkg bool // true if the directory contains at least one package - Synopsis string // package documentation, if any -} - -type DirList struct { - MaxHeight int // directory tree height, > 0 - List []DirEntry -} - -// listing creates a (linear) directory listing from a directory tree. -// If skipRoot is set, the root directory itself is excluded from the list. -// -func (root *Directory) listing(skipRoot bool) *DirList { - if root == nil { - return nil - } - - // determine number of entries n and maximum height - n := 0 - minDepth := 1 << 30 // infinity - maxDepth := 0 - for d := range root.iter(skipRoot) { - n++ - if minDepth > d.Depth { - minDepth = d.Depth - } - if maxDepth < d.Depth { - maxDepth = d.Depth - } - } - maxHeight := maxDepth - minDepth + 1 - - if n == 0 { - return nil - } - - // create list - list := make([]DirEntry, n) - i := 0 - for d := range root.iter(skipRoot) { - p := &list[i] - p.Depth = d.Depth - minDepth - p.Height = maxHeight - p.Depth - // the path is relative to root.Path - remove the root.Path - // prefix (the prefix should always be present but avoid - // crashes and check) - path := strings.TrimPrefix(d.Path, root.Path) - // remove leading separator if any - path must be relative - path = strings.TrimPrefix(path, "/") - p.Path = path - p.Name = d.Name - p.HasPkg = d.HasPkg - p.Synopsis = d.Synopsis - i++ - } - - return &DirList{maxHeight, list} -} diff --git a/src/cmd/godoc/doc.go b/src/cmd/godoc/doc.go deleted file mode 100644 index 1fa57a8b31..0000000000 --- a/src/cmd/godoc/doc.go +++ /dev/null @@ -1,135 +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. - -/* - -Godoc extracts and generates documentation for Go programs. - -It has two modes. - -Without the -http flag, it runs in command-line mode and prints plain text -documentation to standard output and exits. If both a library package and -a command with the same name exists, using the prefix cmd/ will force -documentation on the command rather than the library package. If the -src -flag is specified, godoc prints the exported interface of a package in Go -source form, or the implementation of a specific exported language entity: - - godoc fmt # documentation for package fmt - godoc fmt Printf # documentation for fmt.Printf - godoc cmd/go # force documentation for the go command - godoc -src fmt # fmt package interface in Go source form - godoc -src fmt Printf # implementation of fmt.Printf - -In command-line mode, the -q flag enables search queries against a godoc running -as a webserver. If no explicit server address is specified with the -server flag, -godoc first tries localhost:6060 and then http://golang.org. - - godoc -q Reader - godoc -q math.Sin - godoc -server=:6060 -q sin - -With the -http flag, it runs as a web server and presents the documentation as a -web page. - - godoc -http=:6060 - -Usage: - godoc [flag] package [name ...] - -The flags are: - -v - verbose mode - -q - arguments are considered search queries: a legal query is a - single identifier (such as ToLower) or a qualified identifier - (such as math.Sin). - -src - print (exported) source in command-line mode - -tabwidth=4 - width of tabs in units of spaces - -timestamps=true - show timestamps with directory listings - -index - enable identifier and full text search index - (no search box is shown if -index is not set) - -index_files="" - glob pattern specifying index files; if not empty, - the index is read from these files in sorted order - -index_throttle=0.75 - index throttle value; a value of 0 means no time is allocated - to the indexer (the indexer will never finish), a value of 1.0 - means that index creation is running at full throttle (other - goroutines may get no time while the index is built) - -links=true: - link identifiers to their declarations - -write_index=false - write index to a file; the file name must be specified with - -index_files - -maxresults=10000 - maximum number of full text search results shown - (no full text index is built if maxresults <= 0) - -notes="BUG" - regular expression matching note markers to show - (e.g., "BUG|TODO", ".*") - -html - print HTML in command-line mode - -goroot=$GOROOT - Go root directory - -http=addr - HTTP service address (e.g., '127.0.0.1:6060' or just ':6060') - -server=addr - webserver address for command line searches - -templates="" - directory containing alternate template files; if set, - the directory may provide alternative template files - for the files in $GOROOT/lib/godoc - -url=path - print to standard output the data that would be served by - an HTTP request for path - -zip="" - zip file providing the file system to serve; disabled if empty - -By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set). -This behavior can be altered by providing an alternative $GOROOT with the -goroot -flag. - -When godoc runs as a web server and -index is set, a search index is maintained. -The index is created at startup. - -The index contains both identifier and full text search information (searchable -via regular expressions). The maximum number of full text search results shown -can be set with the -maxresults flag; if set to 0, no full text results are -shown, and only an identifier index but no full text search index is created. - -The presentation mode of web pages served by godoc can be controlled with the -"m" URL parameter; it accepts a comma-separated list of flag names as value: - - all show documentation for all declarations, not just the exported ones - methods show all embedded methods, not just those of unexported anonymous fields - src show the original source code rather then the extracted documentation - text present the page in textual (command-line) form rather than HTML - flat present flat (not indented) directory listings using full paths - -For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation -for all (not just the exported) declarations of package big, in textual form (as -it would appear when using godoc from the command line: "godoc -src math/big .*"). - -By default, godoc serves files from the file system of the underlying OS. -Instead, a .zip file may be provided via the -zip flag, which contains -the file system to serve. The file paths stored in the .zip file must use -slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot) -must be set to the .zip file directory path containing the Go root directory. -For instance, for a .zip file created by the command: - - zip go.zip $HOME/go - -one may run godoc as follows: - - godoc -http=:6060 -zip=go.zip -goroot=$HOME/go - -See "Godoc: documenting Go code" for how to write good comments for godoc: -http://golang.org/doc/articles/godoc_documenting_go_code.html - -*/ -package main diff --git a/src/cmd/godoc/filesystem.go b/src/cmd/godoc/filesystem.go deleted file mode 100644 index 0309d7cabe..0000000000 --- a/src/cmd/godoc/filesystem.go +++ /dev/null @@ -1,562 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file defines types for abstract file system access and -// provides an implementation accessing the file system of the -// underlying OS. - -package main - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - pathpkg "path" - "path/filepath" - "sort" - "strings" - "time" -) - -// 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. -// -// New file trees - implementations of FileSystem - can be added to -// the virtual file system using nameSpace's Bind method. -// The usual setup is to bind OS(runtime.GOROOT) to the root -// of the name space and then bind any GOPATH/src directories -// on top of /src/pkg, so that all sources are in /src/pkg. -// -// For more about name spaces, see the nameSpace type's -// documentation below. -// -// The use of this virtual file system means that most code processing -// paths can assume they are slash-separated and should be using -// package path (often imported as pathpkg) to manipulate them, -// even on Windows. -// -var fs = nameSpace{} // the underlying file system for godoc - -// Setting debugNS = true will enable debugging prints about -// name space translations. -const debugNS = false - -// The FileSystem interface specifies the methods godoc is using -// to access the file system for which it serves documentation. -type FileSystem interface { - Open(path string) (readSeekCloser, error) - Lstat(path string) (os.FileInfo, error) - Stat(path string) (os.FileInfo, error) - ReadDir(path string) ([]os.FileInfo, error) - String() string -} - -type readSeekCloser interface { - io.Reader - io.Seeker - io.Closer -} - -// ReadFile reads the file named by path from fs and returns the contents. -func ReadFile(fs FileSystem, path string) ([]byte, error) { - rc, err := fs.Open(path) - if err != nil { - return nil, err - } - defer rc.Close() - return ioutil.ReadAll(rc) -} - -// OS returns an implementation of FileSystem reading from the -// tree rooted at root. Recording a root is convenient everywhere -// but necessary on Windows, because the slash-separated path -// passed to Open has no way to specify a drive letter. Using a root -// lets code refer to OS(`c:\`), OS(`d:\`) and so on. -func OS(root string) FileSystem { - return osFS(root) -} - -type osFS string - -func (root osFS) String() string { return "os(" + string(root) + ")" } - -func (root osFS) resolve(path string) string { - // Clean the path so that it cannot possibly begin with ../. - // If it did, the result of filepath.Join would be outside the - // tree rooted at root. We probably won't ever see a path - // with .. in it, but be safe anyway. - path = pathpkg.Clean("/" + path) - - return filepath.Join(string(root), path) -} - -func (root osFS) Open(path string) (readSeekCloser, error) { - f, err := os.Open(root.resolve(path)) - if err != nil { - return nil, err - } - fi, err := f.Stat() - if err != nil { - return nil, err - } - if fi.IsDir() { - return nil, fmt.Errorf("Open: %s is a directory", path) - } - return f, nil -} - -func (root osFS) Lstat(path string) (os.FileInfo, error) { - return os.Lstat(root.resolve(path)) -} - -func (root osFS) Stat(path string) (os.FileInfo, error) { - return os.Stat(root.resolve(path)) -} - -func (root osFS) ReadDir(path string) ([]os.FileInfo, error) { - return ioutil.ReadDir(root.resolve(path)) // is sorted -} - -// hasPathPrefix returns true if x == y or x == y + "/" + more -func hasPathPrefix(x, y string) bool { - return x == y || strings.HasPrefix(x, y) && (strings.HasSuffix(y, "/") || strings.HasPrefix(x[len(y):], "/")) -} - -// A nameSpace is a file system made up of other file systems -// mounted at specific locations in the name space. -// -// The representation is a map from mount point locations -// to the list of file systems mounted at that location. A traditional -// Unix mount table would use a single file system per mount point, -// but we want to be able to mount multiple file systems on a single -// mount point and have the system behave as if the union of those -// file systems were present at the mount point. -// For example, if the OS file system has a Go installation in -// c:\Go and additional Go path trees in d:\Work1 and d:\Work2, then -// this name space creates the view we want for the godoc server: -// -// nameSpace{ -// "/": { -// {old: "/", fs: OS(`c:\Go`), new: "/"}, -// }, -// "/src/pkg": { -// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"}, -// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"}, -// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"}, -// }, -// } -// -// This is created by executing: -// -// ns := nameSpace{} -// ns.Bind("/", OS(`c:\Go`), "/", bindReplace) -// ns.Bind("/src/pkg", OS(`d:\Work1`), "/src", bindAfter) -// ns.Bind("/src/pkg", OS(`d:\Work2`), "/src", bindAfter) -// -// A particular mount point entry is a triple (old, fs, new), meaning that to -// operate on a path beginning with old, replace that prefix (old) with new -// and then pass that path to the FileSystem implementation fs. -// -// Given this name space, a ReadDir of /src/pkg/code will check each prefix -// of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src, -// then /), stopping when it finds one. For the above example, /src/pkg/code -// will find the mount point at /src/pkg: -// -// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"}, -// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"}, -// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"}, -// -// ReadDir will when execute these three calls and merge the results: -// -// OS(`c:\Go`).ReadDir("/src/pkg/code") -// OS(`d:\Work1').ReadDir("/src/code") -// OS(`d:\Work2').ReadDir("/src/code") -// -// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by -// just "/src" in the final two calls. -// -// OS is itself an implementation of a file system: it implements -// OS(`c:\Go`).ReadDir("/src/pkg/code") as ioutil.ReadDir(`c:\Go\src\pkg\code`). -// -// Because the new path is evaluated by fs (here OS(root)), another way -// to read the mount table is to mentally combine fs+new, so that this table: -// -// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"}, -// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"}, -// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"}, -// -// reads as: -// -// "/src/pkg" -> c:\Go\src\pkg -// "/src/pkg" -> d:\Work1\src -// "/src/pkg" -> d:\Work2\src -// -// An invariant (a redundancy) of the name space representation is that -// ns[mtpt][i].old is always equal to mtpt (in the example, ns["/src/pkg"]'s -// mount table entries always have old == "/src/pkg"). The 'old' field is -// useful to callers, because they receive just a []mountedFS and not any -// other indication of which mount point was found. -// -type nameSpace map[string][]mountedFS - -// A mountedFS handles requests for path by replacing -// a prefix 'old' with 'new' and then calling the fs methods. -type mountedFS struct { - old string - fs FileSystem - new string -} - -// translate translates path for use in m, replacing old with new. -// -// mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code". -func (m mountedFS) translate(path string) string { - path = pathpkg.Clean("/" + path) - if !hasPathPrefix(path, m.old) { - panic("translate " + path + " but old=" + m.old) - } - return pathpkg.Join(m.new, path[len(m.old):]) -} - -func (nameSpace) String() string { - return "ns" -} - -// Fprint writes a text representation of the name space to w. -func (ns nameSpace) Fprint(w io.Writer) { - fmt.Fprint(w, "name space {\n") - var all []string - for mtpt := range ns { - all = append(all, mtpt) - } - sort.Strings(all) - for _, mtpt := range all { - fmt.Fprintf(w, "\t%s:\n", mtpt) - for _, m := range ns[mtpt] { - fmt.Fprintf(w, "\t\t%s %s\n", m.fs, m.new) - } - } - fmt.Fprint(w, "}\n") -} - -// clean returns a cleaned, rooted path for evaluation. -// It canonicalizes the path so that we can use string operations -// to analyze it. -func (nameSpace) clean(path string) string { - return pathpkg.Clean("/" + path) -} - -// Bind causes references to old to redirect to the path new in newfs. -// If mode is bindReplace, old redirections are discarded. -// If mode is bindBefore, this redirection takes priority over existing ones, -// but earlier ones are still consulted for paths that do not exist in newfs. -// If mode is bindAfter, this redirection happens only after existing ones -// have been tried and failed. - -const ( - bindReplace = iota - bindBefore - bindAfter -) - -func (ns nameSpace) Bind(old string, newfs FileSystem, new string, mode int) { - old = ns.clean(old) - new = ns.clean(new) - m := mountedFS{old, newfs, new} - var mtpt []mountedFS - switch mode { - case bindReplace: - mtpt = append(mtpt, m) - case bindAfter: - mtpt = append(mtpt, ns.resolve(old)...) - mtpt = append(mtpt, m) - case bindBefore: - mtpt = append(mtpt, m) - mtpt = append(mtpt, ns.resolve(old)...) - } - - // Extend m.old, m.new in inherited mount point entries. - for i := range mtpt { - m := &mtpt[i] - if m.old != old { - if !hasPathPrefix(old, m.old) { - // This should not happen. If it does, panic so - // that we can see the call trace that led to it. - panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}", old, m.old, m.fs.String(), m.new)) - } - suffix := old[len(m.old):] - m.old = pathpkg.Join(m.old, suffix) - m.new = pathpkg.Join(m.new, suffix) - } - } - - ns[old] = mtpt -} - -// resolve resolves a path to the list of mountedFS to use for path. -func (ns nameSpace) resolve(path string) []mountedFS { - path = ns.clean(path) - for { - if m := ns[path]; m != nil { - if debugNS { - fmt.Printf("resolve %s: %v\n", path, m) - } - return m - } - if path == "/" { - break - } - path = pathpkg.Dir(path) - } - return nil -} - -// Open implements the FileSystem Open method. -func (ns nameSpace) Open(path string) (readSeekCloser, error) { - var err error - for _, m := range ns.resolve(path) { - if debugNS { - fmt.Printf("tx %s: %v\n", path, m.translate(path)) - } - r, err1 := m.fs.Open(m.translate(path)) - if err1 == nil { - return r, nil - } - if err == nil { - err = err1 - } - } - if err == nil { - err = &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist} - } - return nil, err -} - -// stat implements the FileSystem Stat and Lstat methods. -func (ns nameSpace) stat(path string, f func(FileSystem, string) (os.FileInfo, error)) (os.FileInfo, error) { - var err error - for _, m := range ns.resolve(path) { - fi, err1 := f(m.fs, m.translate(path)) - if err1 == nil { - return fi, nil - } - if err == nil { - err = err1 - } - } - if err == nil { - err = &os.PathError{Op: "stat", Path: path, Err: os.ErrNotExist} - } - return nil, err -} - -func (ns nameSpace) Stat(path string) (os.FileInfo, error) { - return ns.stat(path, FileSystem.Stat) -} - -func (ns nameSpace) Lstat(path string) (os.FileInfo, error) { - return ns.stat(path, FileSystem.Lstat) -} - -// dirInfo is a trivial implementation of os.FileInfo for a directory. -type dirInfo string - -func (d dirInfo) Name() string { return string(d) } -func (d dirInfo) Size() int64 { return 0 } -func (d dirInfo) Mode() os.FileMode { return os.ModeDir | 0555 } -func (d dirInfo) ModTime() time.Time { return startTime } -func (d dirInfo) IsDir() bool { return true } -func (d dirInfo) Sys() interface{} { return nil } - -var startTime = time.Now() - -// ReadDir implements the FileSystem ReadDir method. It's where most of the magic is. -// (The rest is in resolve.) -// -// Logically, ReadDir must return the union of all the directories that are named -// by path. In order to avoid misinterpreting Go packages, of all the directories -// that contain Go source code, we only include the files from the first, -// but we include subdirectories from all. -// -// ReadDir must also return directory entries needed to reach mount points. -// If the name space looks like the example in the type nameSpace comment, -// but c:\Go does not have a src/pkg subdirectory, we still want to be able -// to find that subdirectory, because we've mounted d:\Work1 and d:\Work2 -// there. So if we don't see "src" in the directory listing for c:\Go, we add an -// entry for it before returning. -// -func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) { - path = ns.clean(path) - - var ( - haveGo = false - haveName = map[string]bool{} - all []os.FileInfo - err error - first []os.FileInfo - ) - - for _, m := range ns.resolve(path) { - dir, err1 := m.fs.ReadDir(m.translate(path)) - if err1 != nil { - if err == nil { - err = err1 - } - continue - } - - if dir == nil { - dir = []os.FileInfo{} - } - - if first == nil { - first = dir - } - - // If we don't yet have Go files in 'all' and this directory - // has some, add all the files from this directory. - // Otherwise, only add subdirectories. - useFiles := false - if !haveGo { - for _, d := range dir { - if strings.HasSuffix(d.Name(), ".go") { - useFiles = true - haveGo = true - break - } - } - } - - for _, d := range dir { - name := d.Name() - if (d.IsDir() || useFiles) && !haveName[name] { - haveName[name] = true - all = append(all, d) - } - } - } - - // We didn't find any directories containing Go files. - // If some directory returned successfully, use that. - if !haveGo { - for _, d := range first { - if !haveName[d.Name()] { - haveName[d.Name()] = true - all = append(all, d) - } - } - } - - // Built union. Add any missing directories needed to reach mount points. - for old := range ns { - if hasPathPrefix(old, path) && old != path { - // Find next element after path in old. - elem := old[len(path):] - elem = strings.TrimPrefix(elem, "/") - if i := strings.Index(elem, "/"); i >= 0 { - elem = elem[:i] - } - if !haveName[elem] { - haveName[elem] = true - all = append(all, dirInfo(elem)) - } - } - } - - if len(all) == 0 { - return nil, err - } - - sort.Sort(byName(all)) - return all, nil -} - -// byName implements sort.Interface. -type byName []os.FileInfo - -func (f byName) Len() int { return len(f) } -func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } -func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } - -// An httpFS implements http.FileSystem using a FileSystem. -type httpFS struct { - fs FileSystem -} - -func (h *httpFS) Open(name string) (http.File, error) { - fi, err := h.fs.Stat(name) - if err != nil { - return nil, err - } - if fi.IsDir() { - return &httpDir{h.fs, name, nil}, nil - } - f, err := h.fs.Open(name) - if err != nil { - return nil, err - } - return &httpFile{h.fs, f, name}, nil -} - -// httpDir implements http.File for a directory in a FileSystem. -type httpDir struct { - fs FileSystem - name string - pending []os.FileInfo -} - -func (h *httpDir) Close() error { return nil } -func (h *httpDir) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) } -func (h *httpDir) Read([]byte) (int, error) { - return 0, fmt.Errorf("cannot Read from directory %s", h.name) -} - -func (h *httpDir) Seek(offset int64, whence int) (int64, error) { - if offset == 0 && whence == 0 { - h.pending = nil - return 0, nil - } - return 0, fmt.Errorf("unsupported Seek in directory %s", h.name) -} - -func (h *httpDir) Readdir(count int) ([]os.FileInfo, error) { - if h.pending == nil { - d, err := h.fs.ReadDir(h.name) - if err != nil { - return nil, err - } - if d == nil { - d = []os.FileInfo{} // not nil - } - h.pending = d - } - - if len(h.pending) == 0 && count > 0 { - return nil, io.EOF - } - if count <= 0 || count > len(h.pending) { - count = len(h.pending) - } - d := h.pending[:count] - h.pending = h.pending[count:] - return d, nil -} - -// httpFile implements http.File for a file (not directory) in a FileSystem. -type httpFile struct { - fs FileSystem - readSeekCloser - name string -} - -func (h *httpFile) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) } -func (h *httpFile) Readdir(int) ([]os.FileInfo, error) { - return nil, fmt.Errorf("cannot Readdir from file %s", h.name) -} diff --git a/src/cmd/godoc/format.go b/src/cmd/godoc/format.go deleted file mode 100644 index 59a89c5bf9..0000000000 --- a/src/cmd/godoc/format.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements FormatSelections and FormatText. -// FormatText is used to HTML-format Go and non-Go source -// text with line numbers and highlighted sections. It is -// built on top of FormatSelections, a generic formatter -// for "selected" text. - -package main - -import ( - "fmt" - "go/scanner" - "go/token" - "io" - "regexp" - "strconv" - "text/template" -) - -// ---------------------------------------------------------------------------- -// Implementation of FormatSelections - -// A Segment describes a text segment [start, end). -// The zero value of a Segment is a ready-to-use empty segment. -// -type Segment struct { - start, end int -} - -func (seg *Segment) isEmpty() bool { return seg.start >= seg.end } - -// A Selection is an "iterator" function returning a text segment. -// Repeated calls to a selection return consecutive, non-overlapping, -// non-empty segments, followed by an infinite sequence of empty -// segments. The first empty segment marks the end of the selection. -// -type Selection func() Segment - -// A LinkWriter writes some start or end "tag" to w for the text offset offs. -// It is called by FormatSelections at the start or end of each link segment. -// -type LinkWriter func(w io.Writer, offs int, start bool) - -// A SegmentWriter formats a text according to selections and writes it to w. -// The selections parameter is a bit set indicating which selections provided -// to FormatSelections overlap with the text segment: If the n'th bit is set -// in selections, the n'th selection provided to FormatSelections is overlapping -// with the text. -// -type SegmentWriter func(w io.Writer, text []byte, selections int) - -// FormatSelections takes a text and writes it to w using link and segment -// writers lw and sw as follows: lw is invoked for consecutive segment starts -// and ends as specified through the links selection, and sw is invoked for -// consecutive segments of text overlapped by the same selections as specified -// by selections. The link writer lw may be nil, in which case the links -// Selection is ignored. -// -func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) { - // If we have a link writer, make the links - // selection the last entry in selections - if lw != nil { - selections = append(selections, links) - } - - // compute the sequence of consecutive segment changes - changes := newMerger(selections) - - // The i'th bit in bitset indicates that the text - // at the current offset is covered by selections[i]. - bitset := 0 - lastOffs := 0 - - // Text segments are written in a delayed fashion - // such that consecutive segments belonging to the - // same selection can be combined (peephole optimization). - // last describes the last segment which has not yet been written. - var last struct { - begin, end int // valid if begin < end - bitset int - } - - // flush writes the last delayed text segment - flush := func() { - if last.begin < last.end { - sw(w, text[last.begin:last.end], last.bitset) - } - last.begin = last.end // invalidate last - } - - // segment runs the segment [lastOffs, end) with the selection - // indicated by bitset through the segment peephole optimizer. - segment := func(end int) { - if lastOffs < end { // ignore empty segments - if last.end != lastOffs || last.bitset != bitset { - // the last segment is not adjacent to or - // differs from the new one - flush() - // start a new segment - last.begin = lastOffs - } - last.end = end - last.bitset = bitset - } - } - - for { - // get the next segment change - index, offs, start := changes.next() - if index < 0 || offs > len(text) { - // no more segment changes or the next change - // is past the end of the text - we're done - break - } - // determine the kind of segment change - if lw != nil && index == len(selections)-1 { - // we have a link segment change (see start of this function): - // format the previous selection segment, write the - // link tag and start a new selection segment - segment(offs) - flush() - lastOffs = offs - lw(w, offs, start) - } else { - // we have a selection change: - // format the previous selection segment, determine - // the new selection bitset and start a new segment - segment(offs) - lastOffs = offs - mask := 1 << uint(index) - if start { - bitset |= mask - } else { - bitset &^= mask - } - } - } - segment(len(text)) - flush() -} - -// A merger merges a slice of Selections and produces a sequence of -// consecutive segment change events through repeated next() calls. -// -type merger struct { - selections []Selection - segments []Segment // segments[i] is the next segment of selections[i] -} - -const infinity int = 2e9 - -func newMerger(selections []Selection) *merger { - segments := make([]Segment, len(selections)) - for i, sel := range selections { - segments[i] = Segment{infinity, infinity} - if sel != nil { - if seg := sel(); !seg.isEmpty() { - segments[i] = seg - } - } - } - return &merger{selections, segments} -} - -// next returns the next segment change: index specifies the Selection -// to which the segment belongs, offs is the segment start or end offset -// as determined by the start value. If there are no more segment changes, -// next returns an index value < 0. -// -func (m *merger) next() (index, offs int, start bool) { - // find the next smallest offset where a segment starts or ends - offs = infinity - index = -1 - for i, seg := range m.segments { - switch { - case seg.start < offs: - offs = seg.start - index = i - start = true - case seg.end < offs: - offs = seg.end - index = i - start = false - } - } - if index < 0 { - // no offset found => all selections merged - return - } - // offset found - it's either the start or end offset but - // either way it is ok to consume the start offset: set it - // to infinity so it won't be considered in the following - // next call - m.segments[index].start = infinity - if start { - return - } - // end offset found - consume it - m.segments[index].end = infinity - // advance to the next segment for that selection - seg := m.selections[index]() - if !seg.isEmpty() { - m.segments[index] = seg - } - return -} - -// ---------------------------------------------------------------------------- -// Implementation of FormatText - -// lineSelection returns the line segments for text as a Selection. -func lineSelection(text []byte) Selection { - i, j := 0, 0 - return func() (seg Segment) { - // find next newline, if any - for j < len(text) { - j++ - if text[j-1] == '\n' { - break - } - } - if i < j { - // text[i:j] constitutes a line - seg = Segment{i, j} - i = j - } - return - } -} - -// tokenSelection returns, as a selection, the sequence of -// consecutive occurrences of token sel in the Go src text. -// -func tokenSelection(src []byte, sel token.Token) Selection { - var s scanner.Scanner - fset := token.NewFileSet() - file := fset.AddFile("", fset.Base(), len(src)) - s.Init(file, src, nil, scanner.ScanComments) - return func() (seg Segment) { - for { - pos, tok, lit := s.Scan() - if tok == token.EOF { - break - } - offs := file.Offset(pos) - if tok == sel { - seg = Segment{offs, offs + len(lit)} - break - } - } - return - } -} - -// makeSelection is a helper function to make a Selection from a slice of pairs. -// Pairs describing empty segments are ignored. -// -func makeSelection(matches [][]int) Selection { - i := 0 - return func() Segment { - for i < len(matches) { - m := matches[i] - i++ - if m[0] < m[1] { - // non-empty segment - return Segment{m[0], m[1]} - } - } - return Segment{} - } -} - -// regexpSelection computes the Selection for the regular expression expr in text. -func regexpSelection(text []byte, expr string) Selection { - var matches [][]int - if rx, err := regexp.Compile(expr); err == nil { - matches = rx.FindAllIndex(text, -1) - } - return makeSelection(matches) -} - -var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`) - -// rangeSelection computes the Selection for a text range described -// by the argument str; the range description must match the selRx -// regular expression. -// -func rangeSelection(str string) Selection { - m := selRx.FindStringSubmatch(str) - if len(m) >= 2 { - from, _ := strconv.Atoi(m[1]) - to, _ := strconv.Atoi(m[2]) - if from < to { - return makeSelection([][]int{{from, to}}) - } - } - return nil -} - -// Span tags for all the possible selection combinations that may -// be generated by FormatText. Selections are indicated by a bitset, -// and the value of the bitset specifies the tag to be used. -// -// bit 0: comments -// bit 1: highlights -// bit 2: selections -// -var startTags = [][]byte{ - /* 000 */ []byte(``), - /* 001 */ []byte(``), - /* 010 */ []byte(``), - /* 011 */ []byte(``), - /* 100 */ []byte(``), - /* 101 */ []byte(``), - /* 110 */ []byte(``), - /* 111 */ []byte(``), -} - -var endTag = []byte(``) - -func selectionTag(w io.Writer, text []byte, selections int) { - if selections < len(startTags) { - if tag := startTags[selections]; len(tag) > 0 { - w.Write(tag) - template.HTMLEscape(w, text) - w.Write(endTag) - return - } - } - template.HTMLEscape(w, text) -} - -// FormatText HTML-escapes text and writes it to w. -// Consecutive text segments are wrapped in HTML spans (with tags as -// defined by startTags and endTag) as follows: -// -// - if line >= 0, line number (ln) spans are inserted before each line, -// starting with the value of line -// - if the text is Go source, comments get the "comment" span class -// - each occurrence of the regular expression pattern gets the "highlight" -// span class -// - text segments covered by selection get the "selection" span class -// -// Comments, highlights, and selections may overlap arbitrarily; the respective -// HTML span classes are specified in the startTags variable. -// -func FormatText(w io.Writer, text []byte, line int, goSource bool, pattern string, selection Selection) { - var comments, highlights Selection - if goSource { - comments = tokenSelection(text, token.COMMENT) - } - if pattern != "" { - highlights = regexpSelection(text, pattern) - } - if line >= 0 || comments != nil || highlights != nil || selection != nil { - var lineTag LinkWriter - if line >= 0 { - lineTag = func(w io.Writer, _ int, start bool) { - if start { - fmt.Fprintf(w, "%6d\t", line, line) - line++ - } - } - } - FormatSelections(w, text, lineTag, lineSelection(text), selectionTag, comments, highlights, selection) - } else { - template.HTMLEscape(w, text) - } -} diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go deleted file mode 100644 index 79d485b93d..0000000000 --- a/src/cmd/godoc/godoc.go +++ /dev/null @@ -1,1586 +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. - -package main - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "go/ast" - "go/build" - "go/doc" - "go/format" - "go/printer" - "go/token" - htmlpkg "html" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - pathpkg "path" - "path/filepath" - "regexp" - "runtime" - "sort" - "strings" - "text/template" - "time" - "unicode" - "unicode/utf8" -) - -// ---------------------------------------------------------------------------- -// Globals - -type delayTime struct { - RWValue -} - -func (dt *delayTime) backoff(max time.Duration) { - dt.mutex.Lock() - v := dt.value.(time.Duration) * 2 - if v > max { - v = max - } - dt.value = v - // don't change dt.timestamp - calling backoff indicates an error condition - dt.mutex.Unlock() -} - -var ( - verbose = flag.Bool("v", false, "verbose mode") - - // file system roots - // TODO(gri) consider the invariant that goroot always end in '/' - goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") - testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)") - - // layout control - tabwidth = flag.Int("tabwidth", 4, "tab width") - showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") - 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") - indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+ - "if not empty, the index is read from these files in sorted order") - maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") - indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") - - // file system information - fsTree RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now) - fsModified RWValue // timestamp of last call to invalidateIndex - docMetadata RWValue // mapping from paths to *Metadata - - // http handlers - fileServer http.Handler // default file server - cmdHandler docServer - pkgHandler docServer - - // source code notes - notes = flag.String("notes", "BUG", "regular expression matching note markers to show") -) - -func initHandlers() { - fileServer = http.FileServer(&httpFS{fs}) - cmdHandler = docServer{"/cmd/", "/src/cmd"} - pkgHandler = docServer{"/pkg/", "/src/pkg"} -} - -func registerPublicHandlers(mux *http.ServeMux) { - mux.Handle(cmdHandler.pattern, &cmdHandler) - mux.Handle(pkgHandler.pattern, &pkgHandler) - mux.HandleFunc("/doc/codewalk/", codewalk) - mux.Handle("/doc/play/", fileServer) - mux.HandleFunc("/search", search) - mux.Handle("/robots.txt", fileServer) - mux.HandleFunc("/opensearch.xml", serveSearchDesc) - mux.HandleFunc("/", serveFile) -} - -func initFSTree() { - dir := newDirectory(pathpkg.Join("/", *testDir), -1) - if dir == nil { - log.Println("Warning: FSTree is nil") - return - } - fsTree.set(dir) - invalidateIndex() -} - -// ---------------------------------------------------------------------------- -// Tab conversion - -var spaces = []byte(" ") // 32 spaces seems like a good number - -const ( - indenting = iota - collecting -) - -// A tconv is an io.Writer filter for converting leading tabs into spaces. -type tconv struct { - output io.Writer - state int // indenting or collecting - indent int // valid if state == indenting -} - -func (p *tconv) writeIndent() (err error) { - i := p.indent - for i >= len(spaces) { - i -= len(spaces) - if _, err = p.output.Write(spaces); err != nil { - return - } - } - // i < len(spaces) - if i > 0 { - _, err = p.output.Write(spaces[0:i]) - } - return -} - -func (p *tconv) Write(data []byte) (n int, err error) { - if len(data) == 0 { - return - } - pos := 0 // valid if p.state == collecting - var b byte - for n, b = range data { - switch p.state { - case indenting: - switch b { - case '\t': - p.indent += *tabwidth - case '\n': - p.indent = 0 - if _, err = p.output.Write(data[n : n+1]); err != nil { - return - } - case ' ': - p.indent++ - default: - p.state = collecting - pos = n - if err = p.writeIndent(); err != nil { - return - } - } - case collecting: - if b == '\n' { - p.state = indenting - p.indent = 0 - if _, err = p.output.Write(data[pos : n+1]); err != nil { - return - } - } - } - } - n = len(data) - if pos < n && p.state == collecting { - _, err = p.output.Write(data[pos:]) - } - return -} - -// ---------------------------------------------------------------------------- -// Templates - -// 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) - } -} - -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 `no snippet text available` -} - -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() -} - -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 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 = "" - } - - 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 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 -} - -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) -} - -// fmap describes the template functions installed with all 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 fmap = 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 readTemplate(name string) *template.Template { - path := "lib/godoc/" + name - - // use underlying file system fs to read the template file - // (cannot use template ParseFile functions directly) - data, err := ReadFile(fs, path) - if err != nil { - log.Fatal("readTemplate: ", err) - } - // be explicit with errors (for app engine use) - t, err := template.New(name).Funcs(fmap).Parse(string(data)) - if err != nil { - log.Fatal("readTemplate: ", err) - } - return t -} - -var ( - codewalkHTML, - codewalkdirHTML, - dirlistHTML, - errorHTML, - exampleHTML, - godocHTML, - packageHTML, - packageText, - searchHTML, - searchText, - searchDescXML *template.Template -) - -func readTemplates() { - // have to delay until after flags processing since paths depend on goroot - codewalkHTML = readTemplate("codewalk.html") - codewalkdirHTML = readTemplate("codewalkdir.html") - dirlistHTML = readTemplate("dirlist.html") - errorHTML = readTemplate("error.html") - exampleHTML = readTemplate("example.html") - godocHTML = readTemplate("godoc.html") - packageHTML = readTemplate("package.html") - packageText = readTemplate("package.txt") - searchHTML = readTemplate("search.html") - searchText = readTemplate("search.txt") - searchDescXML = readTemplate("opensearch.xml") -} - -// ---------------------------------------------------------------------------- -// Generic HTML wrapper - -// Page describes the contents of the top-level godoc webpage. -type Page struct { - Title string - Tabtitle string - Subtitle string - Query string - Body []byte - - // filled in by servePage - SearchBox bool - Playground bool - Version string -} - -func servePage(w http.ResponseWriter, page Page) { - if page.Tabtitle == "" { - page.Tabtitle = page.Title - } - page.SearchBox = *indexEnabled - page.Playground = *showPlayground - page.Version = runtime.Version() - if err := godocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed { - // Only log if there's an error that's not about writing on HEAD requests. - // See Issues 5451 and 5454. - log.Printf("godocHTML.Execute: %s", err) - } -} - -func serveText(w http.ResponseWriter, text []byte) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Write(text) -} - -// ---------------------------------------------------------------------------- -// Files - -var ( - doctype = []byte("") -) - -func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { - // get HTML body contents - src, err := ReadFile(fs, abspath) - if err != nil { - log.Printf("ReadFile: %s", err) - serveError(w, r, relpath, err) - return - } - - // if it begins with "") - FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s"))) - buf.WriteString("") - fmt.Fprintf(&buf, ``, htmlpkg.EscapeString(relpath)) - - servePage(w, Page{ - Title: title + " " + relpath, - Tabtitle: relpath, - Body: buf.Bytes(), - }) -} - -func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) { - if redirect(w, r) { - return - } - - list, err := fs.ReadDir(abspath) - if err != nil { - serveError(w, r, relpath, err) - return - } - - servePage(w, Page{ - Title: "Directory " + relpath, - Tabtitle: relpath, - Body: applyTemplate(dirlistHTML, "dirlistHTML", list), - }) -} - -func 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 := 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 - } - serveHTMLDoc(w, r, abspath, relpath) - return - - case ".go": - serveTextFile(w, r, abspath, relpath, "Source file") - return - } - - dir, err := fs.Lstat(abspath) - if err != nil { - log.Print(err) - serveError(w, r, relpath, err) - return - } - - if dir != nil && dir.IsDir() { - if redirect(w, r) { - return - } - if index := pathpkg.Join(abspath, "index.html"); isTextFile(index) { - serveHTMLDoc(w, r, index, index) - return - } - serveDirectory(w, r, abspath, relpath) - return - } - - if isTextFile(abspath) { - if redirectFile(w, r) { - return - } - serveTextFile(w, r, abspath, relpath, "Text file") - return - } - - fileServer.ServeHTTP(w, r) -} - -func serveSearchDesc(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/opensearchdescription+xml") - data := map[string]interface{}{ - "BaseURL": fmt.Sprintf("http://%s", r.Host), - } - if err := searchDescXML.Execute(w, &data); err != nil && err != http.ErrBodyNotAllowed { - // Only log if there's an error that's not about writing on HEAD requests. - // See Issues 5451 and 5454. - log.Printf("searchDescXML.Execute: %s", err) - } -} - -// ---------------------------------------------------------------------------- -// Packages - -// 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" - -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 -) - -// 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 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 - } - } - return adjustPageInfoMode(r, mode) -} - -// Specialized versions of godoc may adjust the PageInfoMode by overriding -// this variable. -var adjustPageInfoMode = func(_ *http.Request, mode PageInfoMode) PageInfoMode { - return mode -} - -// remoteSearchURL returns the search URL for a given query as needed by -// remoteSearch. If html is set, an html result is requested; otherwise -// the result is in textual form. -// Adjust this function as necessary if modeNames or FormValue parameters -// change. -func remoteSearchURL(query string, html bool) string { - s := "/search?m=text&q=" - if html { - s = "/search?q=" - } - return s + url.QueryEscape(query) -} - -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 -} - -type docServer struct { - pattern string // url pattern; e.g. "/pkg/" - fsRoot string // file system root to which the pattern is mapped -} - -// fsReadDir implements ReadDir for the go/build package. -func fsReadDir(dir string) ([]os.FileInfo, error) { - return fs.ReadDir(filepath.ToSlash(dir)) -} - -// fsOpenFile implements OpenFile for the go/build package. -func fsOpenFile(name string) (r io.ReadCloser, err error) { - data, err := ReadFile(fs, filepath.ToSlash(name)) - if err != nil { - return nil, err - } - return ioutil.NopCloser(bytes.NewReader(data)), nil -} - -// 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() - } -} - -// 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.ValueSpec: - for _, id := range s.Names { - names[id.Name] = true - } - } - } - } -} - -// 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(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 { - log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name) - } - } - - 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) -// 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 *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 - // separate implementations for, say, Windows vs Unix, we don't - // jumble them all together. - // Note: Uses current binary's GOOS/GOARCH. - // To use different pair, such as if we allowed the user to choose, - // set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir. - ctxt := build.Default - ctxt.IsAbsPath = pathpkg.IsAbs - ctxt.ReadDir = fsReadDir - ctxt.OpenFile = fsOpenFile - 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 := parseFiles(fset, 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 - - // collect examples - testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...) - files, err = parseFiles(fset, abspath, testfiles) - if err != nil { - log.Println("parsing examples:", err) - } - info.Examples = collectExamples(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, 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 - } - } - } - } - - } 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 = ast.MergePackageFiles(pkg, 0) - } - info.IsMain = pkgname == "main" - } - - // get directory information, if any - var dir *Directory - var timestamp time.Time - if tree, ts := 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 = newDirectory(abspath, 1) - timestamp = time.Now() - } - info.Dirs = dir.listing(true) - info.DirTime = timestamp - info.DirFlat = mode&flatDir != 0 - - return info -} - -func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if redirect(w, r) { - return - } - - relpath := pathpkg.Clean(r.URL.Path[len(h.pattern):]) - abspath := pathpkg.Join(h.fsRoot, relpath) - mode := getPageInfoMode(r) - if relpath == builtinPkgPath { - mode = noFiltering - } - info := h.getPageInfo(abspath, relpath, mode) - if info.Err != nil { - log.Print(info.Err) - serveError(w, r, relpath, info.Err) - return - } - - if mode&noHtml != 0 { - serveText(w, applyTemplate(packageText, "packageText", info)) - return - } - - var tabtitle, title, subtitle string - switch { - case info.PAst != nil: - tabtitle = info.PAst.Name.Name - case info.PDoc != nil: - tabtitle = info.PDoc.Name - default: - tabtitle = info.Dirname - title = "Directory " - if *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/pkg": - tabtitle = "Packages" - case "/src/cmd": - tabtitle = "Commands" - } - - servePage(w, Page{ - Title: title, - Tabtitle: tabtitle, - Subtitle: subtitle, - Body: applyTemplate(packageHTML, "packageHTML", info), - }) -} - -// ---------------------------------------------------------------------------- -// Search - -var searchIndex RWValue - -type SearchResult struct { - Query string - Alert string // error or warning message - - // identifier matches - Pak HitList // packages matching Query - Hit *LookupResult // identifier matches of Query - Alt *AltWords // alternative identifiers to look for - - // textual matches - Found int // number of textual occurrences found - Textual []FileLines // textual matches of Query - Complete bool // true if all textual occurrences of Query are reported -} - -func lookup(query string) (result SearchResult) { - result.Query = query - - index, timestamp := searchIndex.get() - if index != nil { - index := index.(*Index) - - // identifier search - var err error - result.Pak, result.Hit, result.Alt, err = index.Lookup(query) - if err != nil && *maxResults <= 0 { - // ignore the error if full text search is enabled - // since the query may be a valid regular expression - result.Alert = "Error in query string: " + err.Error() - return - } - - // full text search - if *maxResults > 0 && query != "" { - rx, err := regexp.Compile(query) - if err != nil { - result.Alert = "Error in query regular expression: " + err.Error() - return - } - // If we get maxResults+1 results we know that there are more than - // maxResults results and thus the result may be incomplete (to be - // precise, we should remove one result from the result set, but - // nobody is going to count the results on the result page). - result.Found, result.Textual = index.LookupRegexp(rx, *maxResults+1) - result.Complete = result.Found <= *maxResults - if !result.Complete { - result.Found-- // since we looked for maxResults+1 - } - } - } - - // is the result accurate? - if *indexEnabled { - if _, ts := fsModified.get(); timestamp.Before(ts) { - // The index is older than the latest file system change under godoc's observation. - result.Alert = "Indexing in progress: result may be inaccurate" - } - } else { - result.Alert = "Search index disabled: no results available" - } - - return -} - -func search(w http.ResponseWriter, r *http.Request) { - query := strings.TrimSpace(r.FormValue("q")) - result := lookup(query) - - if getPageInfoMode(r)&noHtml != 0 { - serveText(w, applyTemplate(searchText, "searchText", result)) - return - } - - var title string - if result.Hit != nil || len(result.Textual) > 0 { - title = fmt.Sprintf(`Results for query %q`, query) - } else { - title = fmt.Sprintf(`No results found for query %q`, query) - } - - servePage(w, Page{ - Title: title, - Tabtitle: query, - Query: query, - Body: applyTemplate(searchHTML, "searchHTML", result), - }) -} - -// ---------------------------------------------------------------------------- -// Documentation Metadata - -type Metadata struct { - Title string - Subtitle string - Template bool // execute as template - Path string // canonical path for this page - filePath string // filesystem path relative to goroot -} - -// extractMetadata extracts the Metadata from a byte slice. -// It returns the Metadata value and the remaining data. -// If no metadata is present the original byte slice is returned. -// -func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) { - tail = b - if !bytes.HasPrefix(b, jsonStart) { - return - } - end := bytes.Index(b, jsonEnd) - if end < 0 { - return - } - b = b[len(jsonStart)-1 : end+1] // drop leading %s", command, buf.Bytes()) - return text, nil -} - -// parseArg returns the integer or string value of the argument and tells which it is. -func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) { - switch n := arg.(type) { - case int: - if n <= 0 || n > max { - log.Panicf("%q:%d is out of range", file, n) - } - return n, "", true - case string: - return 0, n, false - } - log.Panicf("unrecognized argument %v type %T", arg, arg) - return -} - -// oneLine returns the single line generated by a two-argument code invocation. -func oneLine(file, text string, arg interface{}) string { - lines := strings.SplitAfter(contents(file), "\n") - line, pattern, isInt := parseArg(arg, file, len(lines)) - if isInt { - return lines[line-1] - } - return lines[match(file, 0, lines, pattern)-1] -} - -// multipleLines returns the text generated by a three-argument code invocation. -func multipleLines(file, text string, arg1, arg2 interface{}) string { - lines := strings.SplitAfter(contents(file), "\n") - line1, pattern1, isInt1 := parseArg(arg1, file, len(lines)) - line2, pattern2, isInt2 := parseArg(arg2, file, len(lines)) - if !isInt1 { - line1 = match(file, 0, lines, pattern1) - } - if !isInt2 { - line2 = match(file, line1, lines, pattern2) - } else if line2 < line1 { - log.Panicf("lines out of order for %q: %d %d", text, line1, line2) - } - for k := line1 - 1; k < line2; k++ { - if strings.HasSuffix(lines[k], "OMIT\n") { - lines[k] = "" - } - } - return strings.Join(lines[line1-1:line2], "") -} - -// match identifies the input line that matches the pattern in a code invocation. -// If start>0, match lines starting there rather than at the beginning. -// The return value is 1-indexed. -func match(file string, start int, lines []string, pattern string) int { - // $ matches the end of the file. - if pattern == "$" { - if len(lines) == 0 { - log.Panicf("%q: empty file", file) - } - return len(lines) - } - // /regexp/ matches the line that matches the regexp. - if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' { - re, err := regexp.Compile(pattern[1 : len(pattern)-1]) - if err != nil { - log.Panic(err) - } - for i := start; i < len(lines); i++ { - if re.MatchString(lines[i]) { - return i + 1 - } - } - log.Panicf("%s: no match for %#q", file, pattern) - } - log.Panicf("unrecognized pattern: %q", pattern) - return 0 -} diff --git a/src/cmd/godoc/throttle.go b/src/cmd/godoc/throttle.go deleted file mode 100644 index ac18b44e0e..0000000000 --- a/src/cmd/godoc/throttle.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2011 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 main - -import "time" - -// A Throttle permits throttling of a goroutine by -// calling the Throttle method repeatedly. -// -type Throttle struct { - f float64 // f = (1-r)/r for 0 < r < 1 - dt time.Duration // minimum run time slice; >= 0 - tr time.Duration // accumulated time running - ts time.Duration // accumulated time stopped - tt time.Time // earliest throttle time (= time Throttle returned + tm) -} - -// NewThrottle creates a new Throttle with a throttle value r and -// a minimum allocated run time slice of dt: -// -// r == 0: "empty" throttle; the goroutine is always sleeping -// r == 1: full throttle; the goroutine is never sleeping -// -// A value of r == 0.6 throttles a goroutine such that it runs -// approx. 60% of the time, and sleeps approx. 40% of the time. -// Values of r < 0 or r > 1 are clamped down to values between 0 and 1. -// Values of dt < 0 are set to 0. -// -func NewThrottle(r float64, dt time.Duration) *Throttle { - var f float64 - switch { - case r <= 0: - f = -1 // indicates always sleep - case r >= 1: - f = 0 // assume r == 1 (never sleep) - default: - // 0 < r < 1 - f = (1 - r) / r - } - if dt < 0 { - dt = 0 - } - return &Throttle{f: f, dt: dt, tt: time.Now().Add(dt)} -} - -// Throttle calls time.Sleep such that over time the ratio tr/ts between -// accumulated run (tr) and sleep times (ts) approximates the value 1/(1-r) -// where r is the throttle value. Throttle returns immediately (w/o sleeping) -// if less than tm ns have passed since the last call to Throttle. -// -func (p *Throttle) Throttle() { - if p.f < 0 { - select {} // always sleep - } - - t0 := time.Now() - if t0.Before(p.tt) { - return // keep running (minimum time slice not exhausted yet) - } - - // accumulate running time - p.tr += t0.Sub(p.tt) + p.dt - - // compute sleep time - // Over time we want: - // - // tr/ts = r/(1-r) - // - // Thus: - // - // ts = tr*f with f = (1-r)/r - // - // After some incremental run time δr added to the total run time - // tr, the incremental sleep-time δs to get to the same ratio again - // after waking up from time.Sleep is: - if δs := time.Duration(float64(p.tr)*p.f) - p.ts; δs > 0 { - time.Sleep(δs) - } - - // accumulate (actual) sleep time - t1 := time.Now() - p.ts += t1.Sub(t0) - - // set earliest next throttle time - p.tt = t1.Add(p.dt) -} diff --git a/src/cmd/godoc/utils.go b/src/cmd/godoc/utils.go deleted file mode 100644 index 0cdb7ff7af..0000000000 --- a/src/cmd/godoc/utils.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains support functionality for godoc. - -package main - -import ( - pathpkg "path" - "sync" - "time" - "unicode/utf8" -) - -// An RWValue wraps a value and permits mutually exclusive -// access to it and records the time the value was last set. -// -type RWValue struct { - mutex sync.RWMutex - value interface{} - timestamp time.Time // time of last set() -} - -func (v *RWValue) set(value interface{}) { - v.mutex.Lock() - v.value = value - v.timestamp = time.Now() - v.mutex.Unlock() -} - -func (v *RWValue) get() (interface{}, time.Time) { - v.mutex.RLock() - defer v.mutex.RUnlock() - return v.value, v.timestamp -} - -// isText returns true if a significant prefix of s looks like correct UTF-8; -// that is, if it is likely that s is human-readable text. -// -func isText(s []byte) bool { - const max = 1024 // at least utf8.UTFMax - if len(s) > max { - s = s[0:max] - } - for i, c := range string(s) { - if i+utf8.UTFMax > len(s) { - // last char may be incomplete - ignore - break - } - if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' { - // decoding error or control character - not a text file - return false - } - } - return true -} - -// textExt[x] is true if the extension x indicates a text file, and false otherwise. -var textExt = map[string]bool{ - ".css": false, // must be served raw - ".js": false, // must be served raw -} - -// isTextFile returns true if the file has a known extension indicating -// a text file, or if a significant chunk of the specified file looks like -// correct UTF-8; that is, if it is likely that the file contains human- -// readable text. -// -func isTextFile(filename string) bool { - // if the extension is known, use it for decision making - if isText, found := textExt[pathpkg.Ext(filename)]; found { - return isText - } - - // the extension is not known; read an initial chunk - // of the file and check if it looks like text - f, err := fs.Open(filename) - if err != nil { - return false - } - defer f.Close() - - var buf [1024]byte - n, err := f.Read(buf[0:]) - if err != nil { - return false - } - - return isText(buf[0:n]) -} diff --git a/src/cmd/godoc/zip.go b/src/cmd/godoc/zip.go deleted file mode 100644 index 620eb4f3cc..0000000000 --- a/src/cmd/godoc/zip.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file provides an implementation of the FileSystem -// interface based on the contents of a .zip file. -// -// Assumptions: -// -// - The file paths stored in the zip file must use a slash ('/') as path -// separator; and they must be relative (i.e., they must not start with -// a '/' - this is usually the case if the file was created w/o special -// options). -// - The zip file system treats the file paths found in the zip internally -// like absolute paths w/o a leading '/'; i.e., the paths are considered -// relative to the root of the file system. -// - All path arguments to file system methods must be absolute paths. - -package main - -import ( - "archive/zip" - "fmt" - "io" - "os" - "path" - "sort" - "strings" - "time" -) - -// zipFI is the zip-file based implementation of FileInfo -type zipFI struct { - name string // directory-local name - file *zip.File // nil for a directory -} - -func (fi zipFI) Name() string { - return fi.name -} - -func (fi zipFI) Size() int64 { - if f := fi.file; f != nil { - return int64(f.UncompressedSize) - } - return 0 // directory -} - -func (fi zipFI) ModTime() time.Time { - if f := fi.file; f != nil { - return f.ModTime() - } - return time.Time{} // directory has no modified time entry -} - -func (fi zipFI) Mode() os.FileMode { - if fi.file == nil { - // Unix directories typically are executable, hence 555. - return os.ModeDir | 0555 - } - return 0444 -} - -func (fi zipFI) IsDir() bool { - return fi.file == nil -} - -func (fi zipFI) Sys() interface{} { - return nil -} - -// zipFS is the zip-file based implementation of FileSystem -type zipFS struct { - *zip.ReadCloser - list zipList - name string -} - -func (fs *zipFS) String() string { - return "zip(" + fs.name + ")" -} - -func (fs *zipFS) Close() error { - fs.list = nil - return fs.ReadCloser.Close() -} - -func zipPath(name string) string { - name = path.Clean(name) - if !path.IsAbs(name) { - panic(fmt.Sprintf("stat: not an absolute path: %s", name)) - } - return name[1:] // strip leading '/' -} - -func (fs *zipFS) stat(abspath string) (int, zipFI, error) { - i, exact := fs.list.lookup(abspath) - if i < 0 { - // abspath has leading '/' stripped - print it explicitly - return -1, zipFI{}, fmt.Errorf("file not found: /%s", abspath) - } - _, name := path.Split(abspath) - var file *zip.File - if exact { - file = fs.list[i] // exact match found - must be a file - } - return i, zipFI{name, file}, nil -} - -func (fs *zipFS) Open(abspath string) (readSeekCloser, error) { - _, fi, err := fs.stat(zipPath(abspath)) - if err != nil { - return nil, err - } - if fi.IsDir() { - return nil, fmt.Errorf("Open: %s is a directory", abspath) - } - r, err := fi.file.Open() - if err != nil { - return nil, err - } - return &zipSeek{fi.file, r}, nil -} - -type zipSeek struct { - file *zip.File - io.ReadCloser -} - -func (f *zipSeek) Seek(offset int64, whence int) (int64, error) { - if whence == 0 && offset == 0 { - r, err := f.file.Open() - if err != nil { - return 0, err - } - f.Close() - f.ReadCloser = r - return 0, nil - } - return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name) -} - -func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) { - _, fi, err := fs.stat(zipPath(abspath)) - return fi, err -} - -func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) { - _, fi, err := fs.stat(zipPath(abspath)) - return fi, err -} - -func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) { - path := zipPath(abspath) - i, fi, err := fs.stat(path) - if err != nil { - return nil, err - } - if !fi.IsDir() { - return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath) - } - - var list []os.FileInfo - dirname := path + "/" - prevname := "" - for _, e := range fs.list[i:] { - if !strings.HasPrefix(e.Name, dirname) { - break // not in the same directory anymore - } - name := e.Name[len(dirname):] // local name - file := e - if i := strings.IndexRune(name, '/'); i >= 0 { - // We infer directories from files in subdirectories. - // If we have x/y, return a directory entry for x. - name = name[0:i] // keep local directory name only - file = nil - } - // If we have x/y and x/z, don't return two directory entries for x. - // TODO(gri): It should be possible to do this more efficiently - // by determining the (fs.list) range of local directory entries - // (via two binary searches). - if name != prevname { - list = append(list, zipFI{name, file}) - prevname = name - } - } - - return list, nil -} - -func NewZipFS(rc *zip.ReadCloser, name string) FileSystem { - list := make(zipList, len(rc.File)) - copy(list, rc.File) // sort a copy of rc.File - sort.Sort(list) - return &zipFS{rc, list, name} -} - -type zipList []*zip.File - -// zipList implements sort.Interface -func (z zipList) Len() int { return len(z) } -func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name } -func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] } - -// lookup returns the smallest index of an entry with an exact match -// for name, or an inexact match starting with name/. If there is no -// such entry, the result is -1, false. -func (z zipList) lookup(name string) (index int, exact bool) { - // look for exact match first (name comes before name/ in z) - i := sort.Search(len(z), func(i int) bool { - return name <= z[i].Name - }) - if i >= len(z) { - return -1, false - } - // 0 <= i < len(z) - if z[i].Name == name { - return i, true - } - - // look for inexact match (must be in z[i:], if present) - z = z[i:] - name += "/" - j := sort.Search(len(z), func(i int) bool { - return name <= z[i].Name - }) - if j >= len(z) { - return -1, false - } - // 0 <= j < len(z) - if strings.HasPrefix(z[j].Name, name) { - return i + j, false - } - - return -1, false -}") - template.HTMLEscape(w, data[lo:hi]) - io.WriteString(w, "") - } - template.HTMLEscape(w, data[hi:]) - io.WriteString(w, "
- {{end}} - {{.XML}} -