/
dirs.go
118 lines (103 loc) · 2.63 KB
/
dirs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// 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"
"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.walkRoot(build.Default.GOROOT)
for _, root := range splitGopath() {
d.walkRoot(root)
}
close(d.scan)
}
// walkRoot walks a single directory. Each Go source directory it finds is
// delivered on d.scan.
func (d *Dirs) walkRoot(root string) {
root = path.Join(root, "src")
slashDot := string(filepath.Separator) + "."
// We put a slash on the pkg so can use simple string comparison below
// yet avoid inadvertent matches, like /foobar matching bar.
visit := func(pathName string, f os.FileInfo, err error) error {
if err != nil {
return nil
}
// One package per directory. Ignore the files themselves.
if !f.IsDir() {
return nil
}
// No .git or other dot nonsense please.
if strings.Contains(pathName, slashDot) {
return filepath.SkipDir
}
// Does the directory contain any Go files? If so, it's a candidate.
if hasGoFiles(pathName) {
d.scan <- pathName
return nil
}
return nil
}
filepath.Walk(root, visit)
}
// hasGoFiles tests whether the directory contains at least one file with ".go"
// extension
func hasGoFiles(path string) bool {
dir, err := os.Open(path)
if err != nil {
// ignore unreadable directories
return false
}
defer dir.Close()
names, err := dir.Readdirnames(0)
if err != nil {
// ignore unreadable directories
return false
}
for _, name := range names {
if strings.HasSuffix(name, ".go") {
return true
}
}
return false
}