You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
6.6 KiB
Go
354 lines
6.6 KiB
Go
// Package finder provide a finding tool for find files or dirs,
|
|
// and with some built-in matchers.
|
|
package finder
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FileFinder type alias.
|
|
type FileFinder = Finder
|
|
|
|
// Finder struct
|
|
type Finder struct {
|
|
// config for finder
|
|
c *Config
|
|
// last error
|
|
err error
|
|
// num - founded fs elem number
|
|
num int
|
|
// ch - founded fs elem chan
|
|
ch chan Elem
|
|
// caches - cache found fs elem. if config.CacheResult is true
|
|
caches []Elem
|
|
}
|
|
|
|
// New instance with source dir paths.
|
|
func New(dirs []string) *Finder {
|
|
c := NewConfig(dirs...)
|
|
return NewWithConfig(c)
|
|
}
|
|
|
|
// NewFinder new instance with source dir paths.
|
|
func NewFinder(dirPaths ...string) *Finder { return New(dirPaths) }
|
|
|
|
// NewWithConfig new instance with config.
|
|
func NewWithConfig(c *Config) *Finder {
|
|
return &Finder{c: c}
|
|
}
|
|
|
|
// NewEmpty new empty Finder instance
|
|
func NewEmpty() *Finder {
|
|
return &Finder{c: NewEmptyConfig()}
|
|
}
|
|
|
|
// EmptyFinder new empty Finder instance. alias of NewEmpty()
|
|
func EmptyFinder() *Finder { return NewEmpty() }
|
|
|
|
//
|
|
// --------- do finding ---------
|
|
//
|
|
|
|
// Find files in given dir paths. will return a channel, you can use it to get the result.
|
|
//
|
|
// Usage:
|
|
//
|
|
// f := NewFinder("/path/to/dir").Find()
|
|
// for el := range f {
|
|
// fmt.Println(el.Path())
|
|
// }
|
|
func (f *Finder) Find() <-chan Elem {
|
|
f.find()
|
|
return f.ch
|
|
}
|
|
|
|
// Elems find and return founded file Elem. alias of Find()
|
|
func (f *Finder) Elems() <-chan Elem { return f.Find() }
|
|
|
|
// Results find and return founded file Elem. alias of Find()
|
|
func (f *Finder) Results() <-chan Elem { return f.Find() }
|
|
|
|
// FindNames find and return founded file/dir names.
|
|
func (f *Finder) FindNames() []string {
|
|
paths := make([]string, 0, 8*len(f.c.ScanDirs))
|
|
for el := range f.Find() {
|
|
paths = append(paths, el.Name())
|
|
}
|
|
return paths
|
|
}
|
|
|
|
// FindPaths find and return founded file/dir paths.
|
|
func (f *Finder) FindPaths() []string {
|
|
paths := make([]string, 0, 8*len(f.c.ScanDirs))
|
|
for el := range f.Find() {
|
|
paths = append(paths, el.Path())
|
|
}
|
|
return paths
|
|
}
|
|
|
|
// Each founded file or dir Elem.
|
|
func (f *Finder) Each(fn func(el Elem)) { f.EachElem(fn) }
|
|
|
|
// EachElem founded file or dir Elem.
|
|
func (f *Finder) EachElem(fn func(el Elem)) {
|
|
f.find()
|
|
for el := range f.ch {
|
|
fn(el)
|
|
}
|
|
}
|
|
|
|
// EachPath founded file paths.
|
|
func (f *Finder) EachPath(fn func(filePath string)) {
|
|
f.EachElem(func(el Elem) {
|
|
fn(el.Path())
|
|
})
|
|
}
|
|
|
|
// EachFile each file os.File
|
|
func (f *Finder) EachFile(fn func(file *os.File)) {
|
|
f.EachElem(func(el Elem) {
|
|
file, err := os.Open(el.Path())
|
|
if err == nil {
|
|
fn(file)
|
|
} else {
|
|
f.err = err
|
|
}
|
|
})
|
|
}
|
|
|
|
// EachStat each file os.FileInfo
|
|
func (f *Finder) EachStat(fn func(fi os.FileInfo, filePath string)) {
|
|
f.EachElem(func(el Elem) {
|
|
fi, err := el.Info()
|
|
if err == nil {
|
|
fn(fi, el.Path())
|
|
} else {
|
|
f.err = err
|
|
}
|
|
})
|
|
}
|
|
|
|
// EachContents handle each found file contents
|
|
func (f *Finder) EachContents(fn func(contents, filePath string)) {
|
|
f.EachElem(func(el Elem) {
|
|
bs, err := os.ReadFile(el.Path())
|
|
if err == nil {
|
|
fn(string(bs), el.Path())
|
|
} else {
|
|
f.err = err
|
|
}
|
|
})
|
|
}
|
|
|
|
// prepare for find.
|
|
func (f *Finder) prepare() {
|
|
f.err = nil
|
|
f.ch = make(chan Elem, 8)
|
|
|
|
if f.CacheNum() == 0 {
|
|
f.num = 0
|
|
}
|
|
|
|
if f.c == nil {
|
|
f.c = NewConfig()
|
|
} else {
|
|
f.c.Init()
|
|
}
|
|
}
|
|
|
|
// do finding
|
|
func (f *Finder) find() {
|
|
f.prepare()
|
|
|
|
go func() {
|
|
defer close(f.ch)
|
|
|
|
// read from caches
|
|
if f.c.CacheResult && len(f.caches) > 0 {
|
|
for _, el := range f.caches {
|
|
f.ch <- el
|
|
}
|
|
return
|
|
}
|
|
|
|
// do finding
|
|
var err error
|
|
for _, dirPath := range f.c.ScanDirs {
|
|
if f.c.UseAbsPath {
|
|
dirPath, err = filepath.Abs(dirPath)
|
|
if err != nil {
|
|
f.err = err
|
|
continue
|
|
}
|
|
}
|
|
|
|
f.c.depth = 0
|
|
f.findDir(dirPath, f.c)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// code refer filepath.glob()
|
|
func (f *Finder) findDir(dirPath string, c *Config) {
|
|
des, err := os.ReadDir(dirPath)
|
|
if err != nil {
|
|
return // ignore I/O error
|
|
}
|
|
|
|
var ok bool
|
|
c.depth++
|
|
for _, ent := range des {
|
|
name := ent.Name()
|
|
isDir := ent.IsDir()
|
|
if name[0] == '.' {
|
|
if isDir {
|
|
if c.ExcludeDotDir {
|
|
continue
|
|
}
|
|
} else if c.ExcludeDotFile {
|
|
continue
|
|
}
|
|
}
|
|
|
|
fullPath := filepath.Join(dirPath, name)
|
|
el := NewElem(fullPath, ent)
|
|
|
|
// apply generic filters
|
|
if !applyExMatchers(el, c.ExMatchers) {
|
|
continue
|
|
}
|
|
|
|
// --- dir: apply dir filters
|
|
if isDir {
|
|
if !applyExMatchers(el, c.DirExMatchers) {
|
|
continue
|
|
}
|
|
|
|
if len(c.Matchers) > 0 {
|
|
ok = applyMatchers(el, c.Matchers)
|
|
if !ok && len(c.DirMatchers) > 0 {
|
|
ok = applyMatchers(el, c.DirMatchers)
|
|
}
|
|
} else {
|
|
ok = applyMatchers(el, c.DirMatchers)
|
|
}
|
|
|
|
if ok && c.FindFlags&FlagDir > 0 {
|
|
if c.CacheResult {
|
|
f.caches = append(f.caches, el)
|
|
}
|
|
f.num++
|
|
f.ch <- el
|
|
|
|
if c.FindFlags == FlagDir {
|
|
continue // only find subdir on ok=false
|
|
}
|
|
}
|
|
|
|
// find in sub dir.
|
|
if c.MaxDepth == 0 || c.depth < c.MaxDepth {
|
|
f.findDir(fullPath, c)
|
|
c.depth-- // restore depth
|
|
}
|
|
continue
|
|
}
|
|
|
|
// --- type: file
|
|
if c.FindFlags&FlagFile == 0 {
|
|
continue
|
|
}
|
|
|
|
// apply file filters
|
|
if !applyExMatchers(el, c.FileExMatchers) {
|
|
continue
|
|
}
|
|
|
|
if len(c.Matchers) > 0 {
|
|
ok = applyMatchers(el, c.Matchers)
|
|
if !ok && len(c.FileMatchers) > 0 {
|
|
ok = applyMatchers(el, c.FileMatchers)
|
|
}
|
|
} else {
|
|
ok = applyMatchers(el, c.FileMatchers)
|
|
}
|
|
|
|
// write to consumer
|
|
if ok && c.FindFlags&FlagFile > 0 {
|
|
if c.CacheResult {
|
|
f.caches = append(f.caches, el)
|
|
}
|
|
f.num++
|
|
f.ch <- el
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyMatchers(el Elem, fls []Matcher) bool {
|
|
for _, f := range fls {
|
|
if f.Apply(el) {
|
|
return true
|
|
}
|
|
}
|
|
return len(fls) == 0
|
|
}
|
|
|
|
func applyExMatchers(el Elem, fls []Matcher) bool {
|
|
for _, f := range fls {
|
|
if f.Apply(el) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Reset filters config setting and results info.
|
|
func (f *Finder) Reset() {
|
|
c := NewConfig(f.c.ScanDirs...)
|
|
c.ExcludeDotDir = f.c.ExcludeDotDir
|
|
c.FindFlags = f.c.FindFlags
|
|
c.MaxDepth = f.c.MaxDepth
|
|
|
|
f.c = c
|
|
f.ResetResult()
|
|
}
|
|
|
|
// ResetResult reset result info.
|
|
func (f *Finder) ResetResult() {
|
|
f.num = 0
|
|
f.err = nil
|
|
f.ch = make(chan Elem, 8)
|
|
f.caches = []Elem{}
|
|
}
|
|
|
|
// Num get found elem num. only valid after finding.
|
|
func (f *Finder) Num() int {
|
|
return f.num
|
|
}
|
|
|
|
// Err get last error
|
|
func (f *Finder) Err() error {
|
|
return f.err
|
|
}
|
|
|
|
// Caches get cached results. only valid after finding.
|
|
func (f *Finder) Caches() []Elem {
|
|
return f.caches
|
|
}
|
|
|
|
// CacheNum get
|
|
func (f *Finder) CacheNum() int {
|
|
return len(f.caches)
|
|
}
|
|
|
|
// Config get
|
|
func (f *Finder) Config() Config {
|
|
return *f.c
|
|
}
|
|
|
|
// String all dir paths
|
|
func (f *Finder) String() string {
|
|
return strings.Join(f.c.ScanDirs, ";")
|
|
}
|