1//go:build darwin || aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris
2
3package fastwalk
4
5import (
6 "io/fs"
7 "os"
8 "sort"
9 "sync"
10
11 "github.com/charlievieth/fastwalk/internal/fmtdirent"
12)
13
14type unixDirent struct {
15 parent string
16 name string
17 typ fs.FileMode
18 depth uint32 // uint32 so that we can pack it next to typ
19 info *fileInfo
20 stat *fileInfo
21}
22
23func (d *unixDirent) Name() string { return d.name }
24func (d *unixDirent) IsDir() bool { return d.typ.IsDir() }
25func (d *unixDirent) Type() fs.FileMode { return d.typ }
26func (d *unixDirent) Depth() int { return int(d.depth) }
27func (d *unixDirent) String() string { return fmtdirent.FormatDirEntry(d) }
28
29func (d *unixDirent) Info() (fs.FileInfo, error) {
30 info := loadFileInfo(&d.info)
31 info.once.Do(func() {
32 info.FileInfo, info.err = os.Lstat(d.parent + "/" + d.name)
33 })
34 return info.FileInfo, info.err
35}
36
37func (d *unixDirent) Stat() (fs.FileInfo, error) {
38 if d.typ&os.ModeSymlink == 0 {
39 return d.Info()
40 }
41 stat := loadFileInfo(&d.stat)
42 stat.once.Do(func() {
43 stat.FileInfo, stat.err = os.Stat(d.parent + "/" + d.name)
44 })
45 return stat.FileInfo, stat.err
46}
47
48func newUnixDirent(parent, name string, typ fs.FileMode, depth int) *unixDirent {
49 return &unixDirent{
50 parent: parent,
51 name: name,
52 typ: typ,
53 depth: uint32(depth),
54 }
55}
56
57func fileInfoToDirEntry(dirname string, fi fs.FileInfo) DirEntry {
58 info := &fileInfo{
59 FileInfo: fi,
60 }
61 info.once.Do(func() {})
62 return &unixDirent{
63 parent: dirname,
64 name: fi.Name(),
65 typ: fi.Mode().Type(),
66 info: info,
67 }
68}
69
70var direntSlicePool = sync.Pool{
71 New: func() any {
72 a := make([]*unixDirent, 0, 32)
73 return &a
74 },
75}
76
77func putDirentSlice(p *[]*unixDirent) {
78 if p != nil && cap(*p) <= 32*1024 /* 256Kb */ {
79 a := *p
80 for i := range a {
81 a[i] = nil
82 }
83 *p = a[:0]
84 direntSlicePool.Put(p)
85 }
86}
87
88func sortDirents(mode SortMode, dents []*unixDirent) {
89 if len(dents) <= 1 {
90 return
91 }
92 switch mode {
93 case SortLexical:
94 sort.Slice(dents, func(i, j int) bool {
95 return dents[i].name < dents[j].name
96 })
97 case SortFilesFirst:
98 sort.Slice(dents, func(i, j int) bool {
99 d1 := dents[i]
100 d2 := dents[j]
101 r1 := d1.typ.IsRegular()
102 r2 := d2.typ.IsRegular()
103 switch {
104 case r1 && !r2:
105 return true
106 case !r1 && r2:
107 return false
108 case !r1 && !r2:
109 // Both are not regular files: sort directories last
110 dd1 := d1.typ.IsDir()
111 dd2 := d2.typ.IsDir()
112 switch {
113 case !dd1 && dd2:
114 return true
115 case dd1 && !dd2:
116 return false
117 }
118 }
119 return d1.name < d2.name
120 })
121 case SortDirsFirst:
122 sort.Slice(dents, func(i, j int) bool {
123 d1 := dents[i]
124 d2 := dents[j]
125 dd1 := d1.typ.IsDir()
126 dd2 := d2.typ.IsDir()
127 switch {
128 case dd1 && !dd2:
129 return true
130 case !dd1 && dd2:
131 return false
132 case !dd1 && !dd2:
133 // Both are not directories: sort regular files first
134 r1 := d1.typ.IsRegular()
135 r2 := d2.typ.IsRegular()
136 switch {
137 case r1 && !r2:
138 return true
139 case !r1 && r2:
140 return false
141 }
142 }
143 return d1.name < d2.name
144 })
145 }
146}