2026-08-31 22:25:00 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 00:19:08 +01:00
|
|
|
relPath, relDir, filename, extension := ParsePath(rootDir, absPath)
|
|
|
|
|
if len(s.extensions) > 0 && !slices.Contains(s.extensions, extension) {
|
2026-08-31 22:25:00 +01:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info, err := entry.Info()
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.onError(err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 00:19:08 +01:00
|
|
|
f := &File{
|
|
|
|
|
RootDir: rootDir,
|
|
|
|
|
AbsolutePath: absPath,
|
|
|
|
|
RelativeDir: relDir,
|
|
|
|
|
RelativePath: relPath,
|
|
|
|
|
Filename: filename,
|
|
|
|
|
Extension: extension,
|
|
|
|
|
Size: info.Size(),
|
|
|
|
|
Modified: info.ModTime(),
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 22:25:00 +01:00
|
|
|
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
|
|
|
|
|
}
|