github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/doc/dirs.go (about) 1 // Mostly copied from go source at tip, commit d922c0a. 2 // 3 // Copyright 2015 The Go Authors. All rights reserved. 4 5 package doc 6 7 import ( 8 "log" 9 "os" 10 "path" 11 "path/filepath" 12 "sort" 13 "strings" 14 15 "github.com/gnolang/gno/gnovm/pkg/gnomod" 16 ) 17 18 // A bfsDir describes a directory holding code by specifying 19 // the expected import path and the file system directory. 20 type bfsDir struct { 21 importPath string // import path for that dir 22 dir string // file system directory 23 } 24 25 // dirs is a structure for scanning the directory tree. 26 // Its Next method returns the next Go source directory it finds. 27 // Although it can be used to scan the tree multiple times, it 28 // only walks the tree once, caching the data it finds. 29 type bfsDirs struct { 30 scan chan bfsDir // Directories generated by walk. 31 hist []bfsDir // History of reported Dirs. 32 offset int // Counter for Next. 33 } 34 35 // newDirs begins scanning the given stdlibs directory. 36 // dirs are "gopath-like" directories, such as @/gnovm/stdlibs, whose path 37 // relative to the root specify the import path. 38 // modDirs are user directories, expected to have gno.mod files 39 func newDirs(dirs []string, modDirs []string) *bfsDirs { 40 d := &bfsDirs{ 41 hist: make([]bfsDir, 0, 256), 42 scan: make(chan bfsDir), 43 } 44 45 roots := make([]bfsDir, 0, len(dirs)+len(modDirs)) 46 for _, dir := range dirs { 47 roots = append(roots, bfsDir{ 48 dir: dir, 49 importPath: "", 50 }) 51 } 52 53 for _, mdir := range modDirs { 54 gm, err := gnomod.ParseGnoMod(filepath.Join(mdir, "gno.mod")) 55 if err != nil { 56 log.Printf("%v", err) 57 continue 58 } 59 roots = append(roots, bfsDir{ 60 dir: mdir, 61 importPath: gm.Module.Mod.Path, 62 }) 63 roots = append(roots, getGnoModDirs(gm)...) 64 } 65 66 go d.walk(roots) 67 return d 68 } 69 70 func getGnoModDirs(gm *gnomod.File) []bfsDir { 71 // cmd/go makes use of the go list command, we don't have that here. 72 73 dirs := make([]bfsDir, 0, len(gm.Require)) 74 for _, r := range gm.Require { 75 mv := gm.Resolve(r) 76 path := gnomod.PackageDir("", mv) 77 if _, err := os.Stat(path); err != nil { 78 // only give directories which actually exist and don't give 79 // an error when accessing 80 if !os.IsNotExist(err) { 81 log.Println("open source directories from gno.mod:", err) 82 } 83 continue 84 } 85 dirs = append(dirs, bfsDir{ 86 importPath: mv.Path, 87 dir: path, 88 }) 89 } 90 91 return dirs 92 } 93 94 // Reset puts the scan back at the beginning. 95 func (d *bfsDirs) Reset() { 96 d.offset = 0 97 } 98 99 // Next returns the next directory in the scan. The boolean 100 // is false when the scan is done. 101 func (d *bfsDirs) Next() (bfsDir, bool) { 102 if d.offset < len(d.hist) { 103 dir := d.hist[d.offset] 104 d.offset++ 105 return dir, true 106 } 107 dir, ok := <-d.scan 108 if !ok { 109 return bfsDir{}, false 110 } 111 d.hist = append(d.hist, dir) 112 d.offset++ 113 return dir, ok 114 } 115 116 // walk walks the trees in the given roots. 117 func (d *bfsDirs) walk(roots []bfsDir) { 118 for _, root := range roots { 119 d.bfsWalkRoot(root) 120 } 121 close(d.scan) 122 } 123 124 // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. 125 // Each Go source directory it finds is delivered on d.scan. 126 func (d *bfsDirs) bfsWalkRoot(root bfsDir) { 127 root.dir = filepath.Clean(root.dir) 128 129 // this is the queue of directories to examine in this pass. 130 this := []bfsDir{} 131 // next is the queue of directories to examine in the next pass. 132 next := []bfsDir{root} 133 134 for len(next) > 0 { 135 this, next = next, this[:0] 136 for _, dir := range this { 137 fd, err := os.Open(dir.dir) 138 if err != nil { 139 log.Print(err) 140 continue 141 } 142 143 // read dir entries. 144 entries, err := fd.ReadDir(0) 145 fd.Close() 146 if err != nil { 147 log.Print(err) 148 continue 149 } 150 151 // stop at module boundaries 152 if dir.dir != root.dir && containsGnoMod(entries) { 153 continue 154 } 155 156 hasGnoFiles := false 157 for _, entry := range entries { 158 name := entry.Name() 159 // For plain files, remember if this directory contains any .gno 160 // source files, but ignore them otherwise. 161 if !entry.IsDir() { 162 if !hasGnoFiles && strings.HasSuffix(name, ".gno") { 163 hasGnoFiles = true 164 } 165 continue 166 } 167 // Entry is a directory. 168 169 // Ignore same directories ignored by the go tool. 170 if name[0] == '.' || name[0] == '_' || name == "testdata" { 171 continue 172 } 173 // Remember this (fully qualified) directory for the next pass. 174 next = append(next, bfsDir{ 175 dir: filepath.Join(dir.dir, name), 176 importPath: path.Join(dir.importPath, name), 177 }) 178 } 179 if hasGnoFiles { 180 // It's a candidate. 181 d.scan <- dir 182 } 183 } 184 } 185 } 186 187 func containsGnoMod(entries []os.DirEntry) bool { 188 for _, entry := range entries { 189 if entry.Name() == "gno.mod" && !entry.IsDir() { 190 return true 191 } 192 } 193 return false 194 } 195 196 // findPackage finds a package iterating over d where the import path has 197 // name as a suffix (which may be a package name or a fully-qualified path). 198 // returns a list of possible directories. If a directory's import path matched 199 // exactly, it will be returned as first. 200 func (d *bfsDirs) findPackage(name string) []bfsDir { 201 d.Reset() 202 candidates := make([]bfsDir, 0, 4) 203 for dir, ok := d.Next(); ok; dir, ok = d.Next() { 204 // want either exact matches or suffixes 205 if dir.importPath == name || strings.HasSuffix(dir.importPath, "/"+name) { 206 candidates = append(candidates, dir) 207 } 208 } 209 sort.Slice(candidates, func(i, j int) bool { 210 // prefer shorter paths -- if we have an exact match it will be of the 211 // shortest possible pkg path. 212 ci := strings.Count(candidates[i].importPath, "/") 213 cj := strings.Count(candidates[j].importPath, "/") 214 if ci != cj { 215 return ci < cj 216 } 217 // use alphabetical ordering otherwise. 218 return candidates[i].importPath < candidates[j].importPath 219 }) 220 return candidates 221 } 222 223 // findDir determines if the given absdir is present in the Dirs. 224 // If not, the nil slice is returned. It returns always at most one dir. 225 func (d *bfsDirs) findDir(absdir string) []bfsDir { 226 d.Reset() 227 for dir, ok := d.Next(); ok; dir, ok = d.Next() { 228 if dir.dir == absdir { 229 return []bfsDir{dir} 230 } 231 } 232 return nil 233 }