add flag to filter by file mode

This commit is contained in:
Lilian Jónsdóttir 2024-07-15 21:05:21 -07:00
parent 40ef230a4f
commit 940a2fc14f
6 changed files with 132 additions and 32 deletions

View file

@ -18,12 +18,14 @@ type DiskFile struct {
filesize int64 filesize int64
modified time.Time modified time.Time
isdir bool isdir bool
mode fs.FileMode
} }
func (f DiskFile) Name() string { return f.name } func (f DiskFile) Name() string { return f.name }
func (f DiskFile) Path() string { return f.path } func (f DiskFile) Path() string { return f.path }
func (f DiskFile) Date() time.Time { return f.modified } func (f DiskFile) Date() time.Time { return f.modified }
func (f DiskFile) IsDir() bool { return f.isdir } func (f DiskFile) IsDir() bool { return f.isdir }
func (f DiskFile) Mode() fs.FileMode { return f.mode }
func (f DiskFile) Filesize() int64 { func (f DiskFile) Filesize() int64 {
if f.isdir { if f.isdir {
return -1 return -1
@ -61,6 +63,7 @@ func NewDisk(path string) (DiskFile, error) {
filesize: info.Size(), filesize: info.Size(),
modified: info.ModTime(), modified: info.ModTime(),
isdir: info.IsDir(), isdir: info.IsDir(),
mode: info.Mode(),
}, nil }, nil
} }
@ -170,6 +173,7 @@ func read_dir(dir string, f *filter.Filter) (files Files) {
modified: info.ModTime(), modified: info.ModTime(),
filesize: info.Size(), filesize: info.Size(),
isdir: info.IsDir(), isdir: info.IsDir(),
mode: info.Mode(),
}) })
} }
} }

View file

@ -3,6 +3,7 @@ package files
import ( import (
"cmp" "cmp"
"io/fs"
"strings" "strings"
"time" "time"
) )
@ -13,6 +14,7 @@ type File interface {
Date() time.Time Date() time.Time
Filesize() int64 Filesize() int64
IsDir() bool IsDir() bool
Mode() fs.FileMode
String() string String() string
} }

View file

@ -39,6 +39,7 @@ type TrashInfo struct {
isdir bool isdir bool
trashed time.Time trashed time.Time
filesize int64 filesize int64
mode fs.FileMode
} }
func (t TrashInfo) Name() string { return t.name } func (t TrashInfo) Name() string { return t.name }
@ -47,6 +48,7 @@ func (t TrashInfo) Path() string { return t.ogpath }
func (t TrashInfo) TrashInfo() string { return t.trashinfo } func (t TrashInfo) TrashInfo() string { return t.trashinfo }
func (t TrashInfo) Date() time.Time { return t.trashed } func (t TrashInfo) Date() time.Time { return t.trashed }
func (t TrashInfo) IsDir() bool { return t.isdir } func (t TrashInfo) IsDir() bool { return t.isdir }
func (t TrashInfo) Mode() fs.FileMode { return t.mode }
func (t TrashInfo) Filesize() int64 { func (t TrashInfo) Filesize() int64 {
if t.isdir { if t.isdir {
return -1 return -1

View file

@ -25,6 +25,7 @@ type Filter struct {
matcher *regexp.Regexp matcher *regexp.Regexp
unmatcher *regexp.Regexp unmatcher *regexp.Regexp
minsize, maxsize int64 minsize, maxsize int64
mode fs.FileMode
} }
func (f *Filter) On() time.Time { return f.on } func (f *Filter) On() time.Time { return f.on }
@ -38,6 +39,7 @@ func (f *Filter) DirsOnly() bool { return f.dirsonly }
func (f *Filter) IgnoreHidden() bool { return f.ignorehidden } func (f *Filter) IgnoreHidden() bool { return f.ignorehidden }
func (f *Filter) MinSize() int64 { return f.minsize } func (f *Filter) MinSize() int64 { return f.minsize }
func (f *Filter) MaxSize() int64 { return f.maxsize } func (f *Filter) MaxSize() int64 { return f.maxsize }
func (f *Filter) Mode() fs.FileMode { return f.mode }
func (f *Filter) AddFileName(filename string) { func (f *Filter) AddFileName(filename string) {
filename = filepath.Clean(filename) filename = filepath.Clean(filename)
@ -55,6 +57,7 @@ func (f *Filter) Match(info fs.FileInfo) bool {
modified := info.ModTime() modified := info.ModTime()
isdir := info.IsDir() isdir := info.IsDir()
size := info.Size() size := info.Size()
mode := info.Mode()
// on or before/after, not both // on or before/after, not both
if !f.on.IsZero() { if !f.on.IsZero() {
@ -127,6 +130,11 @@ func (f *Filter) Match(info fs.FileInfo) bool {
return false return false
} }
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
}
// okay it was good // okay it was good
log.Debugf("%s modified:'%s' dir:'%T' mode:'%s' was a good one!", filename, modified, isdir, mode) log.Debugf("%s modified:'%s' dir:'%T' mode:'%s' was a good one!", filename, modified, isdir, mode)
return true return true
@ -160,7 +168,8 @@ func (f *Filter) Blank() bool {
!f.filesonly && !f.filesonly &&
!f.dirsonly && !f.dirsonly &&
f.minsize == 0 && f.minsize == 0 &&
f.maxsize == 0 f.maxsize == 0 &&
f.mode == 0
} }
func (f *Filter) String() string { func (f *Filter) String() string {
@ -172,13 +181,12 @@ func (f *Filter) String() string {
unm = f.unmatcher.String() unm = f.unmatcher.String()
} }
return fmt.Sprintf("on:'%s' before:'%s' after:'%s' glob:'%s' regex:'%s' unglob:'%s' "+ return fmt.Sprintf("on:'%s' before:'%s' after:'%s' glob:'%s' regex:'%s' unglob:'%s' "+
"unregex:'%s' filenames:'%v' filesonly:'%t' dirsonly:'%t' ignorehidden:'%t'", "unregex:'%s' filenames:'%v' filesonly:'%t' dirsonly:'%t' ignorehidden:'%t' "+
"minsize:'%d' maxsize:'%d' mode:'%s'",
f.on, f.before, f.after, f.on, f.before, f.after,
f.glob, m, f.glob, m, f.unglob, unm,
f.unglob, unm, f.filenames, f.filesonly, f.dirsonly,
f.filenames, f.ignorehidden, f.minsize, f.maxsize, f.mode,
f.filesonly, f.dirsonly,
f.ignorehidden,
) )
} }
@ -196,7 +204,7 @@ func (f *Filter) has_unregex() bool {
return f.unmatcher.String() != "" return f.unmatcher.String() != ""
} }
func New(on, before, after, glob, pattern, unglob, unpattern string, filesonly, dirsonly, ignorehidden bool, minsize, maxsize string, names ...string) (*Filter, error) { func New(on, before, after, glob, pattern, unglob, unpattern string, filesonly, dirsonly, ignorehidden bool, minsize, maxsize string, mode fs.FileMode, names ...string) (*Filter, error) {
var ( var (
err error err error
now = time.Now() now = time.Now()
@ -208,6 +216,7 @@ func New(on, before, after, glob, pattern, unglob, unpattern string, filesonly,
filesonly: filesonly, filesonly: filesonly,
dirsonly: dirsonly, dirsonly: dirsonly,
ignorehidden: ignorehidden, ignorehidden: ignorehidden,
mode: mode,
} }
f.AddFileNames(names...) f.AddFileNames(names...)

View file

@ -33,6 +33,7 @@ type testholder struct {
ignorehidden bool ignorehidden bool
good, bad []singletest good, bad []singletest
minsize, maxsize string minsize, maxsize string
mode fs.FileMode
} }
func (t testholder) String() string { func (t testholder) String() string {
@ -74,7 +75,7 @@ func testmatch(t *testing.T, testers []testholder) {
f, err = filter.New( f, err = filter.New(
tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.on, tester.before, tester.after, tester.glob, tester.pattern,
tester.unglob, tester.unpattern, tester.filesonly, tester.dirsonly, tester.unglob, tester.unpattern, tester.filesonly, tester.dirsonly,
tester.ignorehidden, tester.minsize, tester.maxsize, tester.ignorehidden, tester.minsize, tester.maxsize, tester.mode,
tester.filenames..., tester.filenames...,
) )
if err != nil { if err != nil {
@ -102,7 +103,7 @@ func testmatch(t *testing.T, testers []testholder) {
func nameonly(dir bool, names ...string) []singletest { func nameonly(dir bool, names ...string) []singletest {
out := make([]singletest, 0, len(names)) out := make([]singletest, 0, len(names))
for _, name := range names { for _, name := range names {
out = append(out, singletest{filename: name, modified: time.Time{}, isdir: dir, size: 0}) out = append(out, singletest{filename: name, modified: time.Time{}, isdir: dir, size: 0, mode: 0000})
} }
return out return out
} }
@ -110,7 +111,7 @@ func nameonly(dir bool, names ...string) []singletest {
func timeonly(dir bool, times ...time.Time) []singletest { func timeonly(dir bool, times ...time.Time) []singletest {
out := make([]singletest, 0, len(times)) out := make([]singletest, 0, len(times))
for _, time := range times { for _, time := range times {
out = append(out, singletest{filename: "blank.txt", modified: time, isdir: dir, size: 0}) out = append(out, singletest{filename: "blank.txt", modified: time, isdir: dir, size: 0, mode: 0000})
} }
return out return out
} }
@ -118,7 +119,15 @@ func timeonly(dir bool, times ...time.Time) []singletest {
func sizeonly(dir bool, sizes ...int64) []singletest { func sizeonly(dir bool, sizes ...int64) []singletest {
out := make([]singletest, 0, len(sizes)) out := make([]singletest, 0, len(sizes))
for _, size := range sizes { for _, size := range sizes {
out = append(out, singletest{filename: "blank", modified: time.Time{}, isdir: dir, size: size}) out = append(out, singletest{filename: "blank", modified: time.Time{}, isdir: dir, size: size, mode: 0000})
}
return out
}
func modeonly(dir bool, modes ...fs.FileMode) []singletest {
out := make([]singletest, 0, len(modes))
for _, mode := range modes {
out = append(out, singletest{filename: "blank", modified: time.Time{}, isdir: dir, size: 0, mode: mode})
} }
return out return out
} }
@ -412,6 +421,16 @@ func TestFilesize(t *testing.T) {
}) })
} }
func TestMode(t *testing.T) {
testmatch(t, []testholder{
{
mode: fs.FileMode(0755),
good: modeonly(false, fs.FileMode(0755)),
bad: modeonly(false, fs.FileMode(0644)),
},
})
}
func TestFilterMultipleParameters(t *testing.T) { func TestFilterMultipleParameters(t *testing.T) {
y, m, d := now.Date() y, m, d := now.Date()
threepm := time.Date(y, m, d, 15, 0, 0, 0, time.Local) threepm := time.Date(y, m, d, 15, 0, 0, 0, time.Local)
@ -529,13 +548,41 @@ func TestFilterMultipleParameters(t *testing.T) {
}, },
}, },
}, },
{
mode: fs.FileMode(0600),
ignorehidden: true,
good: []singletest{
{
mode: fs.FileMode(0600),
filename: "hello.txt",
},
{
mode: fs.FileMode(0600),
filename: "main.go",
},
},
bad: []singletest{
{
mode: fs.FileMode(0600),
filename: ".bashrc",
},
{
mode: fs.FileMode(0644),
filename: "hello.txt",
},
{
mode: fs.FileMode(0644),
filename: "main.go",
},
},
},
}) })
} }
func TestFilterBlank(t *testing.T) { func TestFilterBlank(t *testing.T) {
var f *filter.Filter var f *filter.Filter
t.Run("new", func(t *testing.T) { t.Run("new", func(t *testing.T) {
f, _ = filter.New("", "", "", "", "", "", "", false, false, false, "0", "0") f, _ = filter.New("", "", "", "", "", "", "", false, false, false, "0", "0", 0)
if !f.Blank() { if !f.Blank() {
t.Fatalf("filter isn't blank? %s", f) t.Fatalf("filter isn't blank? %s", f)
} }
@ -587,6 +634,9 @@ func TestFilterNotBlank(t *testing.T) {
{ {
ignorehidden: true, ignorehidden: true,
}, },
{
mode: fs.FileMode(0644),
},
} }
) )
@ -595,7 +645,7 @@ func TestFilterNotBlank(t *testing.T) {
f, _ = filter.New( f, _ = filter.New(
tester.on, tester.before, tester.after, tester.glob, tester.pattern, tester.on, tester.before, tester.after, tester.glob, tester.pattern,
tester.unglob, tester.unpattern, tester.filesonly, tester.dirsonly, tester.unglob, tester.unpattern, tester.filesonly, tester.dirsonly,
tester.ignorehidden, tester.minsize, tester.maxsize, tester.ignorehidden, tester.minsize, tester.maxsize, tester.mode,
tester.filenames..., tester.filenames...,
) )
if f.Blank() { if f.Blank() {

61
main.go
View file

@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"slices" "slices"
"strconv"
"time" "time"
"git.burning.moe/celediel/gt/internal/files" "git.burning.moe/celediel/gt/internal/files"
@ -29,17 +30,17 @@ const (
) )
var ( var (
loglvl string loglvl string
f *filter.Filter f *filter.Filter
o, b, a, g, p string o, b, a, g, p, m string
sm, lg string sm, lg string
ung, unp string ung, unp string
fo, do, sh, ni bool fo, do, sh, ni bool
askconfirm bool askconfirm bool
workdir, ogdir cli.Path workdir, ogdir cli.Path
recursive bool recursive bool
termwidth int termwidth int
termheight int termheight int
trashDir = filepath.Join(xdg.DataHome, "Trash") trashDir = filepath.Join(xdg.DataHome, "Trash")
@ -91,7 +92,11 @@ var (
) )
if f == nil { if f == nil {
f, err = filter.New(o, b, a, g, p, ung, unp, fo, do, false, sm, lg) md, e := getMode(m)
if e != nil {
return e
}
f, err = filter.New(o, b, a, g, p, ung, unp, fo, do, false, sm, lg, md)
} }
if err != nil { if err != nil {
return err return err
@ -115,7 +120,11 @@ var (
beforeCommands = func(ctx *cli.Context) (err error) { beforeCommands = 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, ung, unp, fo, do, false, sm, lg, ctx.Args().Slice()...) md, e := getMode(m)
if e != nil {
return e
}
f, err = filter.New(o, b, a, g, p, ung, unp, fo, do, false, sm, lg, md, ctx.Args().Slice()...)
} }
log.Debugf("filter: %s", f.String()) log.Debugf("filter: %s", f.String())
return return
@ -123,7 +132,11 @@ var (
beforeTrash = func(_ *cli.Context) (err error) { beforeTrash = func(_ *cli.Context) (err error) {
if f == nil { if f == nil {
f, err = filter.New(o, b, a, g, p, ung, unp, fo, do, !sh, sm, lg) md, e := getMode(m)
if e != nil {
return e
}
f, err = filter.New(o, b, a, g, p, ung, unp, fo, do, !sh, sm, lg, md)
} }
log.Debugf("filter: %s", f.String()) log.Debugf("filter: %s", f.String())
return return
@ -360,6 +373,12 @@ var (
Aliases: []string{"X"}, Aliases: []string{"X"},
Destination: &lg, Destination: &lg,
}, },
&cli.StringFlag{
Name: "mode",
Usage: "operate on files matching mode `MODE`",
Aliases: []string{"x"},
Destination: &m,
},
} }
trashFlags = []cli.Flag{ trashFlags = []cli.Flag{
@ -524,3 +543,17 @@ func confirmTrash(fs files.Files) error {
} }
return nil return nil
} }
func getMode(in string) (fs.FileMode, error) {
if in == "" {
return fs.FileMode(0), nil
}
if len(m) == 3 {
in = "0" + in
}
md, e := strconv.ParseUint(in, 8, 64)
if e != nil {
return 0, e
}
return fs.FileMode(md), nil
}