github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/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 main 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 maxDepth int 54 } 55 56 func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory { 57 if name == testdataDirName { 58 return nil 59 } 60 61 if depth >= b.maxDepth { 62 // return a dummy directory so that the parent directory 63 // doesn't get discarded just because we reached the max 64 // directory depth 65 return &Directory{ 66 Depth: depth, 67 Path: path, 68 Name: name, 69 } 70 } 71 72 list, _ := fs.ReadDir(path) 73 74 // determine number of subdirectories and if there are package files 75 ndirs := 0 76 hasPkgFiles := false 77 var synopses [3]string // prioritized package documentation (0 == highest priority) 78 for _, d := range list { 79 switch { 80 case isPkgDir(d): 81 ndirs++ 82 case isPkgFile(d): 83 // looks like a package file, but may just be a file ending in ".go"; 84 // don't just count it yet (otherwise we may end up with hasPkgFiles even 85 // though the directory doesn't contain any real package files - was bug) 86 if synopses[0] == "" { 87 // no "optimal" package synopsis yet; continue to collect synopses 88 file, err := parseFile(fset, pathpkg.Join(path, d.Name()), 89 parser.ParseComments|parser.PackageClauseOnly) 90 if err == nil { 91 hasPkgFiles = true 92 if file.Doc != nil { 93 // prioritize documentation 94 i := -1 95 switch file.Name.Name { 96 case name: 97 i = 0 // normal case: directory name matches package name 98 case "main": 99 i = 1 // directory contains a main package 100 default: 101 i = 2 // none of the above 102 } 103 if 0 <= i && i < len(synopses) && synopses[i] == "" { 104 synopses[i] = doc.Synopsis(file.Doc.Text()) 105 } 106 } 107 } 108 } 109 } 110 } 111 112 // create subdirectory tree 113 var dirs []*Directory 114 if ndirs > 0 { 115 dirs = make([]*Directory, ndirs) 116 i := 0 117 for _, d := range list { 118 if isPkgDir(d) { 119 name := d.Name() 120 dd := b.newDirTree(fset, pathpkg.Join(path, name), name, depth+1) 121 if dd != nil { 122 dirs[i] = dd 123 i++ 124 } 125 } 126 } 127 dirs = dirs[0:i] 128 } 129 130 // if there are no package files and no subdirectories 131 // containing package files, ignore the directory 132 if !hasPkgFiles && len(dirs) == 0 { 133 return nil 134 } 135 136 // select the highest-priority synopsis for the directory entry, if any 137 synopsis := "" 138 for _, synopsis = range synopses { 139 if synopsis != "" { 140 break 141 } 142 } 143 144 return &Directory{ 145 Depth: depth, 146 Path: path, 147 Name: name, 148 HasPkg: hasPkgFiles, 149 Synopsis: synopsis, 150 Dirs: dirs, 151 } 152 } 153 154 // newDirectory creates a new package directory tree with at most maxDepth 155 // levels, anchored at root. The result tree is pruned such that it only 156 // contains directories that contain package files or that contain 157 // subdirectories containing package files (transitively). If a non-nil 158 // pathFilter is provided, directory paths additionally must be accepted 159 // by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is 160 // provided for maxDepth, nodes at larger depths are pruned as well; they 161 // are assumed to contain package files even if their contents are not known 162 // (i.e., in this case the tree may contain directories w/o any package files). 163 // 164 func newDirectory(root string, maxDepth int) *Directory { 165 // The root could be a symbolic link so use Stat not Lstat. 166 d, err := fs.Stat(root) 167 // If we fail here, report detailed error messages; otherwise 168 // is is hard to see why a directory tree was not built. 169 switch { 170 case err != nil: 171 log.Printf("newDirectory(%s): %s", root, err) 172 return nil 173 case !isPkgDir(d): 174 log.Printf("newDirectory(%s): not a package directory", root) 175 return nil 176 } 177 if maxDepth < 0 { 178 maxDepth = 1e6 // "infinity" 179 } 180 b := treeBuilder{maxDepth} 181 // the file set provided is only for local parsing, no position 182 // information escapes and thus we don't need to save the set 183 return b.newDirTree(token.NewFileSet(), root, d.Name(), 0) 184 } 185 186 func (dir *Directory) writeLeafs(buf *bytes.Buffer) { 187 if dir != nil { 188 if len(dir.Dirs) == 0 { 189 buf.WriteString(dir.Path) 190 buf.WriteByte('\n') 191 return 192 } 193 194 for _, d := range dir.Dirs { 195 d.writeLeafs(buf) 196 } 197 } 198 } 199 200 func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) { 201 if dir != nil { 202 if !skipRoot { 203 c <- dir 204 } 205 for _, d := range dir.Dirs { 206 d.walk(c, false) 207 } 208 } 209 } 210 211 func (dir *Directory) iter(skipRoot bool) <-chan *Directory { 212 c := make(chan *Directory) 213 go func() { 214 dir.walk(c, skipRoot) 215 close(c) 216 }() 217 return c 218 } 219 220 func (dir *Directory) lookupLocal(name string) *Directory { 221 for _, d := range dir.Dirs { 222 if d.Name == name { 223 return d 224 } 225 } 226 return nil 227 } 228 229 func splitPath(p string) []string { 230 p = strings.TrimPrefix(p, "/") 231 if p == "" { 232 return nil 233 } 234 return strings.Split(p, "/") 235 } 236 237 // lookup looks for the *Directory for a given path, relative to dir. 238 func (dir *Directory) lookup(path string) *Directory { 239 d := splitPath(dir.Path) 240 p := splitPath(path) 241 i := 0 242 for i < len(d) { 243 if i >= len(p) || d[i] != p[i] { 244 return nil 245 } 246 i++ 247 } 248 for dir != nil && i < len(p) { 249 dir = dir.lookupLocal(p[i]) 250 i++ 251 } 252 return dir 253 } 254 255 // DirEntry describes a directory entry. The Depth and Height values 256 // are useful for presenting an entry in an indented fashion. 257 // 258 type DirEntry struct { 259 Depth int // >= 0 260 Height int // = DirList.MaxHeight - Depth, > 0 261 Path string // directory path; includes Name, relative to DirList root 262 Name string // directory name 263 HasPkg bool // true if the directory contains at least one package 264 Synopsis string // package documentation, if any 265 } 266 267 type DirList struct { 268 MaxHeight int // directory tree height, > 0 269 List []DirEntry 270 } 271 272 // listing creates a (linear) directory listing from a directory tree. 273 // If skipRoot is set, the root directory itself is excluded from the list. 274 // 275 func (root *Directory) listing(skipRoot bool) *DirList { 276 if root == nil { 277 return nil 278 } 279 280 // determine number of entries n and maximum height 281 n := 0 282 minDepth := 1 << 30 // infinity 283 maxDepth := 0 284 for d := range root.iter(skipRoot) { 285 n++ 286 if minDepth > d.Depth { 287 minDepth = d.Depth 288 } 289 if maxDepth < d.Depth { 290 maxDepth = d.Depth 291 } 292 } 293 maxHeight := maxDepth - minDepth + 1 294 295 if n == 0 { 296 return nil 297 } 298 299 // create list 300 list := make([]DirEntry, n) 301 i := 0 302 for d := range root.iter(skipRoot) { 303 p := &list[i] 304 p.Depth = d.Depth - minDepth 305 p.Height = maxHeight - p.Depth 306 // the path is relative to root.Path - remove the root.Path 307 // prefix (the prefix should always be present but avoid 308 // crashes and check) 309 path := strings.TrimPrefix(d.Path, root.Path) 310 // remove leading separator if any - path must be relative 311 path = strings.TrimPrefix(path, "/") 312 p.Path = path 313 p.Name = d.Name 314 p.HasPkg = d.HasPkg 315 p.Synopsis = d.Synopsis 316 i++ 317 } 318 319 return &DirList{maxHeight, list} 320 }