add reverse glob and regex filters

This commit is contained in:
Lilian Jónsdóttir 2024-06-18 15:56:55 -07:00
parent cc1523034b
commit 6794b3a9b2
3 changed files with 139 additions and 22 deletions

View file

@ -14,8 +14,10 @@ import (
type Filter struct { type Filter struct {
on, before, after time.Time on, before, after time.Time
glob, pattern string glob, pattern string
unglob, unpattern string
filenames []string filenames []string
matcher *regexp.Regexp matcher *regexp.Regexp
unmatcher *regexp.Regexp
} }
func (f *Filter) On() time.Time { return f.on } func (f *Filter) On() time.Time { return f.on }
@ -47,6 +49,14 @@ func (f *Filter) Match(filename string, modified time.Time) bool {
return false return false
} }
} }
if f.has_unregex() && f.unmatcher.MatchString(filename) {
return false
}
if f.unglob != "" {
if match, err := filepath.Match(f.unglob, filename); err != nil || match {
return false
}
}
if len(f.filenames) > 0 && !slices.Contains(f.filenames, filename) { if len(f.filenames) > 0 && !slices.Contains(f.filenames, filename) {
return false return false
} }
@ -61,10 +71,19 @@ func (f *Filter) SetPattern(pattern string) error {
return err return err
} }
func (f *Filter) SetUnPattern(unpattern string) error {
var err error
f.unpattern = unpattern
f.unmatcher, err = regexp.Compile(f.unpattern)
return err
}
func (f *Filter) Blank() bool { func (f *Filter) Blank() bool {
t := time.Time{} t := time.Time{}
return !f.has_regex() && return !f.has_regex() &&
!f.has_unregex() &&
f.glob == "" && f.glob == "" &&
f.unglob == "" &&
f.after.Equal(t) && f.after.Equal(t) &&
f.before.Equal(t) && f.before.Equal(t) &&
f.on.Equal(t) && f.on.Equal(t) &&
@ -72,13 +91,18 @@ func (f *Filter) Blank() bool {
} }
func (f *Filter) String() string { func (f *Filter) String() string {
var m string var m, unm string
if f.matcher != nil { if f.matcher != nil {
m = f.matcher.String() m = f.matcher.String()
} }
return fmt.Sprintf("on:'%s' before:'%s' after:'%s' glob:'%s' regex:'%s' filenames:'%v'", if f.unmatcher != nil {
unm = f.unmatcher.String()
}
return fmt.Sprintf("on:'%s' before:'%s' after:'%s' glob:'%s' regex:'%s' unglob:'%s' unregex:'%s' filenames:'%v'",
f.on, f.before, f.after, f.on, f.before, f.after,
f.glob, m, f.filenames, f.glob, m,
f.unglob, unm,
f.filenames,
) )
} }
@ -89,45 +113,59 @@ func (f *Filter) has_regex() bool {
return f.matcher.String() != "" return f.matcher.String() != ""
} }
func New(o, b, a, g, p string, names ...string) (*Filter, error) { func (f *Filter) has_unregex() bool {
// o b a g p if f.unmatcher == nil {
return false
}
return f.unmatcher.String() != ""
}
func New(on, before, after, glob, pattern, unglob, unpattern string, names ...string) (*Filter, error) {
var ( var (
err error err error
now = time.Now() now = time.Now()
) )
f := &Filter{ f := &Filter{
glob: g, glob: glob,
unglob: unglob,
filenames: append([]string{}, names...), filenames: append([]string{}, names...),
} }
if o != "" { if on != "" {
on, err := anytime.Parse(o, now) o, err := anytime.Parse(on, now)
if err != nil { if err != nil {
return &Filter{}, err return &Filter{}, err
} }
f.on = on f.on = o
} }
if a != "" { if after != "" {
after, err := anytime.Parse(a, now) a, err := anytime.Parse(after, now)
if err != nil { if err != nil {
return &Filter{}, err return &Filter{}, err
} }
f.after = after f.after = a
} }
if b != "" { if before != "" {
before, err := anytime.Parse(b, now) b, err := anytime.Parse(before, now)
if err != nil { if err != nil {
return &Filter{}, err return &Filter{}, err
} }
f.before = before f.before = b
} }
err = f.SetPattern(p) err = f.SetPattern(pattern)
if err != nil {
return nil, err
}
err = f.SetUnPattern(unpattern)
if err != nil {
return nil, err
}
return f, err return f, nil
} }
func same_day(a, b time.Time) bool { func same_day(a, b time.Time) bool {

View file

@ -22,6 +22,7 @@ var (
type testholder struct { type testholder struct {
pattern, glob string pattern, glob string
unpattern, unglob string
before, after, on string before, after, on string
filenames []string filenames []string
good, bad []singletest good, bad []singletest
@ -47,7 +48,7 @@ func testmatch(t *testing.T, testers []testholder) {
err error err error
) )
for _, tester := range testers { for _, tester := range testers {
f, err = New(tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.filenames...) f, err = New(tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.unglob, tester.unpattern, tester.filenames...)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -261,6 +262,46 @@ func TestFilterGlob(t *testing.T) {
}) })
} }
func TestFilterUnMatch(t *testing.T) {
testmatch(t, []testholder{
{
unpattern: "^ss_.*\\.zip",
good: blanktime("hello.zip", "ss_potato.png", "sss.zip"),
bad: blanktime("ss_ost_flac.zip", "ss_guide.zip", "ss_controls.zip"),
},
{
unpattern: "^h.*o$",
good: blanktime("hi", "test", "hellO", "Hello", "oh hello there"),
bad: blanktime("hello", "hippo", "how about some pasta with alfredo"),
},
})
}
func TestFilterUnGlob(t *testing.T) {
testmatch(t, []testholder{
{
unglob: "*.txt",
good: blanktime("test.md", "test.go", "test.tar.gz", "testxt", "test.text"),
bad: blanktime("test.txt", "alsotest.txt"),
},
{
unglob: "*.tar.*",
good: blanktime("test.tar", "test.txt", "test.targz", "test.tgz"),
bad: blanktime("test.tar.gz", "test.tar.xz", "test.tar.zst", "test.tar.bz2"),
},
{
unglob: "pot*o",
good: blanktime("salad", "test", "alsotest"),
bad: blanktime("potato", "potdonkeyo", "potesto"),
},
{
unglob: "t?st",
good: blanktime("best", "fast", "most", "past"),
bad: blanktime("test", "tast", "tfst", "tnst"),
},
})
}
func TestFilterFilenames(t *testing.T) { func TestFilterFilenames(t *testing.T) {
testmatch(t, []testholder{ testmatch(t, []testholder{
{ {
@ -282,6 +323,12 @@ func TestFilterFilenames(t *testing.T) {
} }
func TestFilterMultipleParameters(t *testing.T) { func TestFilterMultipleParameters(t *testing.T) {
y, m, d := now.Date()
threepm := time.Date(y, m, d, 15, 0, 0, 0, time.Local)
tenpm := time.Date(y, m, d, 22, 0, 0, 0, time.Local)
twoam := time.Date(y, m, d, 2, 0, 0, 0, time.Local)
sevenam := time.Date(y, m, d, 7, 0, 0, 0, time.Local)
testmatch(t, []testholder{ testmatch(t, []testholder{
{ {
pattern: "[Tt]est", pattern: "[Tt]est",
@ -318,16 +365,29 @@ func TestFilterMultipleParameters(t *testing.T) {
on: "today", on: "today",
after: "two weeks ago", after: "two weeks ago",
before: "one week ago", before: "one week ago",
good: blankfilename(now, time.Date(now.Year(), now.Month(), now.Day(), 18, 42, 0, 0, time.Local), time.Date(now.Year(), now.Month(), now.Day(), 8, 17, 33, 0, time.Local)), good: blankfilename(now, twoam, sevenam, threepm, tenpm),
bad: blankfilename(yesterday, oneweekago, onemonthago, oneyearago), bad: blankfilename(yesterday, oneweekago, onemonthago, oneyearago),
}, },
{
unpattern: ".*\\.(jpg|png)",
on: "today",
good: []singletest{
{filename: "test.txt", modified: now},
{filename: "hello.md", modified: tenpm},
},
bad: []singletest{
{filename: "test.png", modified: now},
{filename: "test.jpg", modified: twoam},
{filename: "hello.md", modified: twomonthsago},
},
},
}) })
} }
func TestFilterBlank(t *testing.T) { func TestFilterBlank(t *testing.T) {
var f *Filter var f *Filter
t.Run("new", func(t *testing.T) { t.Run("new", func(t *testing.T) {
f, _ = New("", "", "", "", "") f, _ = New("", "", "", "", "", "", "")
if !f.Blank() { if !f.Blank() {
t.Fatalf("filter isn't blank? %s", f) t.Fatalf("filter isn't blank? %s", f)
} }
@ -351,6 +411,12 @@ func TestFilterNotBlank(t *testing.T) {
{ {
glob: "*test*", glob: "*test*",
}, },
{
unpattern: ".*\\.(jpg|png)",
},
{
unglob: "*.jpg",
},
{ {
before: "yesterday", before: "yesterday",
after: "one week ago", after: "one week ago",
@ -369,7 +435,7 @@ func TestFilterNotBlank(t *testing.T) {
for _, tester := range testers { for _, tester := range testers {
t.Run("notblank"+tester.String(), func(t *testing.T) { t.Run("notblank"+tester.String(), func(t *testing.T) {
f, _ = New(tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.filenames...) f, _ = New(tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.unglob, tester.unpattern, tester.filenames...)
if f.Blank() { if f.Blank() {
t.Fatalf("filter is blank?? %s", f) t.Fatalf("filter is blank?? %s", f)
} }

15
main.go
View file

@ -29,6 +29,7 @@ var (
loglvl string loglvl string
f *filter.Filter f *filter.Filter
o, b, a, g, p string o, b, a, g, p string
ung, unp string
workdir string workdir string
recursive bool recursive bool
termwidth int termwidth int
@ -59,7 +60,7 @@ var (
before_commands = func(ctx *cli.Context) (err error) { before_commands = func(ctx *cli.Context) (err error) {
// setup filter // setup filter
if f == nil { if f == nil {
f, err = filter.New(o, b, a, g, p, ctx.Args().Slice()...) f, err = filter.New(o, b, a, g, p, ung, unp, ctx.Args().Slice()...)
} }
log.Debugf("filter: %s", f.String()) log.Debugf("filter: %s", f.String())
return return
@ -227,6 +228,18 @@ var (
Aliases: []string{"g"}, Aliases: []string{"g"},
Destination: &g, Destination: &g,
}, },
&cli.StringFlag{
Name: "not-match",
Usage: "operate on files not matching regex `PATTERN`",
Aliases: []string{"M"},
Destination: &unp,
},
&cli.StringFlag{
Name: "not-glob",
Usage: "operate on files not matching `GLOB`",
Aliases: []string{"G"},
Destination: &ung,
},
&cli.StringFlag{ &cli.StringFlag{
Name: "on", Name: "on",
Usage: "operate on files modified on `DATE`", Usage: "operate on files modified on `DATE`",