2024-07-30 14:43:54 -04:00
|
|
|
// Package filter filters files based on specific criteria
|
2024-06-18 18:21:03 -04:00
|
|
|
package filter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-07-15 23:15:20 -04:00
|
|
|
"io/fs"
|
2024-06-18 18:21:03 -04:00
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"slices"
|
2024-06-19 22:13:10 -04:00
|
|
|
"strings"
|
2024-06-18 18:21:03 -04:00
|
|
|
"time"
|
|
|
|
|
2024-06-30 21:39:23 -04:00
|
|
|
"github.com/charmbracelet/log"
|
|
|
|
"github.com/dustin/go-humanize"
|
2024-06-18 18:21:03 -04:00
|
|
|
"github.com/ijt/go-anytime"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Filter struct {
|
2024-06-19 22:13:10 -04:00
|
|
|
on, before, after time.Time
|
|
|
|
glob, pattern string
|
|
|
|
unglob, unpattern string
|
|
|
|
filenames []string
|
|
|
|
dirsonly, filesonly bool
|
2024-07-03 13:17:08 -04:00
|
|
|
ignorehidden bool
|
2024-06-19 22:13:10 -04:00
|
|
|
matcher *regexp.Regexp
|
|
|
|
unmatcher *regexp.Regexp
|
2024-06-30 21:39:23 -04:00
|
|
|
minsize, maxsize int64
|
2024-07-16 00:05:21 -04:00
|
|
|
mode fs.FileMode
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Filter) On() time.Time { return f.on }
|
|
|
|
func (f *Filter) After() time.Time { return f.after }
|
|
|
|
func (f *Filter) Before() time.Time { return f.before }
|
|
|
|
func (f *Filter) Glob() string { return f.glob }
|
|
|
|
func (f *Filter) Pattern() string { return f.pattern }
|
|
|
|
func (f *Filter) FileNames() []string { return f.filenames }
|
2024-06-19 22:13:10 -04:00
|
|
|
func (f *Filter) FilesOnly() bool { return f.filesonly }
|
|
|
|
func (f *Filter) DirsOnly() bool { return f.dirsonly }
|
2024-07-03 13:17:08 -04:00
|
|
|
func (f *Filter) IgnoreHidden() bool { return f.ignorehidden }
|
2024-06-30 21:39:23 -04:00
|
|
|
func (f *Filter) MinSize() int64 { return f.minsize }
|
|
|
|
func (f *Filter) MaxSize() int64 { return f.maxsize }
|
2024-07-16 00:05:21 -04:00
|
|
|
func (f *Filter) Mode() fs.FileMode { return f.mode }
|
2024-06-18 18:21:03 -04:00
|
|
|
|
2024-06-19 20:14:50 -04:00
|
|
|
func (f *Filter) AddFileName(filename string) {
|
|
|
|
filename = filepath.Clean(filename)
|
|
|
|
f.filenames = append(f.filenames, filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Filter) AddFileNames(filenames ...string) {
|
|
|
|
for _, filename := range filenames {
|
|
|
|
f.AddFileName(filename)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-15 23:15:20 -04:00
|
|
|
func (f *Filter) Match(info fs.FileInfo) bool {
|
|
|
|
filename := info.Name()
|
|
|
|
modified := info.ModTime()
|
|
|
|
isdir := info.IsDir()
|
|
|
|
size := info.Size()
|
2024-07-16 00:05:21 -04:00
|
|
|
mode := info.Mode()
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-18 18:21:03 -04:00
|
|
|
// on or before/after, not both
|
|
|
|
if !f.on.IsZero() {
|
2024-07-30 14:43:54 -04:00
|
|
|
if !sameDay(f.on, modified) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s: %s isn't on %s, bye!", filename, modified, f.on)
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !f.after.IsZero() && f.after.After(modified) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s: %s isn't after %s, bye!", filename, modified, f.after)
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !f.before.IsZero() && f.before.Before(modified) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s: %s isn't before %s, bye!", filename, modified, f.before)
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
if f.hasRegex() && !f.matcher.MatchString(filename) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s doesn't match `%s`, bye!", filename, f.matcher.String())
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-18 18:21:03 -04:00
|
|
|
if f.glob != "" {
|
|
|
|
if match, err := filepath.Match(f.glob, filename); err != nil || !match {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s doesn't match `%s`, bye!", filename, f.glob)
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
if f.hasUnregex() && f.unmatcher.MatchString(filename) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s matches `%s`, bye!", filename, f.unmatcher.String())
|
2024-06-18 18:56:55 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-18 18:56:55 -04:00
|
|
|
if f.unglob != "" {
|
|
|
|
if match, err := filepath.Match(f.unglob, filename); err != nil || match {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s matches `%s`, bye!", filename, f.unglob)
|
2024-06-18 18:56:55 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-18 18:21:03 -04:00
|
|
|
if len(f.filenames) > 0 && !slices.Contains(f.filenames, filename) {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s isn't in %v, bye!", filename, f.filenames)
|
2024-06-18 18:21:03 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-19 22:13:10 -04:00
|
|
|
if f.filesonly && isdir {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s is dir, bye!", filename)
|
2024-06-19 22:13:10 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-19 22:13:10 -04:00
|
|
|
if f.dirsonly && !isdir {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s is file, bye!", filename)
|
2024-06-19 22:13:10 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-07-03 13:17:08 -04:00
|
|
|
if f.ignorehidden && strings.HasPrefix(filename, ".") {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s is hidden, bye!", filename)
|
2024-06-19 22:13:10 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-06-30 21:17:42 -04:00
|
|
|
|
2024-06-30 21:39:23 -04:00
|
|
|
if f.maxsize != 0 && f.maxsize < size {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s is larger than %d, bye!", filename, f.maxsize)
|
2024-06-30 21:39:23 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.minsize != 0 && f.minsize > size {
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s is smaller than %d, bye!", filename, f.minsize)
|
2024-06-30 21:39:23 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-07-16 00:05:21 -04:00
|
|
|
if f.mode != 0 && f.mode != mode && f.mode-fs.ModeDir != mode {
|
|
|
|
log.Debugf("%s mode:'%s' (%d) isn't '%s' (%d), bye!", filename, mode, mode, f.mode, f.mode)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-06-18 18:21:03 -04:00
|
|
|
// okay it was good
|
2024-07-15 23:56:29 -04:00
|
|
|
log.Debugf("%s modified:'%s' dir:'%T' mode:'%s' was a good one!", filename, modified, isdir, mode)
|
2024-06-18 18:21:03 -04:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Filter) SetPattern(pattern string) error {
|
|
|
|
var err error
|
|
|
|
f.pattern = pattern
|
|
|
|
f.matcher, err = regexp.Compile(f.pattern)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-18 18:56:55 -04:00
|
|
|
func (f *Filter) SetUnPattern(unpattern string) error {
|
|
|
|
var err error
|
|
|
|
f.unpattern = unpattern
|
|
|
|
f.unmatcher, err = regexp.Compile(f.unpattern)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-18 18:21:03 -04:00
|
|
|
func (f *Filter) Blank() bool {
|
2024-07-30 14:43:54 -04:00
|
|
|
blank := time.Time{}
|
|
|
|
return !f.hasRegex() &&
|
|
|
|
!f.hasUnregex() &&
|
2024-06-18 18:21:03 -04:00
|
|
|
f.glob == "" &&
|
2024-06-18 18:56:55 -04:00
|
|
|
f.unglob == "" &&
|
2024-07-30 14:43:54 -04:00
|
|
|
f.after.Equal(blank) &&
|
|
|
|
f.before.Equal(blank) &&
|
|
|
|
f.on.Equal(blank) &&
|
2024-06-19 22:13:10 -04:00
|
|
|
len(f.filenames) == 0 &&
|
2024-07-03 13:17:08 -04:00
|
|
|
!f.ignorehidden &&
|
2024-06-19 22:13:10 -04:00
|
|
|
!f.filesonly &&
|
2024-06-30 21:39:23 -04:00
|
|
|
!f.dirsonly &&
|
|
|
|
f.minsize == 0 &&
|
2024-07-16 00:05:21 -04:00
|
|
|
f.maxsize == 0 &&
|
|
|
|
f.mode == 0
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Filter) String() string {
|
2024-07-30 14:43:54 -04:00
|
|
|
var match, unmatch string
|
2024-06-18 18:21:03 -04:00
|
|
|
if f.matcher != nil {
|
2024-07-30 14:43:54 -04:00
|
|
|
match = f.matcher.String()
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
2024-06-18 18:56:55 -04:00
|
|
|
if f.unmatcher != nil {
|
2024-07-30 14:43:54 -04:00
|
|
|
unmatch = f.unmatcher.String()
|
2024-06-18 18:56:55 -04:00
|
|
|
}
|
2024-06-19 22:13:10 -04:00
|
|
|
return fmt.Sprintf("on:'%s' before:'%s' after:'%s' glob:'%s' regex:'%s' unglob:'%s' "+
|
2024-07-16 00:05:21 -04:00
|
|
|
"unregex:'%s' filenames:'%v' filesonly:'%t' dirsonly:'%t' ignorehidden:'%t' "+
|
|
|
|
"minsize:'%d' maxsize:'%d' mode:'%s'",
|
2024-06-18 18:21:03 -04:00
|
|
|
f.on, f.before, f.after,
|
2024-07-30 14:43:54 -04:00
|
|
|
f.glob, match, f.unglob, unmatch,
|
2024-07-16 00:05:21 -04:00
|
|
|
f.filenames, f.filesonly, f.dirsonly,
|
|
|
|
f.ignorehidden, f.minsize, f.maxsize, f.mode,
|
2024-06-18 18:21:03 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
func (f *Filter) hasRegex() bool {
|
2024-06-18 18:21:03 -04:00
|
|
|
if f.matcher == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return f.matcher.String() != ""
|
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
func (f *Filter) hasUnregex() bool {
|
2024-06-18 18:56:55 -04:00
|
|
|
if f.unmatcher == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return f.unmatcher.String() != ""
|
|
|
|
}
|
|
|
|
|
2024-07-16 00:05:21 -04:00
|
|
|
func New(on, before, after, glob, pattern, unglob, unpattern string, filesonly, dirsonly, ignorehidden bool, minsize, maxsize string, mode fs.FileMode, names ...string) (*Filter, error) {
|
2024-06-18 18:21:03 -04:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
now = time.Now()
|
|
|
|
)
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
filter := &Filter{
|
2024-07-03 13:17:08 -04:00
|
|
|
glob: glob,
|
|
|
|
unglob: unglob,
|
|
|
|
filesonly: filesonly,
|
|
|
|
dirsonly: dirsonly,
|
|
|
|
ignorehidden: ignorehidden,
|
2024-07-16 00:05:21 -04:00
|
|
|
mode: mode,
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.AddFileNames(names...)
|
2024-06-19 20:14:50 -04:00
|
|
|
|
2024-06-18 18:56:55 -04:00
|
|
|
if on != "" {
|
|
|
|
o, err := anytime.Parse(on, now)
|
2024-06-18 18:21:03 -04:00
|
|
|
if err != nil {
|
|
|
|
return &Filter{}, err
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.on = o
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
2024-06-18 18:56:55 -04:00
|
|
|
if after != "" {
|
|
|
|
a, err := anytime.Parse(after, now)
|
2024-06-18 18:21:03 -04:00
|
|
|
if err != nil {
|
|
|
|
return &Filter{}, err
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.after = a
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
2024-06-18 18:56:55 -04:00
|
|
|
if before != "" {
|
|
|
|
b, err := anytime.Parse(before, now)
|
2024-06-18 18:21:03 -04:00
|
|
|
if err != nil {
|
|
|
|
return &Filter{}, err
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.before = b
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
err = filter.SetPattern(pattern)
|
2024-06-18 18:56:55 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
err = filter.SetUnPattern(unpattern)
|
2024-06-18 18:56:55 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-18 18:21:03 -04:00
|
|
|
|
2024-06-30 21:39:23 -04:00
|
|
|
if minsize != "" {
|
|
|
|
m, e := humanize.ParseBytes(minsize)
|
|
|
|
if e != nil {
|
|
|
|
log.Errorf("invalid input size '%s'", minsize)
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.minsize = int64(m)
|
2024-06-30 21:39:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if maxsize != "" {
|
|
|
|
m, e := humanize.ParseBytes(maxsize)
|
|
|
|
if e != nil {
|
|
|
|
log.Errorf("invalid input size '%s'", maxsize)
|
|
|
|
}
|
2024-07-30 14:43:54 -04:00
|
|
|
filter.maxsize = int64(m)
|
2024-06-30 21:39:23 -04:00
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
return filter, nil
|
2024-06-18 18:21:03 -04:00
|
|
|
}
|
|
|
|
|
2024-07-30 14:43:54 -04:00
|
|
|
func sameDay(a, b time.Time) bool {
|
2024-06-18 18:21:03 -04:00
|
|
|
ay, am, ad := a.Date()
|
|
|
|
by, bm, bd := b.Date()
|
|
|
|
return ay == by && am == bm && ad == bd
|
|
|
|
}
|