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