Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ support file extended attributes #187

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion completions/bash/g-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ _g() {
--literal -O
--no-owner -Q
--quote-name -g -l
--long -o"
--long -o
--extended -@"

COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
Expand Down
1 change: 1 addition & 0 deletions completions/fish/g.fish
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,5 @@ complete -c g -s Q -l quote-name -d "enclose entry names in double quotes"
complete -c g -s g -d "like -all, but do not list owner"
complete -c g -s l -l long -d "use a long listing format"
complete -c g -s o -d "like -all, but do not list group information"
complete -c g -s @ -l extended -d "list each file's extended attributes and sizes in long listing"
complete -c g -f -a "(__fish_complete_path)"
2 changes: 2 additions & 0 deletions completions/zsh/_g
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ _g() {
'-l[use a long listing format]'
'--long[use a long listing format]'
'-o[like -all, but do not list group information]'
'-@[list each file's extended attributes and sizes in long listing]'
'--extended[list each file's extended attributes and sizes in long listing]'
'*:filename:_files'
)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ require (
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
howett.net/plist v1.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/junegunn/fzf v0.0.0-20240109033114-e4d0f7acd516 h1:QVNsMfKwLPfv0Ajq6LlBgS/dchHP9c/i3lTQRyhkxRs=
Expand Down Expand Up @@ -298,10 +299,13 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
22 changes: 14 additions & 8 deletions internal/cli/g.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (
groupEnabler = contents.NewGroupEnabler()
gitEnabler = contents.NewGitEnabler()
gitRepoEnabler = contents.NewGitRepoEnabler()
extendedEnabler = contents.ExtendedEnabler{}
nameToDisplay = contents.NewNameEnabler()
flagsEnabler = contents.NewFlagsEnabler()
depthLimitMap map[string]int
Expand Down Expand Up @@ -202,8 +203,8 @@ func dive(
continue
}
// store its parent and level/depth
info.Cache["parent"] = []byte(parent)
info.Cache["level"] = []byte(strconv.Itoa(depth))
info.Cache["parent"] = parent
info.Cache["level"] = depth
infos.AppendTo(info)
if f.IsDir() {
wg.Add(1)
Expand Down Expand Up @@ -523,6 +524,11 @@ var logic = func(context *cli.Context) error {
contentFunc = append(contentFunc, gitEnabler.Enable(r))
}

extended := context.Bool("extended")
if extended {
noOutputFunc = append(noOutputFunc, extendedEnabler.Enable(r))
}

gitBranch := context.Bool("git-repo-branch")
if gitBranch {
contentFunc = append(contentFunc, gitRepoEnabler.Enable(r))
Expand Down Expand Up @@ -619,7 +625,7 @@ var logic = func(context *cli.Context) error {
case "never":
case "always":
nameToDisplay.SetHyperlink()
display.IncludeHyperlink = true
global.IncludeHyperlink = true
default:
fallthrough
case "auto":
Expand All @@ -629,7 +635,7 @@ var logic = func(context *cli.Context) error {
case *display.JsonPrinter:
default:
nameToDisplay.SetHyperlink()
display.IncludeHyperlink = true
global.IncludeHyperlink = true
}
}
}
Expand Down Expand Up @@ -800,7 +806,7 @@ var logic = func(context *cli.Context) error {
infos = itemFilter.Filter(infos...)

if tree {
infos[0].Cache["level"] = []byte("0")
infos[0].Cache["level"] = 0
}
goto final
}
Expand All @@ -815,7 +821,7 @@ var logic = func(context *cli.Context) error {
infos = append(
infos, info,
)
infos[0].Cache["level"] = []byte("0")
infos[0].Cache["level"] = 0
if depth >= 1 || depth < 0 {
wg := sync.WaitGroup{}
infoSlice := util.NewSlice[*item.FileInfo](10)
Expand Down Expand Up @@ -981,7 +987,7 @@ var logic = func(context *cli.Context) error {
for _, part := range allPart {
content, ok := it.Get(part)
if ok && part != contents.NameName {
l := display.WidthNoHyperLinkLen(content.String())
l := util.WidthNoHyperLinkLen(content.String())
if l > longestEachPart[part] {
longestEachPart[part] = l
}
Expand All @@ -994,7 +1000,7 @@ var logic = func(context *cli.Context) error {
for _, part := range allPart {
if part != contents.NameName {
content, _ := it.Get(part)
l := display.WidthNoHyperLinkLen(content.String())
l := util.WidthNoHyperLinkLen(content.String())
if l < longestEachPart[part] {
expand := content.SetPrefix
if align.IsLeft(part) {
Expand Down
7 changes: 7 additions & 0 deletions internal/cli/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,13 @@ var viewFlag = []cli.Flag{
DisableDefaultText: true,
Category: "VIEW",
},
&cli.BoolFlag{
Name: "extended",
Aliases: []string{"@"},
Usage: `list each file's extended attributes and sizes in long listing`,
DisableDefaultText: true,
Category: "VIEW",
},
}

func setLimit(context *cli.Context) error {
Expand Down
4 changes: 2 additions & 2 deletions internal/content/charset.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (c *CharsetEnabler) Enable(renderer *render.Renderer) ContentOption {
return func(info *item.FileInfo) (string, string) {
res, returnName := func() (string, string) {
if c, ok := info.Cache[Charset]; ok {
return string(c), Charset
return c.(string), Charset
}
// only text file has charset
if info.IsDir() {
Expand Down Expand Up @@ -63,7 +63,7 @@ func (c *CharsetEnabler) Enable(renderer *render.Renderer) ContentOption {
return "failed_to_detect", Charset
}
charset = best.Charset
info.Cache[Charset] = []byte(charset)
info.Cache[Charset] = charset
}
return charset, Charset
}
Expand Down
2 changes: 1 addition & 1 deletion internal/content/duplicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func fileHash(fileInfo *item.FileInfo, isThorough bool) (string, error) {
var fileReadErr error
if isThorough || fileInfo.Size() <= thresholdFileSize {
if content, ok := fileInfo.Cache["content"]; ok {
bytes = content
bytes = content.([]byte)
} else {
bytes, fileReadErr = os.ReadFile(fileInfo.FullPath)
if fileReadErr != nil {
Expand Down
69 changes: 69 additions & 0 deletions internal/content/extended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package content

import (
"encoding/xml"
"fmt"
"github.com/Equationzhao/g/internal/global"
"github.com/Equationzhao/g/internal/item"
"github.com/Equationzhao/g/internal/render"
"github.com/pkg/xattr"
"github.com/valyala/bytebufferpool"
"howett.net/plist"
)

type ExtendedEnabler struct{}

const Extended = global.NameOfExtended

func formatBytes(bytes []byte) string {
res := bytebufferpool.Get()
defer bytebufferpool.Put(res)
_ = res.WriteByte('[')
for i, b := range bytes {
if i > 0 {
_, _ = res.WriteString(", ")
}
_, _ = res.WriteString(fmt.Sprintf("%02x", b))
}
_ = res.WriteByte(']')
return res.String()
}

// formatXattrValue attempts to parse the xattr value and returns a human-readable string.
func formatXattrValue(value []byte) string {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image the result was too long

maybe it could be parsed into human-readable strings?

if len(value) == 0 {
return "<empty>"
}

// Check if the value is a binary plist
var plistData any
if _, err := plist.Unmarshal(value, &plistData); err == nil {
xmlOut, err := xml.MarshalIndent(plistData, "", " ")
if err == nil {
return string(xmlOut)
}
}
// Default to hex encoding
return formatBytes(value)
}

func (e ExtendedEnabler) Enable(renderer *render.Renderer) NoOutputOption {
return func(info *item.FileInfo) {
var list any
ok := false
if list, ok = info.Cache[Extended]; !ok {
list, _ = xattr.LList(info.FullPath)
info.Cache[Extended] = list
}
if lists, ok := list.([]string); ok && len(lists) > 0 {
for j, key := range lists {
val, _ := xattr.LGet(info.FullPath, key)
if j == len(lists)-1 {
info.AfterLines = append(info.AfterLines, renderer.Extended(fmt.Sprintf("└── %s: %s", key, formatXattrValue(val))))
} else {
info.AfterLines = append(info.AfterLines, renderer.Extended(fmt.Sprintf("├── %s: %s", key, formatXattrValue(val))))
}
}
}
}
}
2 changes: 1 addition & 1 deletion internal/content/inode.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (i *InodeEnabler) Enable(renderer *render.Renderer) ContentOption {
return func(info *item.FileInfo) (string, string) {
i := ""
if m, ok := info.Cache[Inode]; ok {
i = string(m)
i = m.(string)
} else {
i = osbased.Inode(info)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/content/mimetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption {
returnName = ParentMimeTypeName
}
if c, ok := info.Cache[MimeTypeName]; ok {
tn = string(c)
tn = c.(string)
} else {
if info.IsDir() {
tn = "directory"
Expand All @@ -58,7 +58,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption {
}
if m, ok := info.Cache[MimeTypeName]; ok {
info.Cache[Charset] = m
return string(m), returnName
return m.(string), returnName
}

file, err := os.Open(info.FullPath)
Expand All @@ -83,7 +83,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption {
s := strings.SplitN(tn, ";", 2)
tn = s[0]
charset := strings.SplitN(s[1], "=", 2)[1]
info.Cache[Charset] = []byte(charset)
info.Cache[Charset] = charset
}

return tn, returnName
Expand Down
8 changes: 6 additions & 2 deletions internal/content/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ import (
"strconv"

"github.com/Equationzhao/g/internal/align"
constval "github.com/Equationzhao/g/internal/global"
"github.com/Equationzhao/g/internal/global"
"github.com/Equationzhao/g/internal/item"
"github.com/Equationzhao/g/internal/render"
"github.com/pkg/xattr"
)

const Permissions = constval.NameOfPermission
const Permissions = global.NameOfPermission

// EnableFileMode return file mode like -rwxrwxrwx/drwxrwxrwx
func EnableFileMode(renderer *render.Renderer) ContentOption {
align.Register(Permissions)
return func(info *item.FileInfo) (string, string) {
perm := renderer.FileMode(info.Mode().String())
if info.Cache[Extended] != nil {
perm += "@"
}
list, _ := xattr.LList(info.FullPath)
info.Cache[Extended] = list
if len(list) != 0 {
perm += "@"
}
Expand Down
2 changes: 1 addition & 1 deletion internal/content/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func (s *SizeEnabler) EnableSize(size SizeUnit, renderer *render.Renderer) Conte
if s.recursive != nil {
if r, ok := info.Cache[RecursiveSizeName]; ok {
// convert []byte to int64
v, _ = strconv.ParseInt(string(r), 10, 64)
v = r.(int64)
} else {
v = util.RecursivelySizeOf(info, s.recursive.depth)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/content/sum.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (s SumEnabler) EnableSum(renderer *render.Renderer, sumTypes ...SumType) []

var content []byte
if content_, ok := info.Cache["content"]; ok {
content = content_
content = content_.([]byte)
} else {
file, err := os.Open(info.FullPath)
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion internal/display/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strings"

"github.com/Equationzhao/g/internal/util"

"github.com/Equationzhao/g/internal/align"
constval "github.com/Equationzhao/g/internal/global"
"github.com/Equationzhao/g/internal/item"
Expand Down Expand Up @@ -52,7 +54,7 @@ func (h HeaderMaker) Make(p Printer, Items ...*item.FileInfo) {
for _, it := range Items {
content, _ := it.Get(s)
if s != constval.NameOfName {
toAddNum := len(s) - WidthNoHyperLinkLen(content.String())
toAddNum := len(s) - util.WidthNoHyperLinkLen(content.String())
if align.IsLeft(s) {
content.AddSuffix(strings.Repeat(" ", toAddNum))
} else {
Expand Down
Loading
Loading