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.
ntool/nfs/finder/finder.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, ";")
}