add reverse glob and regex filters
This commit is contained in:
parent
cc1523034b
commit
6794b3a9b2
|
@ -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 {
|
||||||
|
|
|
@ -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
15
main.go
|
@ -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`",
|
||||||
|
|
Loading…
Reference in a new issue