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 }