github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/website/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 // "fmt" 12 "go/doc" 13 "go/parser" 14 "go/token" 15 "io/ioutil" 16 "log" 17 "os" 18 pathpkg "path" 19 "strings" 20 ) 21 22 // Conventional name for directories containing test data. 23 // Excluded from directory trees. 24 // 25 const testdataDirName = "testdata" 26 27 type Directory struct { 28 Depth int 29 Path string // directory path; includes Name 30 Name string // directory name 31 HasPkg bool // true if the directory contains at least one package 32 Synopsis string // package documentation, if any 33 Dirs []*Directory // subdirectories 34 } 35 36 func isGoFile(fi os.FileInfo) bool { 37 name := fi.Name() 38 return !fi.IsDir() && 39 len(name) > 0 && name[0] != '.' && // ignore .files 40 pathpkg.Ext(name) == ".go" 41 } 42 43 func isPkgFile(fi os.FileInfo) bool { 44 return isGoFile(fi) && 45 !strings.HasSuffix(fi.Name(), "_test.go") && // ignore test files 46 !strings.HasSuffix(fi.Name(), fileembedPattern) 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] != '.' // ignore _files and .files 53 } 54 55 type treeBuilder struct { 56 maxDepth int 57 } 58 59 func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory { 60 if name == testdataDirName { 61 return nil 62 } 63 64 if depth >= b.maxDepth { 65 // return a dummy directory so that the parent directory 66 // doesn't get discarded just because we reached the max 67 // directory depth 68 return &Directory{ 69 Depth: depth, 70 Path: path, 71 Name: name, 72 } 73 } 74 75 list, err := ioutil.ReadDir(path) 76 if err != nil { 77 log.Printf("Could not read %v\n", path) 78 return nil 79 } 80 81 // determine number of subdirectories and if there are package files 82 ndirs := 0 83 hasPkgFiles := false 84 var synopses [4]string // prioritized package documentation (0 == highest priority) 85 for _, d := range list { 86 switch { 87 case isPkgDir(d): 88 ndirs++ 89 case isPkgFile(d): 90 // looks like a package file, but may just be a file ending in ".go"; 91 // don't just count it yet (otherwise we may end up with hasPkgFiles even 92 // though the directory doesn't contain any real package files - was bug) 93 if synopses[0] == "" { 94 // no "optimal" package synopsis yet; continue to collect synopses 95 src, err := ioutil.ReadFile(pathpkg.Join(path, d.Name())) 96 if err != nil { 97 log.Printf("Could not read %v\n", pathpkg.Join(path, d.Name())) 98 continue 99 } 100 file, err := parser.ParseFile(fset, pathpkg.Join(path, d.Name()), 101 src, parser.ParseComments|parser.PackageClauseOnly) 102 if err == nil { 103 hasPkgFiles = true 104 if file.Doc != nil { 105 // prioritize documentation 106 i := -1 107 switch file.Name.Name { 108 case name: 109 i = 0 // normal case: directory name matches package name 110 case "main": 111 i = 2 // directory contains a main package 112 default: 113 i = 3 // none of the above 114 } 115 if 0 <= i && i < len(synopses) && synopses[i] == "" { 116 synopses[i] = doc.Synopsis(file.Doc.Text()) 117 } 118 } 119 } 120 } 121 } 122 } 123 124 // create subdirectory tree 125 var dirs []*Directory 126 if ndirs > 0 { 127 dirs = make([]*Directory, ndirs) 128 i := 0 129 for _, d := range list { 130 if isPkgDir(d) { 131 name := d.Name() 132 dd := b.newDirTree(fset, pathpkg.Join(path, name), name, depth+1) 133 if dd != nil { 134 dirs[i] = dd 135 i++ 136 } 137 } 138 } 139 dirs = dirs[0:i] 140 } 141 142 // if there are no package files and no subdirectories 143 // containing package files, ignore the directory 144 if !hasPkgFiles && len(dirs) == 0 { 145 return nil 146 } 147 148 // select the highest-priority synopsis for the directory entry, if any 149 synopsis := "" 150 for _, synopsis = range synopses { 151 if synopsis != "" { 152 break 153 } 154 } 155 156 return &Directory{ 157 Depth: depth, 158 Path: path, 159 Name: name, 160 HasPkg: hasPkgFiles, 161 Synopsis: synopsis, 162 Dirs: dirs, 163 } 164 } 165 166 // newDirectory creates a new package directory tree with at most maxDepth 167 // levels, anchored at root. The result tree is pruned such that it only 168 // contains directories that contain package files or that contain 169 // subdirectories containing package files (transitively). If a non-nil 170 // pathFilter is provided, directory paths additionally must be accepted 171 // by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is 172 // provided for maxDepth, nodes at larger depths are pruned as well; they 173 // are assumed to contain package files even if their contents are not known 174 // (i.e., in this case the tree may contain directories w/o any package files). 175 // 176 func newDirectory(root string, maxDepth int) *Directory { 177 // The root could be a symbolic link so use Stat not Lstat. 178 d, err := os.Stat(root) 179 // If we fail here, report detailed error messages; otherwise 180 // is is hard to see why a directory tree was not built. 181 switch { 182 case err != nil: 183 log.Printf("newDirectory(%s): %s", root, err) 184 return nil 185 case !isPkgDir(d): 186 log.Printf("newDirectory(%s): not a package directory", root) 187 return nil 188 } 189 if maxDepth < 0 { 190 maxDepth = 1e6 // "infinity" 191 } 192 b := treeBuilder{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 if strings.HasPrefix(p, "/") { 243 p = p[1:] 244 } 245 if p == "" { 246 return nil 247 } 248 return strings.Split(p, "/") 249 } 250 251 // lookup looks for the *Directory for a given path, relative to dir. 252 func (dir *Directory) lookup(path string) *Directory { 253 d := splitPath(dir.Path) 254 p := splitPath(path) 255 i := 0 256 for i < len(d) { 257 if i >= len(p) || d[i] != p[i] { 258 return nil 259 } 260 i++ 261 } 262 for dir != nil && i < len(p) { 263 dir = dir.lookupLocal(p[i]) 264 i++ 265 } 266 return dir 267 } 268 269 // DirEntry describes a directory entry. The Depth and Height values 270 // are useful for presenting an entry in an indented fashion. 271 // 272 type DirEntry struct { 273 Depth int // >= 0 274 Height int // = DirList.MaxHeight - Depth, > 0 275 Path string // directory path; includes Name, absolute, with the camli dir as root 276 Name string // directory name 277 HasPkg bool // true if the directory contains at least one package 278 Synopsis string // package documentation, if any 279 } 280 281 type DirList struct { 282 MaxHeight int // directory tree height, > 0 283 List []DirEntry 284 } 285 286 // listing creates a (linear) directory listing from a directory tree. 287 // If skipRoot is set, the root directory itself is excluded from the list. 288 // 289 func (root *Directory) listing(skipRoot 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, n) 315 i := 0 316 for d := range root.iter(skipRoot) { 317 p := &list[i] 318 p.Depth = d.Depth - minDepth 319 p.Height = maxHeight - p.Depth 320 // the suffix is absolute, with the camlistore dir as the root 321 idx := strings.LastIndex(d.Path, domainName) 322 if idx == -1 { 323 log.Fatalf("No \"%s\" in path to file %s", domainName, d.Path) 324 } 325 suffix := pathpkg.Clean(d.Path[idx+len(domainName):]) 326 327 p.Path = suffix 328 p.Name = d.Name 329 p.HasPkg = d.HasPkg 330 p.Synopsis = d.Synopsis 331 i++ 332 } 333 334 return &DirList{maxHeight, list} 335 }