Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(289)

Unified Diff: src/cmd/godoc/dirtrees.go

Issue 2098044: code review 2098044: godoc: moved package directory support code into separa... (Closed)
Patch Set: code review 2098044: godoc: moved package directory support code into separa... Created 14 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/cmd/godoc/Makefile ('k') | src/cmd/godoc/godoc.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/cmd/godoc/dirtrees.go
===================================================================
new file mode 100644
--- /dev/null
+++ b/src/cmd/godoc/dirtrees.go
@@ -0,0 +1,301 @@
+// 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 (
+ "go/doc"
+ "go/parser"
+ "io/ioutil"
+ "os"
+ pathutil "path"
+ "strings"
+ "unicode"
+)
+
+
+type Directory struct {
+ Depth int
+ Path string // includes Name
+ Name string
+ Text string // package documentation, if any
+ Dirs []*Directory // subdirectories
+}
+
+
+func isGoFile(f *os.FileInfo) bool {
+ return f.IsRegular() &&
+ !strings.HasPrefix(f.Name, ".") && // ignore .files
+ pathutil.Ext(f.Name) == ".go"
+}
+
+
+func isPkgFile(f *os.FileInfo) bool {
+ return isGoFile(f) &&
+ !strings.HasSuffix(f.Name, "_test.go") // ignore test files
+}
+
+
+func isPkgDir(f *os.FileInfo) bool {
+ return f.IsDirectory() && len(f.Name) > 0 && f.Name[0] != '_'
+}
+
+
+func firstSentence(s string) string {
+ i := -1 // index+1 of first terminator (punctuation ending a sentence)
+ j := -1 // index+1 of first terminator followed by white space
+ prev := 'A'
+ for k, ch := range s {
+ k1 := k + 1
+ if ch == '.' || ch == '!' || ch == '?' {
+ if i < 0 {
+ i = k1 // first terminator
+ }
+ if k1 < len(s) && s[k1] <= ' ' {
+ if j < 0 {
+ j = k1 // first terminator followed by white space
+ }
+ if !unicode.IsUpper(prev) {
+ j = k1
+ break
+ }
+ }
+ }
+ prev = ch
+ }
+
+ if j < 0 {
+ // use the next best terminator
+ j = i
+ if j < 0 {
+ // no terminator at all, use the entire string
+ j = len(s)
+ }
+ }
+
+ return s[0:j]
+}
+
+
+func newDirTree(path, name string, depth, maxDepth int) *Directory {
+ if depth >= 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, path, name, "", nil}
+ }
+
+ list, _ := ioutil.ReadDir(path) // ignore errors
+
+ // determine number of subdirectories and package files
+ ndirs := 0
+ nfiles := 0
+ var synopses [4]string // prioritized package documentation (0 == highest priority)
+ for _, d := range list {
+ switch {
+ case isPkgDir(d):
+ ndirs++
+ case isPkgFile(d):
+ nfiles++
+ if synopses[0] == "" {
+ // no "optimal" package synopsis yet; continue to collect synopses
+ file, err := parser.ParseFile(pathutil.Join(path, d.Name), nil,
+ parser.ParseComments|parser.PackageClauseOnly)
+ if err == nil && file.Doc != nil {
+ // prioritize documentation
+ i := -1
+ switch file.Name.Name {
+ case name:
+ i = 0 // normal case: directory name matches package name
+ case fakePkgName:
+ i = 1 // synopses for commands
+ case "main":
+ i = 2 // directory contains a main package
+ default:
+ i = 3 // none of the above
+ }
+ if 0 <= i && i < len(synopses) && synopses[i] == "" {
+ synopses[i] = firstSentence(doc.CommentText(file.Doc))
+ }
+ }
+ }
+ }
+ }
+
+ // create subdirectory tree
+ var dirs []*Directory
+ if ndirs > 0 {
+ dirs = make([]*Directory, ndirs)
+ i := 0
+ for _, d := range list {
+ if isPkgDir(d) {
+ dd := newDirTree(pathutil.Join(path, d.Name), d.Name, depth+1, maxDepth)
+ if dd != nil {
+ dirs[i] = dd
+ i++
+ }
+ }
+ }
+ dirs = dirs[0:i]
+ }
+
+ // if there are no package files and no subdirectories
+ // (with package files), ignore the directory
+ if nfiles == 0 && 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, path, name, synopsis, 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 maxDepth is
+// too shallow, the leaf nodes 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 {
+ d, err := os.Lstat(root)
+ if err != nil || !isPkgDir(d) {
+ return nil
+ }
+ return newDirTree(root, d.Name, 0, maxDepth)
+}
+
+
+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
+}
+
+
+// lookup looks for the *Directory for a given path, relative to dir.
+func (dir *Directory) lookup(path string) *Directory {
+ d := strings.Split(dir.Path, "/", -1)
+ p := strings.Split(path, "/", -1)
+ 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 // includes Name, relative to DirList root
+ Name string
+ Synopsis string
+}
+
+
+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 := d.Path
+ if strings.HasPrefix(d.Path, root.Path) {
+ path = d.Path[len(root.Path):]
+ }
+ // remove trailing '/' if any - path must be relative
+ if len(path) > 0 && path[0] == '/' {
+ path = path[1:]
+ }
+ p.Path = path
+ p.Name = d.Name
+ p.Synopsis = d.Text
+ i++
+ }
+
+ return &DirList{maxHeight, list}
+}
« no previous file with comments | « src/cmd/godoc/Makefile ('k') | src/cmd/godoc/godoc.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b