1
2
3
4
5 package main
6
7 import (
8 "io/fs"
9 "log"
10 "os"
11 "path"
12 "path/filepath"
13 "sort"
14 "strings"
15 "time"
16 )
17
18
19
20 type Archive struct {
21 Files []File
22 }
23
24
25 type File struct {
26 Name string
27 Time time.Time
28 Mode fs.FileMode
29 Size int64
30 Src string
31 }
32
33
34
35 func (f *File) Info() fs.FileInfo {
36 return fileInfo{f}
37 }
38
39
40 type fileInfo struct {
41 f *File
42 }
43
44 func (i fileInfo) Name() string { return path.Base(i.f.Name) }
45 func (i fileInfo) ModTime() time.Time { return i.f.Time }
46 func (i fileInfo) Mode() fs.FileMode { return i.f.Mode }
47 func (i fileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 }
48 func (i fileInfo) Size() int64 { return i.f.Size }
49 func (i fileInfo) Sys() any { return nil }
50
51 func (i fileInfo) String() string {
52 return fs.FormatFileInfo(i)
53 }
54
55
56
57 func NewArchive(dir string) (*Archive, error) {
58 a := new(Archive)
59 err := fs.WalkDir(os.DirFS(dir), ".", func(name string, d fs.DirEntry, err error) error {
60 if err != nil {
61 return err
62 }
63 if d.IsDir() {
64 return nil
65 }
66 info, err := d.Info()
67 if err != nil {
68 return err
69 }
70 a.Add(name, filepath.Join(dir, name), info)
71 return nil
72 })
73 if err != nil {
74 return nil, err
75 }
76 a.Sort()
77 return a, nil
78 }
79
80
81
82
83
84 func (a *Archive) Add(name, src string, info fs.FileInfo) {
85 a.Files = append(a.Files, File{
86 Name: name,
87 Time: info.ModTime(),
88 Mode: info.Mode(),
89 Size: info.Size(),
90 Src: src,
91 })
92 }
93
94 func nameLess(x, y string) bool {
95 for i := 0; i < len(x) && i < len(y); i++ {
96 if x[i] != y[i] {
97
98 if x[i] == '/' {
99 return true
100 }
101 if y[i] == '/' {
102 return false
103 }
104 return x[i] < y[i]
105 }
106 }
107 return len(x) < len(y)
108 }
109
110
111
112
113
114 func (a *Archive) Sort() {
115 sort.Slice(a.Files, func(i, j int) bool {
116 return nameLess(a.Files[i].Name, a.Files[j].Name)
117 })
118 }
119
120
121
122
123 func (a *Archive) Clone() *Archive {
124 b := &Archive{
125 Files: make([]File, len(a.Files)),
126 }
127 copy(b.Files, a.Files)
128 return b
129 }
130
131
132 func (a *Archive) AddPrefix(prefix string) {
133 for i := range a.Files {
134 a.Files[i].Name = path.Join(prefix, a.Files[i].Name)
135 }
136 }
137
138
139 func (a *Archive) Filter(keep func(name string) bool) {
140 files := a.Files[:0]
141 for _, f := range a.Files {
142 if keep(f.Name) {
143 files = append(files, f)
144 }
145 }
146 a.Files = files
147 }
148
149
150
151 func (a *Archive) SetMode(mode func(name string, m fs.FileMode) fs.FileMode) {
152 for i := range a.Files {
153 a.Files[i].Mode = mode(a.Files[i].Name, a.Files[i].Mode)
154 }
155 }
156
157
158
159
160
161 func (a *Archive) Remove(patterns ...string) {
162 a.Filter(func(name string) bool {
163 for _, pattern := range patterns {
164 match, err := amatch(pattern, name)
165 if err != nil {
166 log.Fatalf("archive remove: %v", err)
167 }
168 if match {
169 return false
170 }
171 }
172 return true
173 })
174 }
175
176
177 func (a *Archive) SetTime(t time.Time) {
178 for i := range a.Files {
179 a.Files[i].Time = t
180 }
181 }
182
183
184
185 func (a *Archive) RenameGoMod() {
186 for i, f := range a.Files {
187 if strings.HasSuffix(f.Name, "/go.mod") {
188 a.Files[i].Name = strings.TrimSuffix(f.Name, "go.mod") + "_go.mod"
189 }
190 }
191 }
192
193 func amatch(pattern, name string) (bool, error) {
194
195
196 firstN := func(name string, n int) string {
197 for i := 0; i < len(name); i++ {
198 if name[i] == '/' {
199 if n--; n == 0 {
200 return name[:i]
201 }
202 }
203 }
204 return name
205 }
206
207
208
209 lastN := func(name string, n int) string {
210 for i := len(name) - 1; i >= 0; i-- {
211 if name[i] == '/' {
212 if n--; n == 0 {
213 return name[i+1:]
214 }
215 }
216 }
217 return name
218 }
219
220 if p, ok := strings.CutPrefix(pattern, "**/"); ok {
221 return path.Match(p, lastN(name, 1+strings.Count(p, "/")))
222 }
223 if p, ok := strings.CutSuffix(pattern, "/**"); ok {
224 return path.Match(p, firstN(name, 1+strings.Count(p, "/")))
225 }
226 return path.Match(pattern, name)
227 }
228
View as plain text