// Copyright 2015 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 (
"go/build"
"log"
"os"
"path"
"path/filepath"
"strings"
)
// Dirs is a structure for scanning the directory tree.
// Its Next method returns the next Go source directory it finds.
// Although it can be used to scan the tree multiple times, it
// only walks the tree once, caching the data it finds.
type Dirs struct {
scan chan string // directories generated by walk.
paths []string // Cache of known paths.
offset int // Counter for Next.
}
var dirs Dirs
func init() {
dirs.paths = make([]string, 0, 1000)
dirs.scan = make(chan string)
go dirs.walk()
}
// Reset puts the scan back at the beginning.
func (d *Dirs) Reset() {
d.offset = 0
}
// Next returns the next directory in the scan. The boolean
// is false when the scan is done.
func (d *Dirs) Next() (string, bool) {
if d.offset < len(d.paths) {
path := d.paths[d.offset]
d.offset++
return path, true
}
path, ok := <-d.scan
if !ok {
return "", false
}
d.paths = append(d.paths, path)
d.offset++
return path, ok
}
// walk walks the trees in GOROOT and GOPATH.
func (d *Dirs) walk() {
d.bfsWalkRoot(build.Default.GOROOT)
for _, root := range splitGopath() {
d.bfsWalkRoot(root)
}
close(d.scan)
}
// bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order.
// Each Go source directory it finds is delivered on d.scan.
func (d *Dirs) bfsWalkRoot(root string) {
root = path.Join(root, "src")
// this is the queue of directories to examine in this pass.
this := []string{}
// next is the queue of directories to examine in the next pass.
next := []string{root}
for len(next) > 0 {
this, next = next, this[0:0]
for _, dir := range this {
fd, err := os.Open(dir)
if err != nil {
log.Print(err)
continue
}
entries, err := fd.Readdir(0)
fd.Close()
if err != nil {
log.Print(err)
continue
}
hasGoFiles := false
for _, entry := range entries {
name := entry.Name()
// For plain files, remember if this directory contains any .go
// source files, but ignore them otherwise.
if !entry.IsDir() {
if !hasGoFiles && strings.HasSuffix(name, ".go") {
hasGoFiles = true
}
continue
}
// Entry is a directory.
// No .git or other dot nonsense please.
if strings.HasPrefix(name, ".") {
continue
}
// Remember this (fully qualified) directory for the next pass.
next = append(next, filepath.Join(dir, name))
}
if hasGoFiles {
// It's a candidate.
d.scan <- dir
}
}
}
}