1
2
3
4
5
6
7 package godoc
8
9 import (
10 "go/doc"
11 "go/parser"
12 "go/token"
13 "log"
14 "os"
15 pathpkg "path"
16 "runtime"
17 "sort"
18 "strings"
19
20 "golang.org/x/tools/godoc/vfs"
21 )
22
23
24
25 const testdataDirName = "testdata"
26
27 type Directory struct {
28 Depth int
29 Path string
30 Name string
31 HasPkg bool
32 Synopsis string
33 RootType vfs.RootType
34 Dirs []*Directory
35 }
36
37 func isGoFile(fi os.FileInfo) bool {
38 name := fi.Name()
39 return !fi.IsDir() &&
40 len(name) > 0 && name[0] != '.' &&
41 pathpkg.Ext(name) == ".go"
42 }
43
44 func isPkgFile(fi os.FileInfo) bool {
45 return isGoFile(fi) &&
46 !strings.HasSuffix(fi.Name(), "_test.go")
47 }
48
49 func isPkgDir(fi os.FileInfo) bool {
50 name := fi.Name()
51 return fi.IsDir() && len(name) > 0 &&
52 name[0] != '_' && name[0] != '.'
53 }
54
55 type treeBuilder struct {
56 c *Corpus
57 maxDepth int
58 }
59
60
61
62 var ioGate = make(chan struct{}, 20)
63
64
65
66
67
68 var workGate = make(chan struct{}, runtime.NumCPU()*4)
69
70 func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
71 if name == testdataDirName {
72 return nil
73 }
74
75 if depth >= b.maxDepth {
76
77
78
79 return &Directory{
80 Depth: depth,
81 Path: path,
82 Name: name,
83 }
84 }
85
86 var synopses [3]string
87
88 show := true
89 hasPkgFiles := false
90 haveSummary := false
91
92 if hook := b.c.SummarizePackage; hook != nil {
93 if summary, show0, ok := hook(strings.TrimPrefix(path, "/src/")); ok {
94 hasPkgFiles = true
95 show = show0
96 synopses[0] = summary
97 haveSummary = true
98 }
99 }
100
101 ioGate <- struct{}{}
102 list, err := b.c.fs.ReadDir(path)
103 <-ioGate
104 if err != nil {
105
106
107 if b.c.Verbose {
108 log.Printf("newDirTree reading %s: %v", path, err)
109 }
110 }
111
112
113 var dirchs []chan *Directory
114 var dirs []*Directory
115
116 for _, d := range list {
117 filename := pathpkg.Join(path, d.Name())
118 switch {
119 case isPkgDir(d):
120 name := d.Name()
121 select {
122 case workGate <- struct{}{}:
123 ch := make(chan *Directory, 1)
124 dirchs = append(dirchs, ch)
125 go func() {
126 ch <- b.newDirTree(fset, filename, name, depth+1)
127 <-workGate
128 }()
129 default:
130
131 dir := b.newDirTree(fset, filename, name, depth+1)
132 if dir != nil {
133 dirs = append(dirs, dir)
134 }
135 }
136 case !haveSummary && isPkgFile(d):
137
138
139
140
141 ioGate <- struct{}{}
142 const flags = parser.ParseComments | parser.PackageClauseOnly
143 file, err := b.c.parseFile(fset, filename, flags)
144 <-ioGate
145 if err != nil {
146 if b.c.Verbose {
147 log.Printf("Error parsing %v: %v", filename, err)
148 }
149 break
150 }
151
152 hasPkgFiles = true
153 if file.Doc != nil {
154
155 i := -1
156 switch file.Name.Name {
157 case name:
158 i = 0
159 case "main":
160 i = 1
161 default:
162 i = 2
163 }
164 if 0 <= i && i < len(synopses) && synopses[i] == "" {
165 synopses[i] = doc.Synopsis(file.Doc.Text())
166 }
167 }
168 haveSummary = synopses[0] != ""
169 }
170 }
171
172
173 for _, ch := range dirchs {
174 if d := <-ch; d != nil {
175 dirs = append(dirs, d)
176 }
177 }
178
179
180
181 sort.Slice(dirs, func(i, j int) bool {
182 return dirs[i].Name < dirs[j].Name
183 })
184
185
186
187 if !hasPkgFiles && len(dirs) == 0 {
188 return nil
189 }
190
191
192 synopsis := ""
193 for _, synopsis = range synopses {
194 if synopsis != "" {
195 break
196 }
197 }
198
199 return &Directory{
200 Depth: depth,
201 Path: path,
202 Name: name,
203 HasPkg: hasPkgFiles && show,
204 Synopsis: synopsis,
205 RootType: b.c.fs.RootType(path),
206 Dirs: dirs,
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 func (c *Corpus) newDirectory(root string, maxDepth int) *Directory {
220
221 d, err := c.fs.Stat(root)
222
223
224 switch {
225 case err != nil:
226 log.Printf("newDirectory(%s): %s", root, err)
227 return nil
228 case root != "/" && !isPkgDir(d):
229 log.Printf("newDirectory(%s): not a package directory", root)
230 return nil
231 case root == "/" && !d.IsDir():
232 log.Printf("newDirectory(%s): not a directory", root)
233 return nil
234 }
235 if maxDepth < 0 {
236 maxDepth = 1e6
237 }
238 b := treeBuilder{c, maxDepth}
239
240
241 return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
242 }
243
244 func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
245 if dir != nil {
246 if !skipRoot {
247 c <- dir
248 }
249 for _, d := range dir.Dirs {
250 d.walk(c, false)
251 }
252 }
253 }
254
255 func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
256 c := make(chan *Directory)
257 go func() {
258 dir.walk(c, skipRoot)
259 close(c)
260 }()
261 return c
262 }
263
264 func (dir *Directory) lookupLocal(name string) *Directory {
265 for _, d := range dir.Dirs {
266 if d.Name == name {
267 return d
268 }
269 }
270 return nil
271 }
272
273 func splitPath(p string) []string {
274 p = strings.TrimPrefix(p, "/")
275 if p == "" {
276 return nil
277 }
278 return strings.Split(p, "/")
279 }
280
281
282 func (dir *Directory) lookup(path string) *Directory {
283 d := splitPath(dir.Path)
284 p := splitPath(path)
285 i := 0
286 for i < len(d) {
287 if i >= len(p) || d[i] != p[i] {
288 return nil
289 }
290 i++
291 }
292 for dir != nil && i < len(p) {
293 dir = dir.lookupLocal(p[i])
294 i++
295 }
296 return dir
297 }
298
299
300
301 type DirEntry struct {
302 Depth int
303 Height int
304 Path string
305 Name string
306 HasPkg bool
307 Synopsis string
308 RootType vfs.RootType
309 }
310
311 type DirList struct {
312 MaxHeight int
313 List []DirEntry
314 }
315
316
317
318 func hasThirdParty(list []DirEntry) bool {
319 for _, entry := range list {
320 if entry.RootType == vfs.RootTypeGoPath {
321 return true
322 }
323 }
324 return false
325 }
326
327
328
329
330
331 func (dir *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
332 if dir == nil {
333 return nil
334 }
335
336
337 n := 0
338 minDepth := 1 << 30
339 maxDepth := 0
340 for d := range dir.iter(skipRoot) {
341 n++
342 if minDepth > d.Depth {
343 minDepth = d.Depth
344 }
345 if maxDepth < d.Depth {
346 maxDepth = d.Depth
347 }
348 }
349 maxHeight := maxDepth - minDepth + 1
350
351 if n == 0 {
352 return nil
353 }
354
355
356 list := make([]DirEntry, 0, n)
357 for d := range dir.iter(skipRoot) {
358 if filter != nil && !filter(d.Path) {
359 continue
360 }
361 var p DirEntry
362 p.Depth = d.Depth - minDepth
363 p.Height = maxHeight - p.Depth
364
365
366
367 path := strings.TrimPrefix(d.Path, dir.Path)
368
369 path = strings.TrimPrefix(path, "/")
370 p.Path = path
371 p.Name = d.Name
372 p.HasPkg = d.HasPkg
373 p.Synopsis = d.Synopsis
374 p.RootType = d.RootType
375 list = append(list, p)
376 }
377
378 return &DirList{maxHeight, list}
379 }
380
View as plain text