gt/main.go

310 lines
6.9 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"time"
"git.burning.moe/celediel/gt/internal/files"
"git.burning.moe/celediel/gt/internal/filter"
"git.burning.moe/celediel/gt/internal/trash"
"github.com/adrg/xdg"
"github.com/charmbracelet/log"
"github.com/urfave/cli/v2"
"golang.org/x/term"
)
const (
appname string = "gt"
appdesc string = "xdg trash cli"
appversion string = "v0.0.1"
)
var (
loglvl string
f *filter.Filter
o, b, a, g, p string
ung, unp string
workdir string
recursive bool
termwidth int
trashDir = filepath.Join(xdg.DataHome, "Trash")
before_all = func(ctx *cli.Context) (err error) {
// setup log
log.SetReportTimestamp(true)
log.SetTimeFormat(time.TimeOnly)
if l, e := log.ParseLevel(loglvl); e == nil {
log.SetLevel(l)
// Some extra info for debug level
if log.GetLevel() == log.DebugLevel {
log.SetReportCaller(true)
}
}
w, _, e := term.GetSize(int(os.Stdout.Fd()))
if e != nil {
w = 80
}
termwidth = w
return
}
before_commands = func(ctx *cli.Context) (err error) {
// setup filter
if f == nil {
f, err = filter.New(o, b, a, g, p, ung, unp, ctx.Args().Slice()...)
}
log.Debugf("filter: %s", f.String())
return
}
after = func(ctx *cli.Context) error {
return nil
}
do_trash = &cli.Command{
Name: "trash",
Aliases: []string{"tr"},
Usage: "trash a file or files",
Flags: slices.Concat(trash_flags, filter_flags),
Before: before_commands,
Action: func(ctx *cli.Context) error {
fls, err := files.Find(workdir, recursive, f)
if err != nil {
return err
}
if len(fls) == 0 {
fmt.Println("no files to trash")
return nil
}
fls.Show(termwidth)
if confirm(fmt.Sprintf("trash these %d files?", len(fls))) {
tfs := make([]string, 0, len(fls))
for _, file := range fls {
log.Debugf("gonna trash %s", file.Filename())
tfs = append(tfs, file.Filename())
}
trashed, err := trash.TrashFiles(trashDir, tfs...)
if err != nil {
return err
}
log.Printf("trashed %d files", trashed)
} else {
log.Info("not gonna do it")
return nil
}
return nil
},
}
do_list = &cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "list trashed files",
Flags: slices.Concat(filter_flags),
Before: before_commands,
Action: func(ctx *cli.Context) error {
log.Debugf("searching in directory %s for files", trashDir)
// look for files
files, err := trash.FindFiles(trashDir, f)
var msg string
if f.Blank() {
msg = "trash is empty"
} else {
msg = "no files to show"
}
if len(files) == 0 {
fmt.Println(msg)
return nil
} else if err != nil {
return err
}
// display them
files.Show(termwidth)
return nil
},
}
do_restore = &cli.Command{
Name: "restore",
Aliases: []string{"re"},
Usage: "restore a trashed file or files",
Flags: slices.Concat(filter_flags),
Before: before_commands,
Action: func(ctx *cli.Context) error {
log.Debugf("searching in directory %s for files", trashDir)
// look for files
files, err := trash.FindFiles(trashDir, f)
if len(files) == 0 {
fmt.Println("no files to restore")
return nil
} else if err != nil {
return err
}
files.Show(termwidth)
if confirm(fmt.Sprintf("restore these %d files?", len(files))) {
log.Info("doing the thing")
restored, err := trash.Restore(files)
if err != nil {
return fmt.Errorf("restored %d files before error %s", restored, err)
}
log.Printf("restored %d files\n", restored)
} else {
log.Info("not gonna do it")
}
return nil
},
}
do_clean = &cli.Command{
Name: "clean",
Aliases: []string{"cl"},
Usage: "clean files from trash",
Flags: slices.Concat(filter_flags),
Before: before_commands,
Action: func(ctx *cli.Context) error {
files, err := trash.FindFiles(trashDir, f)
if len(files) == 0 {
fmt.Println("no files to clean")
return nil
} else if err != nil {
return err
}
files.Show(termwidth)
if confirm(fmt.Sprintf("remove these %d files permanently from the trash?", len(files))) &&
confirm(fmt.Sprintf("really remove all %d of these files permanently from the trash forever??", len(files))) {
log.Info("gonna remove some files forever")
removed, err := trash.Remove(files)
if err != nil {
return fmt.Errorf("removed %d files before error %s", removed, err)
}
log.Printf("removed %d files\n", removed)
} else {
log.Printf("left %d files alone", len(files))
}
return nil
},
}
global_flags = []cli.Flag{
&cli.StringFlag{
Name: "log",
Usage: "Log level",
Value: "warn",
Aliases: []string{"l"},
Destination: &loglvl,
},
}
filter_flags = []cli.Flag{
&cli.StringFlag{
Name: "match",
Usage: "operate on files matching regex `PATTERN`",
Aliases: []string{"m"},
Destination: &p,
},
&cli.StringFlag{
Name: "glob",
Usage: "operate on files matching `GLOB`",
Aliases: []string{"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{
Name: "on",
Usage: "operate on files modified on `DATE`",
Aliases: []string{"o"},
Destination: &o,
},
&cli.StringFlag{
Name: "after",
Usage: "operate on files modified before `DATE`",
Aliases: []string{"a"},
Destination: &a,
},
&cli.StringFlag{
Name: "before",
Usage: "operate on files modified after `DATE`",
Aliases: []string{"b"},
Destination: &b,
},
}
trash_flags = []cli.Flag{
&cli.BoolFlag{
Name: "recursive",
Usage: "trash files recursively",
Aliases: []string{"r"},
Destination: &recursive,
Value: false,
DisableDefaultText: true,
},
&cli.PathFlag{
Name: "work-dir",
Usage: "trash files in this `DIRECTORY`",
Aliases: []string{"w"},
Destination: &workdir,
},
}
)
func main() {
app := &cli.App{
Name: appname,
Usage: appdesc,
Version: appversion,
Before: before_all,
After: after,
Commands: []*cli.Command{do_trash, do_list, do_restore, do_clean},
Flags: global_flags,
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func confirm(s string) bool {
r := bufio.NewReader(os.Stdin)
fmt.Printf("%s [y/n]: ", s)
got, err := r.ReadString('\n')
if err != nil {
log.Fatal(err)
}
if len(got) < 2 {
return false
} else {
return strings.ToLower(strings.TrimSpace(got))[0] == 'y'
}
}