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  }