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

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file contains the code dealing with package directory trees.
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"go/doc"
    12  	"go/parser"
    13  	"go/token"
    14  	"log"
    15  	"os"
    16  	pathpkg "path"
    17  	"strings"
    18  )
    19  
    20  // Conventional name for directories containing test data.
    21  // Excluded from directory trees.
    22  //
    23  const testdataDirName = "testdata"
    24  
    25  type Directory struct {
    26  	Depth    int
    27  	Path     string       // directory path; includes Name
    28  	Name     string       // directory name
    29  	HasPkg   bool         // true if the directory contains at least one package
    30  	Synopsis string       // package documentation, if any
    31  	Dirs     []*Directory // subdirectories
    32  }
    33  
    34  func isGoFile(fi os.FileInfo) bool {
    35  	name := fi.Name()
    36  	return !fi.IsDir() &&
    37  		len(name) > 0 && name[0] != '.' && // ignore .files
    38  		pathpkg.Ext(name) == ".go"
    39  }
    40  
    41  func isPkgFile(fi os.FileInfo) bool {
    42  	return isGoFile(fi) &&
    43  		!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
    44  }
    45  
    46  func isPkgDir(fi os.FileInfo) bool {
    47  	name := fi.Name()
    48  	return fi.IsDir() && len(name) > 0 &&
    49  		name[0] != '_' && name[0] != '.' // ignore _files and .files
    50  }
    51  
    52  type treeBuilder struct {
    53  	maxDepth int
    54  }
    55  
    56  func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
    57  	if name == testdataDirName {
    58  		return nil
    59  	}
    60  
    61  	if depth >= b.maxDepth {
    62  		// return a dummy directory so that the parent directory
    63  		// doesn't get discarded just because we reached the max
    64  		// directory depth
    65  		return &Directory{
    66  			Depth: depth,
    67  			Path:  path,
    68  			Name:  name,
    69  		}
    70  	}
    71  
    72  	list, _ := fs.ReadDir(path)
    73  
    74  	// determine number of subdirectories and if there are package files
    75  	ndirs := 0
    76  	hasPkgFiles := false
    77  	var synopses [3]string // prioritized package documentation (0 == highest priority)
    78  	for _, d := range list {
    79  		switch {
    80  		case isPkgDir(d):
    81  			ndirs++
    82  		case isPkgFile(d):
    83  			// looks like a package file, but may just be a file ending in ".go";
    84  			// don't just count it yet (otherwise we may end up with hasPkgFiles even
    85  			// though the directory doesn't contain any real package files - was bug)
    86  			if synopses[0] == "" {
    87  				// no "optimal" package synopsis yet; continue to collect synopses
    88  				file, err := parseFile(fset, pathpkg.Join(path, d.Name()),
    89  					parser.ParseComments|parser.PackageClauseOnly)
    90  				if err == nil {
    91  					hasPkgFiles = true
    92  					if file.Doc != nil {
    93  						// prioritize documentation
    94  						i := -1
    95  						switch file.Name.Name {
    96  						case name:
    97  							i = 0 // normal case: directory name matches package name
    98  						case "main":
    99  							i = 1 // directory contains a main package
   100  						default:
   101  							i = 2 // none of the above
   102  						}
   103  						if 0 <= i && i < len(synopses) && synopses[i] == "" {
   104  							synopses[i] = doc.Synopsis(file.Doc.Text())
   105  						}
   106  					}
   107  				}
   108  			}
   109  		}
   110  	}
   111  
   112  	// create subdirectory tree
   113  	var dirs []*Directory
   114  	if ndirs > 0 {
   115  		dirs = make([]*Directory, ndirs)
   116  		i := 0
   117  		for _, d := range list {
   118  			if isPkgDir(d) {
   119  				name := d.Name()
   120  				dd := b.newDirTree(fset, pathpkg.Join(path, name), name, depth+1)
   121  				if dd != nil {
   122  					dirs[i] = dd
   123  					i++
   124  				}
   125  			}
   126  		}
   127  		dirs = dirs[0:i]
   128  	}
   129  
   130  	// if there are no package files and no subdirectories
   131  	// containing package files, ignore the directory
   132  	if !hasPkgFiles && len(dirs) == 0 {
   133  		return nil
   134  	}
   135  
   136  	// select the highest-priority synopsis for the directory entry, if any
   137  	synopsis := ""
   138  	for _, synopsis = range synopses {
   139  		if synopsis != "" {
   140  			break
   141  		}
   142  	}
   143  
   144  	return &Directory{
   145  		Depth:    depth,
   146  		Path:     path,
   147  		Name:     name,
   148  		HasPkg:   hasPkgFiles,
   149  		Synopsis: synopsis,
   150  		Dirs:     dirs,
   151  	}
   152  }
   153  
   154  // newDirectory creates a new package directory tree with at most maxDepth
   155  // levels, anchored at root. The result tree is pruned such that it only
   156  // contains directories that contain package files or that contain
   157  // subdirectories containing package files (transitively). If a non-nil
   158  // pathFilter is provided, directory paths additionally must be accepted
   159  // by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is
   160  // provided for maxDepth, nodes at larger depths are pruned as well; they
   161  // are assumed to contain package files even if their contents are not known
   162  // (i.e., in this case the tree may contain directories w/o any package files).
   163  //
   164  func newDirectory(root string, maxDepth int) *Directory {
   165  	// The root could be a symbolic link so use Stat not Lstat.
   166  	d, err := fs.Stat(root)
   167  	// If we fail here, report detailed error messages; otherwise
   168  	// is is hard to see why a directory tree was not built.
   169  	switch {
   170  	case err != nil:
   171  		log.Printf("newDirectory(%s): %s", root, err)
   172  		return nil
   173  	case !isPkgDir(d):
   174  		log.Printf("newDirectory(%s): not a package directory", root)
   175  		return nil
   176  	}
   177  	if maxDepth < 0 {
   178  		maxDepth = 1e6 // "infinity"
   179  	}
   180  	b := treeBuilder{maxDepth}
   181  	// the file set provided is only for local parsing, no position
   182  	// information escapes and thus we don't need to save the set
   183  	return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
   184  }
   185  
   186  func (dir *Directory) writeLeafs(buf *bytes.Buffer) {
   187  	if dir != nil {
   188  		if len(dir.Dirs) == 0 {
   189  			buf.WriteString(dir.Path)
   190  			buf.WriteByte('\n')
   191  			return
   192  		}
   193  
   194  		for _, d := range dir.Dirs {
   195  			d.writeLeafs(buf)
   196  		}
   197  	}
   198  }
   199  
   200  func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
   201  	if dir != nil {
   202  		if !skipRoot {
   203  			c <- dir
   204  		}
   205  		for _, d := range dir.Dirs {
   206  			d.walk(c, false)
   207  		}
   208  	}
   209  }
   210  
   211  func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
   212  	c := make(chan *Directory)
   213  	go func() {
   214  		dir.walk(c, skipRoot)
   215  		close(c)
   216  	}()
   217  	return c
   218  }
   219  
   220  func (dir *Directory) lookupLocal(name string) *Directory {
   221  	for _, d := range dir.Dirs {
   222  		if d.Name == name {
   223  			return d
   224  		}
   225  	}
   226  	return nil
   227  }
   228  
   229  func splitPath(p string) []string {
   230  	p = strings.TrimPrefix(p, "/")
   231  	if p == "" {
   232  		return nil
   233  	}
   234  	return strings.Split(p, "/")
   235  }
   236  
   237  // lookup looks for the *Directory for a given path, relative to dir.
   238  func (dir *Directory) lookup(path string) *Directory {
   239  	d := splitPath(dir.Path)
   240  	p := splitPath(path)
   241  	i := 0
   242  	for i < len(d) {
   243  		if i >= len(p) || d[i] != p[i] {
   244  			return nil
   245  		}
   246  		i++
   247  	}
   248  	for dir != nil && i < len(p) {
   249  		dir = dir.lookupLocal(p[i])
   250  		i++
   251  	}
   252  	return dir
   253  }
   254  
   255  // DirEntry describes a directory entry. The Depth and Height values
   256  // are useful for presenting an entry in an indented fashion.
   257  //
   258  type DirEntry struct {
   259  	Depth    int    // >= 0
   260  	Height   int    // = DirList.MaxHeight - Depth, > 0
   261  	Path     string // directory path; includes Name, relative to DirList root
   262  	Name     string // directory name
   263  	HasPkg   bool   // true if the directory contains at least one package
   264  	Synopsis string // package documentation, if any
   265  }
   266  
   267  type DirList struct {
   268  	MaxHeight int // directory tree height, > 0
   269  	List      []DirEntry
   270  }
   271  
   272  // listing creates a (linear) directory listing from a directory tree.
   273  // If skipRoot is set, the root directory itself is excluded from the list.
   274  //
   275  func (root *Directory) listing(skipRoot bool) *DirList {
   276  	if root == nil {
   277  		return nil
   278  	}
   279  
   280  	// determine number of entries n and maximum height
   281  	n := 0
   282  	minDepth := 1 << 30 // infinity
   283  	maxDepth := 0
   284  	for d := range root.iter(skipRoot) {
   285  		n++
   286  		if minDepth > d.Depth {
   287  			minDepth = d.Depth
   288  		}
   289  		if maxDepth < d.Depth {
   290  			maxDepth = d.Depth
   291  		}
   292  	}
   293  	maxHeight := maxDepth - minDepth + 1
   294  
   295  	if n == 0 {
   296  		return nil
   297  	}
   298  
   299  	// create list
   300  	list := make([]DirEntry, n)
   301  	i := 0
   302  	for d := range root.iter(skipRoot) {
   303  		p := &list[i]
   304  		p.Depth = d.Depth - minDepth
   305  		p.Height = maxHeight - p.Depth
   306  		// the path is relative to root.Path - remove the root.Path
   307  		// prefix (the prefix should always be present but avoid
   308  		// crashes and check)
   309  		path := strings.TrimPrefix(d.Path, root.Path)
   310  		// remove leading separator if any - path must be relative
   311  		path = strings.TrimPrefix(path, "/")
   312  		p.Path = path
   313  		p.Name = d.Name
   314  		p.HasPkg = d.HasPkg
   315  		p.Synopsis = d.Synopsis
   316  		i++
   317  	}
   318  
   319  	return &DirList{maxHeight, list}
   320  }