add interactive tables for all commands
This commit is contained in:
parent
c72b82f542
commit
00cee25075
9
go.mod
9
go.mod
|
@ -4,6 +4,8 @@ go 1.22.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.4.0
|
github.com/adrg/xdg v0.4.0
|
||||||
|
github.com/charmbracelet/bubbles v0.18.0
|
||||||
|
github.com/charmbracelet/bubbletea v0.25.0
|
||||||
github.com/charmbracelet/lipgloss v0.11.0
|
github.com/charmbracelet/lipgloss v0.11.0
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
|
@ -17,16 +19,23 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.1.1 // indirect
|
github.com/charmbracelet/x/ansi v0.1.1 // indirect
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d // indirect
|
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.3.8 // indirect
|
||||||
)
|
)
|
||||||
|
|
21
go.sum
21
go.sum
|
@ -2,12 +2,18 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||||
|
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||||
|
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||||
|
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
||||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
|
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
|
||||||
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -28,12 +34,22 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
@ -57,15 +73,20 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
|
21
internal/dirs/dirs.go
Normal file
21
internal/dirs/dirs.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package dirs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnExpand unexpands some directory shortcuts
|
||||||
|
//
|
||||||
|
// $HOME -> ~
|
||||||
|
func UnExpand(dir string) (outdir string) {
|
||||||
|
var (
|
||||||
|
home = os.Getenv("HOME")
|
||||||
|
)
|
||||||
|
|
||||||
|
outdir = filepath.Clean(dir)
|
||||||
|
outdir = strings.ReplaceAll(outdir, home, "~")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -2,19 +2,13 @@
|
||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.burning.moe/celediel/gt/internal/filter"
|
"git.burning.moe/celediel/gt/internal/filter"
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/charmbracelet/lipgloss/table"
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
|
@ -33,40 +27,6 @@ func (f File) Modified() time.Time { return f.modified }
|
||||||
func (f File) Filesize() int64 { return f.filesize }
|
func (f File) Filesize() int64 { return f.filesize }
|
||||||
func (f File) IsDir() bool { return f.isdir }
|
func (f File) IsDir() bool { return f.isdir }
|
||||||
|
|
||||||
func (fls Files) Table(width int) string {
|
|
||||||
// sort newest on top
|
|
||||||
slices.SortStableFunc(fls, SortByModifiedReverse)
|
|
||||||
|
|
||||||
data := [][]string{}
|
|
||||||
for _, file := range fls {
|
|
||||||
var t, b string
|
|
||||||
t = humanize.Time(file.modified)
|
|
||||||
if file.isdir {
|
|
||||||
b = strings.Repeat("─", 3)
|
|
||||||
} else {
|
|
||||||
b = humanize.Bytes(uint64(file.filesize))
|
|
||||||
}
|
|
||||||
data = append(data, []string{
|
|
||||||
file.name,
|
|
||||||
file.path,
|
|
||||||
t,
|
|
||||||
b,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
t := table.New().
|
|
||||||
Border(lipgloss.RoundedBorder()).
|
|
||||||
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
|
|
||||||
Width(width).
|
|
||||||
Headers("filename", "path", "modified", "size").
|
|
||||||
Rows(data...)
|
|
||||||
|
|
||||||
return fmt.Sprint(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fls Files) Show(width int) {
|
|
||||||
fmt.Println(fls.Table(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Find(dir string, recursive bool, f *filter.Filter) (files Files, err error) {
|
func Find(dir string, recursive bool, f *filter.Filter) (files Files, err error) {
|
||||||
if dir == "." || dir == "" {
|
if dir == "." || dir == "" {
|
||||||
var d string
|
var d string
|
||||||
|
|
386
internal/tables/tables.go
Normal file
386
internal/tables/tables.go
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.burning.moe/celediel/gt/internal/dirs"
|
||||||
|
"git.burning.moe/celediel/gt/internal/files"
|
||||||
|
"git.burning.moe/celediel/gt/internal/trash"
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
"github.com/charmbracelet/bubbles/table"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
uncheck string = "☐"
|
||||||
|
check string = "☑"
|
||||||
|
space string = " "
|
||||||
|
woffset int = 13 // why this number, I don't know
|
||||||
|
hoffset int = 5
|
||||||
|
|
||||||
|
// TODO: make these configurable or something
|
||||||
|
borderbg string = "5"
|
||||||
|
hoveritembg string = "13"
|
||||||
|
black string = "0"
|
||||||
|
darkblack string = "8"
|
||||||
|
white string = "7"
|
||||||
|
darkgray string = "15"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
style = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color(borderbg))
|
||||||
|
regulartext = lipgloss.NewStyle().
|
||||||
|
Padding(0, 2)
|
||||||
|
darktext = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(darkgray))
|
||||||
|
darkertext = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(darkblack))
|
||||||
|
darkesttext = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(black))
|
||||||
|
)
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
table table.Model
|
||||||
|
keys keyMap
|
||||||
|
selected []int
|
||||||
|
readonly bool
|
||||||
|
termheight int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: reconcile trash.Info and files.File into an interface so I can shorten this up
|
||||||
|
|
||||||
|
func newInfosModel(is trash.Infos, width, height int, readonly, preselected bool) model {
|
||||||
|
var (
|
||||||
|
fwidth int = int(math.Round(float64(width-woffset) * 0.4))
|
||||||
|
owidth int = int(math.Round(float64(width-woffset) * 0.2))
|
||||||
|
dwidth int = int(math.Round(float64(width-woffset) * 0.25))
|
||||||
|
swidth int = int(math.Round(float64(width-woffset) * 0.12))
|
||||||
|
cwidth int = int(math.Round(float64(width-woffset) * 0.03))
|
||||||
|
theight int = min(height-hoffset, len(is))
|
||||||
|
|
||||||
|
m = model{
|
||||||
|
keys: defaultKeyMap(),
|
||||||
|
readonly: readonly,
|
||||||
|
termheight: height,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
slices.SortStableFunc(is, trash.SortByTrashedReverse)
|
||||||
|
|
||||||
|
rows := []table.Row{}
|
||||||
|
for j, i := range is {
|
||||||
|
var t, b string
|
||||||
|
t = humanize.Time(i.Trashed())
|
||||||
|
if i.IsDir() {
|
||||||
|
b = strings.Repeat("─", 3)
|
||||||
|
} else {
|
||||||
|
b = humanize.Bytes(uint64(i.Filesize()))
|
||||||
|
}
|
||||||
|
r := table.Row{
|
||||||
|
i.Name(),
|
||||||
|
dirs.UnExpand(filepath.Dir(i.OGPath())),
|
||||||
|
t,
|
||||||
|
b,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.readonly {
|
||||||
|
r = append(r, getCheck(preselected))
|
||||||
|
}
|
||||||
|
if preselected {
|
||||||
|
m.selected = append(m.selected, j)
|
||||||
|
}
|
||||||
|
rows = append(rows, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := []table.Column{
|
||||||
|
{Title: "filename", Width: fwidth},
|
||||||
|
{Title: "original path", Width: owidth},
|
||||||
|
{Title: "deleted", Width: dwidth},
|
||||||
|
{Title: "size", Width: swidth},
|
||||||
|
}
|
||||||
|
if !m.readonly {
|
||||||
|
columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
|
||||||
|
} else {
|
||||||
|
columns[0].Width += cwidth
|
||||||
|
}
|
||||||
|
|
||||||
|
m.table = createTable(columns, rows, theight, m.readonlyOnePage())
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilesModel(fs files.Files, width, height int, readonly, preselected bool) model {
|
||||||
|
var (
|
||||||
|
fwidth int = int(math.Round(float64(width-woffset) * 0.4))
|
||||||
|
owidth int = int(math.Round(float64(width-woffset) * 0.2))
|
||||||
|
dwidth int = int(math.Round(float64(width-woffset) * 0.25))
|
||||||
|
swidth int = int(math.Round(float64(width-woffset) * 0.12))
|
||||||
|
cwidth int = int(math.Round(float64(width-woffset) * 0.03))
|
||||||
|
theight int = min(height-hoffset, len(fs))
|
||||||
|
|
||||||
|
m = model{
|
||||||
|
keys: defaultKeyMap(),
|
||||||
|
readonly: readonly,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
slices.SortStableFunc(fs, files.SortByModifiedReverse)
|
||||||
|
|
||||||
|
rows := []table.Row{}
|
||||||
|
for j, f := range fs {
|
||||||
|
var t, b string
|
||||||
|
t = humanize.Time(f.Modified())
|
||||||
|
if f.IsDir() {
|
||||||
|
b = strings.Repeat("─", 3)
|
||||||
|
} else {
|
||||||
|
b = humanize.Bytes(uint64(f.Filesize()))
|
||||||
|
}
|
||||||
|
r := table.Row{
|
||||||
|
f.Name(),
|
||||||
|
dirs.UnExpand(f.Path()),
|
||||||
|
t,
|
||||||
|
b,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.readonly {
|
||||||
|
r = append(r, getCheck(preselected))
|
||||||
|
}
|
||||||
|
if preselected {
|
||||||
|
m.selected = append(m.selected, j)
|
||||||
|
}
|
||||||
|
rows = append(rows, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := []table.Column{
|
||||||
|
{Title: "filename", Width: fwidth},
|
||||||
|
{Title: "path", Width: owidth},
|
||||||
|
{Title: "modified", Width: dwidth},
|
||||||
|
{Title: "size", Width: swidth},
|
||||||
|
}
|
||||||
|
if !m.readonly {
|
||||||
|
columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
|
||||||
|
} else {
|
||||||
|
columns[0].Width += cwidth
|
||||||
|
}
|
||||||
|
|
||||||
|
m.table = createTable(columns, rows, theight, m.readonlyOnePage())
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyMap struct {
|
||||||
|
mark key.Binding
|
||||||
|
doit key.Binding
|
||||||
|
quit key.Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultKeyMap() keyMap {
|
||||||
|
return keyMap{
|
||||||
|
mark: key.NewBinding(
|
||||||
|
key.WithKeys(space),
|
||||||
|
key.WithHelp("space", "toggle selected"),
|
||||||
|
),
|
||||||
|
doit: key.NewBinding(
|
||||||
|
key.WithKeys("enter", "y"),
|
||||||
|
key.WithHelp("enter/y", "confirm"),
|
||||||
|
),
|
||||||
|
quit: key.NewBinding(
|
||||||
|
key.WithKeys("q", "ctrl+c"),
|
||||||
|
key.WithHelp("q/^c", "quit"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Init() tea.Cmd {
|
||||||
|
if m.readonlyOnePage() {
|
||||||
|
return tea.Quit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch {
|
||||||
|
case key.Matches(msg, m.keys.mark):
|
||||||
|
m.select_item(m.table.SelectedRow(), m.table.Cursor())
|
||||||
|
case key.Matches(msg, m.keys.doit):
|
||||||
|
if !m.readonly {
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
case key.Matches(msg, m.keys.quit):
|
||||||
|
m.selected = []int{}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass events along to the table
|
||||||
|
m.table, cmd = m.table.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) View() (out string) {
|
||||||
|
var (
|
||||||
|
n string
|
||||||
|
panels []string = []string{
|
||||||
|
style.Render(m.table.View()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if m.readonlyOnePage() {
|
||||||
|
n = "\n"
|
||||||
|
} else {
|
||||||
|
panels = append(panels, m.footer())
|
||||||
|
}
|
||||||
|
|
||||||
|
out = lipgloss.JoinVertical(lipgloss.Top, panels...)
|
||||||
|
return out + n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) readonlyOnePage() bool {
|
||||||
|
return m.readonly && m.termheight > m.table.Height()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) showHelp() string {
|
||||||
|
// TODO: maybe use bubbletea built in help
|
||||||
|
var keys []string = []string{
|
||||||
|
fmt.Sprintf("%s %s", darktext.Render(m.keys.quit.Help().Key), darkertext.Render(m.keys.quit.Help().Desc)),
|
||||||
|
}
|
||||||
|
if !m.readonly {
|
||||||
|
keys = append([]string{
|
||||||
|
fmt.Sprintf("%s %s", darktext.Render(m.keys.mark.Help().Key), darkertext.Render(m.keys.mark.Help().Desc)),
|
||||||
|
fmt.Sprintf("%s %s", darktext.Render(m.keys.doit.Help().Key), darkertext.Render(m.keys.doit.Help().Desc)),
|
||||||
|
}, keys...)
|
||||||
|
}
|
||||||
|
return strings.Join(keys, darkesttext.Render(" • "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) footer() string {
|
||||||
|
return regulartext.Render(m.showHelp())
|
||||||
|
}
|
||||||
|
|
||||||
|
// select_item selects an item, and returns its current selected state
|
||||||
|
func (m *model) select_item(row table.Row, index int) (selected bool) {
|
||||||
|
if m.readonly {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// select the thing
|
||||||
|
if slices.Contains(m.selected, index) {
|
||||||
|
// already selected
|
||||||
|
m.selected = slices.DeleteFunc(m.selected, func(other int) bool { return index == other })
|
||||||
|
selected = false
|
||||||
|
} else {
|
||||||
|
// not selected
|
||||||
|
m.selected = append(m.selected, index)
|
||||||
|
selected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the rows with the state
|
||||||
|
rows := m.table.Rows()
|
||||||
|
rows[index] = table.Row{
|
||||||
|
row[0],
|
||||||
|
row[1],
|
||||||
|
row[2],
|
||||||
|
row[3],
|
||||||
|
getCheck(selected),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.table.SetRows(rows)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InfoTable(is trash.Infos, width, height int, readonly, preselected bool) ([]int, error) {
|
||||||
|
if endmodel, err := tea.NewProgram(newInfosModel(is, width, height, readonly, preselected)).Run(); err != nil {
|
||||||
|
return []int{}, err
|
||||||
|
} else {
|
||||||
|
m, ok := endmodel.(model)
|
||||||
|
if ok {
|
||||||
|
return m.selected, nil
|
||||||
|
} else {
|
||||||
|
return []int{}, fmt.Errorf("model isn't the right type??")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilesTable(fs files.Files, width, height int, readonly, preselected bool) ([]int, error) {
|
||||||
|
if endmodel, err := tea.NewProgram(newFilesModel(fs, width, height, readonly, preselected)).Run(); err != nil {
|
||||||
|
return []int{}, err
|
||||||
|
} else {
|
||||||
|
m, ok := endmodel.(model)
|
||||||
|
if ok {
|
||||||
|
return m.selected, nil
|
||||||
|
} else {
|
||||||
|
return []int{}, fmt.Errorf("model isn't the right type??")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCheck(selected bool) (ourcheck string) {
|
||||||
|
if selected {
|
||||||
|
ourcheck = check
|
||||||
|
} else {
|
||||||
|
ourcheck = uncheck
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTable(columns []table.Column, rows []table.Row, height int, readonlyonepage bool) table.Model {
|
||||||
|
t := table.New(
|
||||||
|
table.WithColumns(columns),
|
||||||
|
table.WithRows(rows),
|
||||||
|
table.WithFocused(true),
|
||||||
|
table.WithHeight(height),
|
||||||
|
)
|
||||||
|
t.KeyMap = fixTableKeymap()
|
||||||
|
if readonlyonepage {
|
||||||
|
style := makeStyle()
|
||||||
|
style.Selected = style.Selected.
|
||||||
|
Foreground(lipgloss.NoColor{}).
|
||||||
|
Background(lipgloss.NoColor{}).
|
||||||
|
Bold(false)
|
||||||
|
|
||||||
|
t.SetStyles(style)
|
||||||
|
} else {
|
||||||
|
t.SetStyles(makeStyle())
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixTableKeymap() table.KeyMap {
|
||||||
|
t := table.DefaultKeyMap()
|
||||||
|
|
||||||
|
// remove spacebar from default page down keybind, but keep the rest
|
||||||
|
t.PageDown.SetKeys(
|
||||||
|
slices.DeleteFunc(t.PageDown.Keys(), func(s string) bool {
|
||||||
|
return s == space
|
||||||
|
})...,
|
||||||
|
)
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStyle() table.Styles {
|
||||||
|
s := table.DefaultStyles()
|
||||||
|
s.Header = s.Header.
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(lipgloss.Color(black)).
|
||||||
|
BorderBottom(true).
|
||||||
|
Bold(false)
|
||||||
|
s.Selected = s.Selected.
|
||||||
|
Foreground(lipgloss.Color(white)).
|
||||||
|
Background(lipgloss.Color(hoveritembg)).
|
||||||
|
Bold(false)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
|
@ -3,19 +3,14 @@
|
||||||
package trash
|
package trash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.burning.moe/celediel/gt/internal/filter"
|
"git.burning.moe/celediel/gt/internal/filter"
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/charmbracelet/lipgloss/table"
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"gitlab.com/tymonx/go-formatter/formatter"
|
"gitlab.com/tymonx/go-formatter/formatter"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
@ -49,41 +44,6 @@ func (i Info) Trashed() time.Time { return i.trashed }
|
||||||
func (i Info) Filesize() int64 { return i.filesize }
|
func (i Info) Filesize() int64 { return i.filesize }
|
||||||
func (i Info) IsDir() bool { return i.isdir }
|
func (i Info) IsDir() bool { return i.isdir }
|
||||||
|
|
||||||
func (is Infos) Table(width int) string {
|
|
||||||
|
|
||||||
// sort newest on top
|
|
||||||
slices.SortStableFunc(is, SortByTrashedReverse)
|
|
||||||
out := [][]string{}
|
|
||||||
for _, file := range is {
|
|
||||||
var t, b string
|
|
||||||
t = humanize.Time(file.trashed)
|
|
||||||
if file.isdir {
|
|
||||||
b = strings.Repeat("─", 3)
|
|
||||||
} else {
|
|
||||||
b = humanize.Bytes(uint64(file.filesize))
|
|
||||||
}
|
|
||||||
out = append(out, []string{
|
|
||||||
file.name,
|
|
||||||
filepath.Dir(file.ogpath),
|
|
||||||
t,
|
|
||||||
b,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t := table.New().
|
|
||||||
Border(lipgloss.RoundedBorder()).
|
|
||||||
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
|
|
||||||
Width(width).
|
|
||||||
Headers("filename", "original path", "deleted", "size").
|
|
||||||
Rows(out...)
|
|
||||||
|
|
||||||
return fmt.Sprint(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (is Infos) Show(width int) {
|
|
||||||
fmt.Println(is.Table(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr error) {
|
func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr error) {
|
||||||
outerr = filepath.WalkDir(trashdir, func(path string, d fs.DirEntry, err error) error {
|
outerr = filepath.WalkDir(trashdir, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -103,6 +63,7 @@ func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr er
|
||||||
}
|
}
|
||||||
if s := c.Section(trash_info_sec); s != nil {
|
if s := c.Section(trash_info_sec); s != nil {
|
||||||
basepath := s.Key(trash_info_path).String()
|
basepath := s.Key(trash_info_path).String()
|
||||||
|
|
||||||
filename := filepath.Base(basepath)
|
filename := filepath.Base(basepath)
|
||||||
// maybe this is kind of a HACK
|
// maybe this is kind of a HACK
|
||||||
trashedpath := strings.Replace(strings.Replace(path, "info", "files", 1), trash_info_ext, "", 1)
|
trashedpath := strings.Replace(strings.Replace(path, "info", "files", 1), trash_info_ext, "", 1)
|
||||||
|
@ -153,7 +114,6 @@ func Restore(files []Info) (restored int, err error) {
|
||||||
}
|
}
|
||||||
restored++
|
restored++
|
||||||
}
|
}
|
||||||
fmt.Printf("restored %d files\n", restored)
|
|
||||||
return restored, err
|
return restored, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
161
main.go
161
main.go
|
@ -1,16 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.burning.moe/celediel/gt/internal/files"
|
"git.burning.moe/celediel/gt/internal/files"
|
||||||
"git.burning.moe/celediel/gt/internal/filter"
|
"git.burning.moe/celediel/gt/internal/filter"
|
||||||
|
"git.burning.moe/celediel/gt/internal/tables"
|
||||||
"git.burning.moe/celediel/gt/internal/trash"
|
"git.burning.moe/celediel/gt/internal/trash"
|
||||||
|
|
||||||
"github.com/adrg/xdg"
|
"github.com/adrg/xdg"
|
||||||
|
@ -33,6 +33,7 @@ var (
|
||||||
workdir, ogdir cli.Path
|
workdir, ogdir cli.Path
|
||||||
recursive bool
|
recursive bool
|
||||||
termwidth int
|
termwidth int
|
||||||
|
termheight int
|
||||||
|
|
||||||
trashDir = filepath.Join(xdg.DataHome, "Trash")
|
trashDir = filepath.Join(xdg.DataHome, "Trash")
|
||||||
|
|
||||||
|
@ -48,11 +49,13 @@ var (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w, _, e := term.GetSize(int(os.Stdout.Fd()))
|
w, h, e := term.GetSize(int(os.Stdout.Fd()))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
w = 80
|
w = 80
|
||||||
|
h = 24
|
||||||
}
|
}
|
||||||
termwidth = w
|
termwidth = w
|
||||||
|
termheight = h
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,10 +89,23 @@ var (
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fls.Show(termwidth)
|
indices, err := tables.FilesTable(fls, termwidth, termheight, false, !f.Blank())
|
||||||
if confirm(fmt.Sprintf("trash these %d files?", len(fls))) {
|
if err != nil {
|
||||||
tfs := make([]string, 0, len(fls))
|
return err
|
||||||
for _, file := range fls {
|
}
|
||||||
|
|
||||||
|
var selected files.Files
|
||||||
|
for _, i := range indices {
|
||||||
|
selected = append(selected, fls[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(selected) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm(fmt.Sprintf("trash %d selected files?", len(selected))) {
|
||||||
|
tfs := make([]string, 0, len(selected))
|
||||||
|
for _, file := range selected {
|
||||||
log.Debugf("gonna trash %s", file.Filename())
|
log.Debugf("gonna trash %s", file.Filename())
|
||||||
tfs = append(tfs, file.Filename())
|
tfs = append(tfs, file.Filename())
|
||||||
}
|
}
|
||||||
|
@ -98,15 +114,50 @@ var (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("trashed %d files", trashed)
|
fmt.Printf("trashed %d files\n", trashed)
|
||||||
} else {
|
} else {
|
||||||
log.Info("not gonna do it")
|
fmt.Printf("not doing anything\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action = func(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
fls trash.Infos
|
||||||
|
indicies []int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
f, err = filter.New(o, b, a, g, p, ung, unp, ctx.Args().Slice()...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fls, err = trash.FindFiles(trashDir, ogdir, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fls) <= 0 {
|
||||||
|
log.Printf("no files to show")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
indicies, err = tables.InfoTable(fls, termwidth, termheight, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range indicies {
|
||||||
|
log.Printf("gonna do something with %s", fls[i].Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
do_list = &cli.Command{
|
do_list = &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
|
@ -117,7 +168,7 @@ var (
|
||||||
log.Debugf("searching in directory %s for files", trashDir)
|
log.Debugf("searching in directory %s for files", trashDir)
|
||||||
|
|
||||||
// look for files
|
// look for files
|
||||||
files, err := trash.FindFiles(trashDir, ogdir, f)
|
fls, err := trash.FindFiles(trashDir, ogdir, f)
|
||||||
|
|
||||||
var msg string
|
var msg string
|
||||||
if f.Blank() {
|
if f.Blank() {
|
||||||
|
@ -126,7 +177,7 @@ var (
|
||||||
msg = "no files to show"
|
msg = "no files to show"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(files) == 0 {
|
if len(fls) == 0 {
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -134,9 +185,9 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// display them
|
// display them
|
||||||
files.Show(termwidth)
|
_, err = tables.InfoTable(fls, termwidth, termheight, true, false)
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,24 +201,37 @@ var (
|
||||||
log.Debugf("searching in directory %s for files", trashDir)
|
log.Debugf("searching in directory %s for files", trashDir)
|
||||||
|
|
||||||
// look for files
|
// look for files
|
||||||
files, err := trash.FindFiles(trashDir, ogdir, f)
|
fls, err := trash.FindFiles(trashDir, ogdir, f)
|
||||||
if len(files) == 0 {
|
if len(fls) == 0 {
|
||||||
fmt.Println("no files to restore")
|
fmt.Println("no files to restore")
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
files.Show(termwidth)
|
indices, err := tables.InfoTable(fls, termwidth, termheight, false, !f.Blank())
|
||||||
if confirm(fmt.Sprintf("restore these %d files?", len(files))) {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected trash.Infos
|
||||||
|
for _, i := range indices {
|
||||||
|
selected = append(selected, fls[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(selected) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm(fmt.Sprintf("restore %d selected files?", len(selected))) {
|
||||||
log.Info("doing the thing")
|
log.Info("doing the thing")
|
||||||
restored, err := trash.Restore(files)
|
restored, err := trash.Restore(selected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("restored %d files before error %s", restored, err)
|
return fmt.Errorf("restored %d files before error %s", restored, err)
|
||||||
}
|
}
|
||||||
log.Printf("restored %d files\n", restored)
|
fmt.Printf("restored %d files\n", restored)
|
||||||
} else {
|
} else {
|
||||||
log.Info("not gonna do it")
|
fmt.Printf("not doing anything\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -181,25 +245,38 @@ var (
|
||||||
Flags: slices.Concat(alreadyintrash_flags, filter_flags),
|
Flags: slices.Concat(alreadyintrash_flags, filter_flags),
|
||||||
Before: before_commands,
|
Before: before_commands,
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
files, err := trash.FindFiles(trashDir, ogdir, f)
|
fls, err := trash.FindFiles(trashDir, ogdir, f)
|
||||||
if len(files) == 0 {
|
if len(fls) == 0 {
|
||||||
fmt.Println("no files to clean")
|
fmt.Println("no files to clean")
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
files.Show(termwidth)
|
indices, err := tables.InfoTable(fls, termwidth, termheight, false, !f.Blank())
|
||||||
if confirm(fmt.Sprintf("remove these %d files permanently from the trash?", len(files))) &&
|
if err != nil {
|
||||||
confirm(fmt.Sprintf("really remove all %d of these files permanently from the trash forever??", len(files))) {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected trash.Infos
|
||||||
|
for _, i := range indices {
|
||||||
|
selected = append(selected, fls[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(selected) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm(fmt.Sprintf("remove %d selected files permanently from the trash?", len(selected))) &&
|
||||||
|
confirm(fmt.Sprintf("really remove all these %d selected files permanently from the trash forever??", len(selected))) {
|
||||||
log.Info("gonna remove some files forever")
|
log.Info("gonna remove some files forever")
|
||||||
removed, err := trash.Remove(files)
|
removed, err := trash.Remove(selected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("removed %d files before error %s", removed, err)
|
return fmt.Errorf("removed %d files before error %s", removed, err)
|
||||||
}
|
}
|
||||||
log.Printf("removed %d files\n", removed)
|
fmt.Printf("removed %d files\n", removed)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("left %d files alone", len(files))
|
fmt.Printf("not doing anything\n")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -294,6 +371,7 @@ func main() {
|
||||||
Version: appversion,
|
Version: appversion,
|
||||||
Before: before_all,
|
Before: before_all,
|
||||||
After: after,
|
After: after,
|
||||||
|
Action: action,
|
||||||
Commands: []*cli.Command{do_trash, do_list, do_restore, do_clean},
|
Commands: []*cli.Command{do_trash, do_list, do_restore, do_clean},
|
||||||
Flags: global_flags,
|
Flags: global_flags,
|
||||||
}
|
}
|
||||||
|
@ -303,16 +381,27 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func confirm(s string) bool {
|
func confirm(prompt string) bool {
|
||||||
r := bufio.NewReader(os.Stdin)
|
// TODO: handle errors better
|
||||||
fmt.Printf("%s [y/n]: ", s)
|
// switch stdin into 'raw' mode
|
||||||
got, err := r.ReadString('\n')
|
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if len(got) < 2 {
|
defer func() {
|
||||||
|
if err := term.Restore(int(os.Stdin.Fd()), oldState); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Printf("%s [y/n]: ", prompt)
|
||||||
|
|
||||||
|
// read one byte from stdin
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err = os.Stdin.Read(b)
|
||||||
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
} else {
|
|
||||||
return strings.ToLower(strings.TrimSpace(got))[0] == 'y'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return bytes.ToLower(b)[0] == 'y'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue