package sqan import ( "iter" "os" "path" "slices" "strings" "github.com/gobwas/glob" ) type Scanner struct { rootDir string extensions []string ignorePatterns []string compiledPatterns []*glob.Pattern onError func(error) } func (s *Scanner) OnError(onError func(error)) *Scanner { s.onError = onError return s } func (s *Scanner) Scan() iter.Seq[*File] { if err := s.compile(); err != nil { s.onError(err) } return func(yield func(*File) bool) { s.scan(s.rootDir, s.rootDir, yield) } } func (s *Scanner) compile() error { compiledPatterns := []*glob.Pattern{} for _, text := range s.ignorePatterns { pattern, err := glob.Compile(text) if err != nil { return err } compiledPatterns = append(compiledPatterns, pattern) } s.compiledPatterns = compiledPatterns return nil } func (s *Scanner) scan(rootDir, dir string, yield func(*File) bool) { entries, err := os.ReadDir(dir) if err != nil { s.onError(err) return } for _, entry := range entries { absPath := path.Join(dir, entry.Name()) ignore := false for _, pattern := range s.compiledPatterns { if pattern.Match(absPath) { ignore = true break } } if ignore { continue } if entry.IsDir() { s.scan(rootDir, absPath, yield) continue } relPath, relDir, filename, extension := ParsePath(rootDir, absPath) if len(s.extensions) > 0 && !slices.Contains(s.extensions, extension) { continue } info, err := entry.Info() if err != nil { s.onError(err) continue } f := &File{ RootDir: rootDir, AbsolutePath: absPath, RelativeDir: relDir, RelativePath: relPath, Filename: filename, Extension: extension, Size: info.Size(), Modified: info.ModTime(), } if !yield(f) { return } } } func (s *Scanner) WithExtensions(exts ...string) *Scanner { lower := []string{} for _, ext := range exts { lower = append(lower, strings.ToLower(ext)) } s.extensions = append(s.extensions, lower...) return s } func (s *Scanner) WithIgnorePatterns(patterns ...string) *Scanner { s.ignorePatterns = append(s.ignorePatterns, patterns...) return s } func NewScanner(rootDir string) *Scanner { if rootDir[0] != '/' { cwd, _ := os.Getwd() rootDir = path.Join(cwd, rootDir) } s := &Scanner{ rootDir: rootDir, onError: func(err error) {}, } return s }