github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/cmd/generates/sources/sources.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package sources 19 20 import ( 21 "github.com/aacfactory/errors" 22 "go/ast" 23 "go/parser" 24 "go/token" 25 "os" 26 "path/filepath" 27 "strings" 28 "sync" 29 ) 30 31 func newSource(path string, dir string) *Sources { 32 return &Sources{ 33 locker: &sync.Mutex{}, 34 dir: dir, 35 path: path, 36 readers: make(map[string]*SourceDirReader), 37 } 38 } 39 40 type Sources struct { 41 locker sync.Locker 42 dir string 43 path string 44 readers map[string]*SourceDirReader 45 } 46 47 func (sources *Sources) DestinationPath(path string) (v string, err error) { 48 sub, cut := strings.CutPrefix(path, sources.path+"/") 49 if !cut { 50 if path == sources.path { 51 v = filepath.ToSlash(sources.dir) 52 return 53 } 54 err = errors.Warning("sources: path is not in module").WithMeta("path", path).WithMeta("mod", sources.path) 55 return 56 } 57 v = filepath.ToSlash(filepath.Join(sources.dir, sub)) 58 return 59 } 60 61 func (sources *Sources) ReadFile(path string, name string) (file *ast.File, filename string, err error) { 62 sources.locker.Lock() 63 reader, has := sources.readers[path] 64 sources.locker.Unlock() 65 if has { 66 for _, sf := range reader.files { 67 _, sfn := filepath.Split(sf.filename) 68 if sfn == name { 69 file, err = sf.File() 70 return 71 } 72 } 73 err = errors.Warning("sources: read file failed").WithCause(errors.Warning("no file found")).WithMeta("path", path).WithMeta("file", name).WithMeta("mod", sources.path) 74 return 75 } 76 dir, dirErr := sources.DestinationPath(path) 77 if dirErr != nil { 78 err = errors.Warning("sources: read file failed").WithCause(dirErr).WithMeta("path", path).WithMeta("file", name).WithMeta("mod", sources.path) 79 return 80 } 81 filename = filepath.ToSlash(filepath.Join(dir, name)) 82 file, err = parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors|parser.ParseComments) 83 if err != nil { 84 err = errors.Warning("sources: read file failed").WithCause(err).WithMeta("path", path).WithMeta("file", name).WithMeta("mod", sources.path) 85 return 86 } 87 return 88 } 89 90 func (sources *Sources) getReader(path string) (reader *SourceDirReader, err error) { 91 sources.locker.Lock() 92 has := false 93 reader, has = sources.readers[path] 94 if !has { 95 dir, dirErr := sources.DestinationPath(path) 96 if dirErr != nil { 97 err = errors.Warning("sources: get source reader failed").WithCause(dirErr).WithMeta("path", path).WithMeta("mod", sources.path) 98 sources.locker.Unlock() 99 return 100 } 101 entries, readErr := os.ReadDir(dir) 102 if readErr != nil { 103 err = errors.Warning("sources: get source reader failed").WithCause(readErr).WithMeta("path", path).WithMeta("mod", sources.path) 104 sources.locker.Unlock() 105 return 106 } 107 if entries == nil || len(entries) == 0 { 108 err = errors.Warning("sources: get source reader failed").WithCause(errors.Warning("no entries found")).WithMeta("path", path).WithMeta("mod", sources.path) 109 sources.locker.Unlock() 110 return 111 } 112 files := make([]*SourceFile, 0, len(entries)) 113 for _, entry := range entries { 114 if entry.IsDir() || strings.HasSuffix(entry.Name(), "_test.go") || filepath.Ext(entry.Name()) != ".go" { 115 continue 116 } 117 files = append(files, &SourceFile{ 118 locker: &sync.Mutex{}, 119 parsed: false, 120 filename: filepath.ToSlash(filepath.Join(dir, entry.Name())), 121 file: nil, 122 err: nil, 123 }) 124 } 125 reader = &SourceDirReader{ 126 locker: &sync.Mutex{}, 127 files: files, 128 } 129 sources.readers[path] = reader 130 } 131 sources.locker.Unlock() 132 return 133 } 134 135 func (sources *Sources) ReadDir(path string, fn func(file *ast.File, filename string) (err error)) (err error) { 136 reader, readerErr := sources.getReader(path) 137 if readerErr != nil { 138 err = errors.Warning("sources: read source dir failed").WithCause(readerErr).WithMeta("path", path).WithMeta("mod", sources.path) 139 return 140 } 141 err = reader.Each(fn) 142 return 143 } 144 145 func (sources *Sources) FindFileInDir(path string, matcher func(file *ast.File) (ok bool)) (file *ast.File, err error) { 146 reader, readerErr := sources.getReader(path) 147 if readerErr != nil { 148 err = errors.Warning("sources: find file in source dir failed").WithCause(readerErr).WithMeta("path", path).WithMeta("mod", sources.path) 149 return 150 } 151 file, err = reader.Find(matcher) 152 return 153 } 154 155 func (sources *Sources) FindTypeSpec(path string, name string) (spec *ast.TypeSpec, imports Imports, genericDoc string, err error) { 156 reader, readerErr := sources.getReader(path) 157 if readerErr != nil { 158 err = errors.Warning("sources: find type spec in source dir failed"). 159 WithCause(readerErr). 160 WithMeta("path", path).WithMeta("name", name).WithMeta("mod", sources.path) 161 return 162 } 163 for _, sf := range reader.files { 164 file, fileErr := sf.File() 165 if fileErr != nil { 166 err = errors.Warning("sources: find type spec in source dir failed"). 167 WithCause(fileErr). 168 WithMeta("path", path).WithMeta("name", name).WithMeta("mod", sources.path) 169 return 170 } 171 if file.Decls == nil || len(file.Decls) == 0 { 172 continue 173 } 174 for _, declaration := range file.Decls { 175 genDecl, isGenDecl := declaration.(*ast.GenDecl) 176 if !isGenDecl { 177 continue 178 } 179 if genDecl.Specs == nil || len(genDecl.Specs) == 0 { 180 continue 181 } 182 for _, s := range genDecl.Specs { 183 ts, isType := s.(*ast.TypeSpec) 184 if !isType { 185 continue 186 } 187 if ts.Name.Name == name { 188 spec = ts 189 imports = NewImportsFromAstFileImports(file.Imports) 190 if genDecl.Doc != nil { 191 genericDoc = genDecl.Doc.Text() 192 } 193 return 194 } 195 } 196 } 197 } 198 err = errors.Warning("sources: find type spec in source dir failed"). 199 WithCause(errors.Warning("sources: not found")). 200 WithMeta("path", path).WithMeta("name", name).WithMeta("mod", sources.path) 201 return 202 } 203 204 type SourceDirReader struct { 205 locker sync.Locker 206 files []*SourceFile 207 } 208 209 func (reader *SourceDirReader) Each(fn func(file *ast.File, filename string) (err error)) (err error) { 210 for _, sf := range reader.files { 211 file, fileErr := sf.File() 212 if fileErr != nil { 213 err = fileErr 214 return 215 } 216 err = fn(file, sf.filename) 217 if err != nil { 218 return 219 } 220 } 221 return 222 } 223 224 func (reader *SourceDirReader) Find(matcher func(file *ast.File) (ok bool)) (file *ast.File, err error) { 225 for _, sf := range reader.files { 226 file, err = sf.File() 227 if err != nil { 228 return 229 } 230 ok := matcher(file) 231 if ok { 232 return 233 } 234 } 235 err = errors.Warning("sources: source file was not found") 236 return 237 } 238 239 type SourceFile struct { 240 locker sync.Locker 241 parsed bool 242 filename string 243 file *ast.File 244 err error 245 } 246 247 func (sf *SourceFile) File() (file *ast.File, err error) { 248 sf.locker.Lock() 249 defer sf.locker.Unlock() 250 if !sf.parsed { 251 file, err = parser.ParseFile(token.NewFileSet(), sf.filename, nil, parser.AllErrors|parser.ParseComments) 252 if err != nil { 253 err = errors.Warning("sources: parse source failed").WithCause(err).WithMeta("file", sf.filename) 254 sf.err = err 255 } else { 256 sf.file = file 257 } 258 sf.parsed = true 259 return 260 } 261 file = sf.file 262 err = sf.err 263 return 264 }