github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/src/cmd/doc/dirs.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"go/build"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  )
    14  
    15  // Dirs is a structure for scanning the directory tree.
    16  // Its Next method returns the next Go source directory it finds.
    17  // Although it can be used to scan the tree multiple times, it
    18  // only walks the tree once, caching the data it finds.
    19  type Dirs struct {
    20  	scan   chan string // directories generated by walk.
    21  	paths  []string    // Cache of known paths.
    22  	offset int         // Counter for Next.
    23  }
    24  
    25  var dirs Dirs
    26  
    27  func init() {
    28  	dirs.paths = make([]string, 0, 1000)
    29  	dirs.scan = make(chan string)
    30  	go dirs.walk()
    31  }
    32  
    33  // Reset puts the scan back at the beginning.
    34  func (d *Dirs) Reset() {
    35  	d.offset = 0
    36  }
    37  
    38  // Next returns the next directory in the scan. The boolean
    39  // is false when the scan is done.
    40  func (d *Dirs) Next() (string, bool) {
    41  	if d.offset < len(d.paths) {
    42  		path := d.paths[d.offset]
    43  		d.offset++
    44  		return path, true
    45  	}
    46  	path, ok := <-d.scan
    47  	if !ok {
    48  		return "", false
    49  	}
    50  	d.paths = append(d.paths, path)
    51  	d.offset++
    52  	return path, ok
    53  }
    54  
    55  // walk walks the trees in GOROOT and GOPATH.
    56  func (d *Dirs) walk() {
    57  	d.walkRoot(build.Default.GOROOT)
    58  	for _, root := range splitGopath() {
    59  		d.walkRoot(root)
    60  	}
    61  	close(d.scan)
    62  }
    63  
    64  // walkRoot walks a single directory. Each Go source directory it finds is
    65  // delivered on d.scan.
    66  func (d *Dirs) walkRoot(root string) {
    67  	root = path.Join(root, "src")
    68  	slashDot := string(filepath.Separator) + "."
    69  	// We put a slash on the pkg so can use simple string comparison below
    70  	// yet avoid inadvertent matches, like /foobar matching bar.
    71  
    72  	visit := func(pathName string, f os.FileInfo, err error) error {
    73  		if err != nil {
    74  			return nil
    75  		}
    76  		// One package per directory. Ignore the files themselves.
    77  		if !f.IsDir() {
    78  			return nil
    79  		}
    80  		// No .git or other dot nonsense please.
    81  		if strings.Contains(pathName, slashDot) {
    82  			return filepath.SkipDir
    83  		}
    84  		// Does the directory contain any Go files? If so, it's a candidate.
    85  		if hasGoFiles(pathName) {
    86  			d.scan <- pathName
    87  			return nil
    88  		}
    89  		return nil
    90  	}
    91  
    92  	filepath.Walk(root, visit)
    93  }
    94  
    95  // hasGoFiles tests whether the directory contains at least one file with ".go"
    96  // extension
    97  func hasGoFiles(path string) bool {
    98  	dir, err := os.Open(path)
    99  	if err != nil {
   100  		// ignore unreadable directories
   101  		return false
   102  	}
   103  	defer dir.Close()
   104  
   105  	names, err := dir.Readdirnames(0)
   106  	if err != nil {
   107  		// ignore unreadable directories
   108  		return false
   109  	}
   110  
   111  	for _, name := range names {
   112  		if strings.HasSuffix(name, ".go") {
   113  			return true
   114  		}
   115  	}
   116  
   117  	return false
   118  }