Compare commits

..

No commits in common. "03c09e1da6253a4b468b2c44b01c26e4f09ed394" and "d4f086ab547eadd0f56a338d85399ce175787077" have entirely different histories.

13 changed files with 117 additions and 145 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
bin/ bin/
dist/ dist/*.tar.gz

View file

@ -48,7 +48,7 @@ Find files in the trash based on the filter flags and any filename args.
#### flags #### flags
*--all*, *-a* *--all*, *-a*
restore all files in trash operate on all files in trash
*--original-path* dir, *-O* dir *--original-path* dir, *-O* dir
restore files trashed from this directory restore files trashed from this directory
@ -60,7 +60,7 @@ Find files in the trash based on the filter flags and any filename args.
#### flags #### flags
*--all*, *-a* *--all*, *-a*
clean all files in trash operate on all files in trash
*--original-path* dir, *-O* dir *--original-path* dir, *-O* dir
remove files trashed from this directory remove files trashed from this directory
@ -117,6 +117,6 @@ See also gt(1) or `gt --help`.
## Screenshots ## Screenshots
![trashing screenshot](./screenshots/Screenshot01.png) ![trashing screenshot](./dist/Screenshot01.png)
![list screenshot](./screenshots/Screenshot02.png) ![list screenshot](./dist/Screenshot02.png)

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

8
gt.1
View file

@ -5,11 +5,11 @@
.nh .nh
.ad l .ad l
.\" Begin generated content: .\" Begin generated content:
.TH "gt" "1" "2024-07-31" "gt version v0.0.2" "User Commands" .TH "gt" "1" "2024-07-31" "gt version v0.0.1" "User Commands"
.PP .PP
.SH NAME .SH NAME
.PP .PP
gt - manual page for gt version 0.\&0.\&2 gt - manual page for gt version 0.\&0.\&1
.PP .PP
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
@ -27,7 +27,7 @@ g(o)t(rash) is a simple, command line program to interface with the XDG Trash.\&
.PP .PP
.SS VERSION: .SS VERSION:
.PP .PP
0.\&0.\&2 0.\&0.\&1
.PP .PP
.SS AUTHOR: .SS AUTHOR:
.PP .PP
@ -68,7 +68,7 @@ operate on files recursively
.RE .RE
\fB--work-dir\fR dir, \fB-w\fR dir \fB--work-dir\fR dir, \fB-w\fR dir
.RS 4 .RS 4
operate on files in this directory operate on files in this `DIRECTORY`
.PP .PP
.RE .RE
\fB--hidden\fR, \fB-h\fR \fB--hidden\fR, \fB-h\fR

View file

@ -1,8 +1,8 @@
gt(1) ["gt version v0.0.2" ["User Commands"]] gt(1) ["gt version v0.0.1" ["User Commands"]]
# NAME # NAME
gt \- manual page for gt version 0.0.2 gt \- manual page for gt version 0.0.1
# DESCRIPTION # DESCRIPTION
@ -20,7 +20,7 @@ g(o)t(rash) is a simple, command line program to interface with the XDG Trash. F
## VERSION: ## VERSION:
0.0.2 0.0.1
## AUTHOR: ## AUTHOR:

View file

@ -10,7 +10,7 @@ import (
const sep = string(os.PathSeparator) const sep = string(os.PathSeparator)
var ( var (
home = os.Getenv("HOME") home string = os.Getenv("HOME")
pwd, _ = os.Getwd() pwd, _ = os.Getwd()
) )

View file

@ -27,18 +27,20 @@ func SortByModified(a, b File) int {
return 1 return 1
} else if a.Date().After(b.Date()) { } else if a.Date().After(b.Date()) {
return -1 return -1
} } else {
return 0 return 0
} }
}
func SortByModifiedReverse(a, b File) int { func SortByModifiedReverse(a, b File) int {
if a.Date().After(b.Date()) { if a.Date().After(b.Date()) {
return 1 return 1
} else if a.Date().Before(b.Date()) { } else if a.Date().Before(b.Date()) {
return -1 return -1
} } else {
return 0 return 0
} }
}
func SortBySize(a, b File) int { func SortBySize(a, b File) int {
as := getSortingSize(a) as := getSortingSize(a)
@ -85,18 +87,20 @@ func SortDirectoriesFirst(a, b File) int {
return 1 return 1
} else if a.IsDir() && !b.IsDir() { } else if a.IsDir() && !b.IsDir() {
return -1 return -1
} } else {
return 0 return 0
} }
}
func SortDirectoriesLast(a, b File) int { func SortDirectoriesLast(a, b File) int {
if a.IsDir() && !b.IsDir() { if a.IsDir() && !b.IsDir() {
return 1 return 1
} else if !a.IsDir() && b.IsDir() { } else if !a.IsDir() && b.IsDir() {
return -1 return -1
} } else {
return 0 return 0
} }
}
func doNameSort(a, b File) int { func doNameSort(a, b File) int {
aname := strings.ToLower(a.Name()) aname := strings.ToLower(a.Name())
@ -115,6 +119,7 @@ func doNameSort(a, b File) int {
func getSortingSize(f File) int64 { func getSortingSize(f File) int64 {
if f.IsDir() { if f.IsDir() {
return -1 return -1
} } else {
return f.Filesize() return f.Filesize()
} }
}

View file

@ -296,8 +296,7 @@ func ensureUniqueName(filename, trashDir string) (string, string) {
// doesn't exist, so use it // doesn't exist, so use it
path := filepath.Join(filedir, filename) path := filepath.Join(filedir, filename)
return info, path return info, path
} } else {
// otherwise, try random suffixes until one works // otherwise, try random suffixes until one works
log.Debugf("%s exists in trash, generating random name", filename) log.Debugf("%s exists in trash, generating random name", filename)
var tries int var tries int
@ -312,3 +311,4 @@ func ensureUniqueName(filename, trashDir string) (string, string) {
} }
} }
} }
}

View file

@ -4,7 +4,6 @@ package interactive
import ( import (
"fmt" "fmt"
"math" "math"
"os"
"path/filepath" "path/filepath"
"slices" "slices"
"strings" "strings"
@ -13,7 +12,6 @@ import (
"git.burning.moe/celediel/gt/internal/files" "git.burning.moe/celediel/gt/internal/files"
"git.burning.moe/celediel/gt/internal/interactive/modes" "git.burning.moe/celediel/gt/internal/interactive/modes"
"git.burning.moe/celediel/gt/internal/interactive/sorting" "git.burning.moe/celediel/gt/internal/interactive/sorting"
"golang.org/x/term"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/table"
@ -86,37 +84,64 @@ type model struct {
fltrfiles files.Files fltrfiles files.Files
} }
func newModel(fls []files.File, selectall, readonly, once bool, workdir string, mode modes.Mode) (m model) { func newModel(fls []files.File, width, height int, selectall, readonly, once bool, workdir string, mode modes.Mode) model {
m = model{ var (
fwidth = int(math.Round(float64(width-woffset) * filenameColumnW))
owidth = int(math.Round(float64(width-woffset) * pathColumnW))
dwidth = int(math.Round(float64(width-woffset) * dateColumnW))
swidth = int(math.Round(float64(width-woffset) * sizeColumnW))
cwidth = int(math.Round(float64(width-woffset) * checkColumnW))
theight = min(height-hoffset, len(fls))
mdl = model{
keys: defaultKeyMap(), keys: defaultKeyMap(),
readonly: readonly, readonly: readonly,
once: once, once: once,
termheight: height,
termwidth: width,
mode: mode, mode: mode,
selected: map[string]bool{}, selected: map[string]bool{},
selectsize: 0, selectsize: 0,
files: fls, files: fls,
} }
)
m.termwidth, m.termheight = termSizes()
if workdir != "" { if workdir != "" {
m.workdir = filepath.Clean(workdir) mdl.workdir = filepath.Clean(workdir)
} }
rows := m.freshRows() rows := mdl.freshRows()
columns := m.freshColumns()
theight := min(m.termheight-hoffset, len(fls)) var datecolumn string
m.table = createTable(columns, rows, theight) switch mdl.mode {
case modes.Trashing:
datecolumn = modifiedColumn
default:
datecolumn = trashedColumn
}
m.sorting = sorting.Name columns := []table.Column{
m.sort() {Title: filenameColumn, Width: fwidth},
{Title: pathColumn, Width: owidth},
{Title: datecolumn, Width: dwidth},
{Title: sizeColumn, Width: swidth},
}
if !mdl.readonly {
columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
} else {
columns[0].Width += cwidth
}
mdl.table = createTable(columns, rows, theight)
mdl.sorting = sorting.Name
mdl.sort()
if selectall { if selectall {
m.selectAll() mdl.selectAll()
} }
return return mdl
} }
type keyMap struct { type keyMap struct {
@ -214,8 +239,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.updateTableSize()
case tea.KeyMsg: case tea.KeyMsg:
if m.filtering { if m.filtering {
switch { switch {
@ -289,6 +312,7 @@ func (m model) View() string {
} }
func (m model) showHelp() string { func (m model) showHelp() string {
// TODO: maybe use bubbletea built in help
var filterText string var filterText string
if m.filter != "" { if m.filter != "" {
filterText = fmt.Sprintf(" (%s)", m.filter) filterText = fmt.Sprintf(" (%s)", m.filter)
@ -589,48 +613,6 @@ func (m *model) filteredFiles() (filteredFiles files.Files) {
return return
} }
func (m *model) freshColumns() []table.Column {
var (
fwidth = int(math.Round(float64(m.termwidth-woffset) * filenameColumnW))
owidth = int(math.Round(float64(m.termwidth-woffset) * pathColumnW))
dwidth = int(math.Round(float64(m.termwidth-woffset) * dateColumnW))
swidth = int(math.Round(float64(m.termwidth-woffset) * sizeColumnW))
cwidth = int(math.Round(float64(m.termwidth-woffset) * checkColumnW))
datecolumn string
)
switch m.mode {
case modes.Trashing:
datecolumn = modifiedColumn
default:
datecolumn = trashedColumn
}
columns := []table.Column{
{Title: filenameColumn, Width: fwidth},
{Title: pathColumn, Width: owidth},
{Title: datecolumn, Width: dwidth},
{Title: sizeColumn, Width: swidth},
}
if !m.readonly {
columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
} else {
columns[0].Width += cwidth
}
return columns
}
func (m *model) updateTableSize() {
width, height := termSizes()
m.termheight = height
m.termwidth = width - poffset
m.table.SetWidth(m.termwidth)
m.updateTableHeight()
m.table.SetColumns(m.freshColumns())
}
func (m *model) updateTableHeight() { func (m *model) updateTableHeight() {
h := min(m.termheight-hoffset, len(m.table.Rows())) h := min(m.termheight-hoffset, len(m.table.Rows()))
m.table.SetHeight(h) m.table.SetHeight(h)
@ -639,8 +621,8 @@ func (m *model) updateTableHeight() {
} }
} }
func Select(fls files.Files, selectall, once bool, workdir string, mode modes.Mode) (files.Files, modes.Mode, error) { func Select(fls files.Files, width, height int, selectall, once bool, workdir string, mode modes.Mode) (files.Files, modes.Mode, error) {
mdl := newModel(fls, selectall, false, once, workdir, mode) mdl := newModel(fls, width, height, selectall, false, once, workdir, mode)
endmodel, err := tea.NewProgram(mdl).Run() endmodel, err := tea.NewProgram(mdl).Run()
if err != nil { if err != nil {
return fls, 0, err return fls, 0, err
@ -652,8 +634,8 @@ func Select(fls files.Files, selectall, once bool, workdir string, mode modes.Mo
return m.selectedFiles(), m.mode, nil return m.selectedFiles(), m.mode, nil
} }
func Show(fls files.Files, once bool, workdir string) error { func Show(fls files.Files, width, height int, once bool, workdir string) error {
mdl := newModel(fls, false, true, once, workdir, modes.Listing) mdl := newModel(fls, width, height, false, true, once, workdir, modes.Listing)
if _, err := tea.NewProgram(mdl).Run(); err != nil { if _, err := tea.NewProgram(mdl).Run(); err != nil {
return err return err
} }
@ -743,14 +725,3 @@ func isMatch(pattern, filename string) bool {
f := strings.ToLower(filename) f := strings.ToLower(filename)
return fuzzy.Match(p, f) return fuzzy.Match(p, f)
} }
func termSizes() (width int, height int) {
// read the term height and width for tables
var err error
width, height, err = term.GetSize(int(os.Stdout.Fd()))
if err != nil {
width = 80
height = 24
}
return
}

View file

@ -11,6 +11,8 @@ import (
"golang.org/x/term" "golang.org/x/term"
) )
// TODO: use charm stuff for this
func YesNo(prompt string) bool { func YesNo(prompt string) bool {
return AskRune(prompt, "y/n") == 'y' return AskRune(prompt, "y/n") == 'y'
} }

View file

@ -1,16 +1,10 @@
binary := "gt" binary := "gt"
version := "0.0.2" version := "0.0.1"
build_dir := "bin" build_dir := "bin"
dist_dir := "dist" dist_dir := "dist"
cmd := "." cmd := "."
output := "." / build_dir / binary output := "." / build_dir / binary
dist := "." / dist_dir / binary dist := "." / dist_dir / binary
contrib_dir := "." / "contrib"
screenshots_dir := "screenshots"
archive_base := dist_dir / binary + "-" + version
linux_archive := archive_base + "-x86_64.tar"
arm64_archive := archive_base + "-arm64.tar"
arm_archive := archive_base + "-arm.tar"
# do the thing # do the thing
default: test check install default: test check install
@ -19,24 +13,12 @@ default: test check install
build: build:
go build -o {{output}} {{cmd}} go build -o {{output}} {{cmd}}
package $CGO_ENABLED="0": man package:
go build -o {{binary}} {{cmd}} go build -o {{binary}}
tar cafv {{linux_archive}} {{binary}} {{binary}}.1 README.md LICENSE {{screenshots_dir}} distrobox enter alpine -- go build -o {{binary}}-musl {{cmd}}
tar rafv {{linux_archive}} -C {{contrib_dir}} "completions" tar cafv {{dist_dir}}/{{binary}}-{{version}}-x86_64.tar.gz {{binary}} README.md LICENSE
gzip -f {{linux_archive}} tar cafv {{dist_dir}}/{{binary}}-{{version}}-x86_64-musl.tar.gz {{binary}}-musl README.md LICENSE
rm {{binary}} rm {{binary}} {{binary}}-musl
GOARCH="arm" GOARM="7" go build -o {{binary}} {{cmd}}
tar cafv {{arm_archive}} {{binary}} {{binary}}.1 README.md LICENSE {{screenshots_dir}}
tar rafv {{arm_archive}} -C {{contrib_dir}} "completions"
gzip -f {{arm_archive}}
rm {{binary}}
GOARCH="arm64" go build -o {{binary}} {{cmd}}
tar cafv {{arm64_archive}} {{binary}} {{binary}}.1 README.md LICENSE {{screenshots_dir}}
tar rafv {{arm64_archive}} -C {{contrib_dir}} "completions"
gzip -f {{arm64_archive}}
rm {{binary}}
# run from source # run from source
run: run:
@ -62,6 +44,7 @@ install-man:
# clean up after yourself # clean up after yourself
clean: clean:
-rm {{output}} -rm {{output}}
-rm {{output}}-musl
-rm {{dist_dir}}/*.tar.gz -rm {{dist_dir}}/*.tar.gz
# run go tests # run go tests

25
main.go
View file

@ -1,4 +1,3 @@
// Package main does the thing
package main package main
import ( import (
@ -18,12 +17,13 @@ import (
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/term"
) )
const ( const (
appname string = "gt" appname string = "gt"
appsubtitle string = "xdg trash cli" appsubtitle string = "xdg trash cli"
appversion string = "v0.0.2" appversion string = "v0.0.1"
appdesc string = `A small command line program to interface with the appdesc string = `A small command line program to interface with the
Freedesktop.org / XDG trash specification. Freedesktop.org / XDG trash specification.
@ -45,6 +45,8 @@ var (
askconfirm, all bool askconfirm, all bool
workdir, ogdir cli.Path workdir, ogdir cli.Path
recursive bool recursive bool
termwidth int
termheight int
trashDir = filepath.Join(xdg.DataHome, "Trash") trashDir = filepath.Join(xdg.DataHome, "Trash")
@ -62,6 +64,15 @@ var (
log.Errorf("unknown log level '%s' (possible values: debug, info, warn, error, fatal, default: warn)", loglvl) log.Errorf("unknown log level '%s' (possible values: debug, info, warn, error, fatal, default: warn)", loglvl)
} }
// read the term height and width for tables
width, height, e := term.GetSize(int(os.Stdout.Fd()))
if e != nil {
width = 80
height = 24
}
termwidth = width
termheight = height
// ensure trash directories exist // ensure trash directories exist
if _, e := os.Stat(trashDir); os.IsNotExist(e) { if _, e := os.Stat(trashDir); os.IsNotExist(e) {
if err := os.Mkdir(trashDir, executePerm); err != nil { if err := os.Mkdir(trashDir, executePerm); err != nil {
@ -121,7 +132,7 @@ var (
fmt.Fprintln(os.Stdout, msg) fmt.Fprintln(os.Stdout, msg)
return nil return nil
} }
selected, mode, err = interactive.Select(infiles, false, false, workdir, modes.Interactive) selected, mode, err = interactive.Select(infiles, termwidth, termheight, false, false, workdir, modes.Interactive)
if err != nil { if err != nil {
return err return err
} }
@ -222,7 +233,7 @@ var (
filesToTrash = append(filesToTrash, fls...) filesToTrash = append(filesToTrash, fls...)
} }
selected, _, err := interactive.Select(filesToTrash, false, false, workdir, modes.Trashing) selected, _, err := interactive.Select(filesToTrash, termwidth, termheight, false, false, workdir, modes.Trashing)
if err != nil { if err != nil {
return err return err
} }
@ -261,7 +272,7 @@ var (
return err return err
} }
return interactive.Show(fls, noInterArg, workdir) return interactive.Show(fls, termwidth, termheight, noInterArg, workdir)
}, },
} }
@ -283,7 +294,7 @@ var (
return err return err
} }
selected, _, err := interactive.Select(fls, all, all, workdir, modes.Restoring) selected, _, err := interactive.Select(fls, termwidth, termheight, all, all, workdir, modes.Restoring)
if err != nil { if err != nil {
return err return err
} }
@ -312,7 +323,7 @@ var (
return err return err
} }
selected, _, err := interactive.Select(fls, all, all, workdir, modes.Cleaning) selected, _, err := interactive.Select(fls, termwidth, termheight, all, all, workdir, modes.Cleaning)
if err != nil { if err != nil {
return err return err
} }