-
Notifications
You must be signed in to change notification settings - Fork 2
/
rebed.go
201 lines (189 loc) · 5.75 KB
/
rebed.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Package rebed brings simple embedded file functionality
// to Go's new embed directive.
//
// It can recreate the directory structure
// from the embed.FS type with or without
// the files it contains. This is useful to
// expose the filesystem to the end user so they
// may see and modify the files.
//
// It also provides basic directory walking functionality for
// the embed.FS type.
package rebed
import (
"embed"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
// FolderMode MkdirAll is called with this permission to prevent restricted folders
// from being created. Default value is 0755=rwxr-xr-x.
var FolderMode os.FileMode = 0755
// ErrExist returned by Create when encountering
// a file conflict in filesystem creation
var ErrExist error = os.ErrExist
// Tree creates the target filesystem folder structure.
func Tree(fsys embed.FS, outputPath string) error {
return Walk(fsys, ".", func(dirpath string, de fs.DirEntry) error {
fullpath := filepath.Join(outputPath, dirpath, de.Name())
if de.IsDir() {
return os.MkdirAll(fullpath, FolderMode)
}
return nil
})
}
// Touch creates the target filesystem folder structure in the binary's
// current working directory with empty files. Does not modify
// already existing files.
func Touch(fsys embed.FS, outputPath string) error {
return Walk(fsys, ".", func(dirpath string, de fs.DirEntry) error {
fullpath := filepath.Join(outputPath, dirpath, de.Name())
if de.IsDir() {
return os.MkdirAll(fullpath, FolderMode)
}
// unsure how IsNotExist works. this could be improved
_, err := os.Stat(fullpath)
if os.IsNotExist(err) {
_, err = os.Create(fullpath)
}
return err
})
}
// Write overwrites files of same path/name
// in binaries current working directory or
// creates new ones if not exist.
func Write(fsys embed.FS, outputPath string) error {
return Walk(fsys, ".", func(dirpath string, de fs.DirEntry) error {
embedPath := sanitize(filepath.Join(dirpath, de.Name()))
fullpath := filepath.Join(outputPath, embedPath)
if de.IsDir() {
return os.MkdirAll(fullpath, FolderMode)
}
return embedCopyToFile(fsys, embedPath, fullpath)
})
}
// Patch creates files which are missing in
// FS filesystem. Does not modify existing files
func Patch(fsys embed.FS, outputPath string) error {
return Walk(fsys, ".", func(dirpath string, de fs.DirEntry) error {
embedPath := sanitize(filepath.Join(dirpath, de.Name()))
fullpath := filepath.Join(outputPath, embedPath)
if de.IsDir() {
return os.MkdirAll(fullpath, FolderMode)
}
_, err := os.Stat(fullpath)
if os.IsNotExist(err) {
err = embedCopyToFile(fsys, embedPath, fullpath)
}
return err
})
}
// Create attempts to recreate filesystem. It first checks that
// there be no matching files present and returns an error
// if there is an existing file conflict in outputPath.
//
// Folders are not considered to conflict.
func Create(fsys embed.FS, outputPath string) error {
err := Walk(fsys, ".", func(dirpath string, de fs.DirEntry) error {
embedPath := filepath.Join(dirpath, de.Name())
fullpath := filepath.Join(outputPath, embedPath)
if de.IsDir() {
return nil
}
_, err := os.Stat(fullpath)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return err
}
return ErrExist
})
if err != nil {
return err
}
return Patch(fsys, outputPath)
}
// Walk expects a relative path within fsys.
// f called on every file/directory found recursively.
//
// f's first argument is the relative/absolute path to directory being scanned.
// "." as startPath will scan all files and folders.
//
// Any error returned by f will cause Walk to return said error immediately.
func Walk(fsys embed.FS, startPath string, f func(path string, de fs.DirEntry) error) error {
folders := make([]string, 0) // buffer of folders to process
err := WalkDir(fsys, startPath, func(dirpath string, de fs.DirEntry) error {
if de.IsDir() {
folders = append(folders, filepath.Join(dirpath, de.Name()))
}
return f(dirpath, de)
})
if err != nil {
if len(folders) == 0 {
return fmt.Errorf("no folder found: %v", err)
}
return err
}
n := len(folders)
for n != 0 {
for i := 0; i < n; i++ {
err = WalkDir(fsys, folders[i], func(dirpath string, de fs.DirEntry) error {
if de.IsDir() {
folders = append(folders, filepath.Join(dirpath, de.Name()))
}
return f(dirpath, de)
})
if err != nil {
return err
}
}
// we process n folders at a time, add new folders while
//processing n folders, then discard those n folders once finished
// and resume with a new n list of folders
var newFolders int = len(folders) - n
folders = folders[n : n+newFolders] // if found 0 new folders, end
n = len(folders)
}
return nil
}
// WalkDir applies f to every file/folder in embedded directory fsys.
//
// f's first argument is the relative/absolute path to directory being scanned.
func WalkDir(fsys embed.FS, startPath string, f func(path string, de fs.DirEntry) error) error {
startPath = sanitize(startPath)
items, err := fsys.ReadDir(startPath)
if err != nil {
return err
}
for _, item := range items {
if err := f(startPath, item); err != nil {
return err
}
}
return nil
}
// embedCopyToFile copies an embedded file's contents
// to a file on the host machine.
func embedCopyToFile(fsys embed.FS, embedPath, path string) error {
embedPath = sanitize(embedPath)
fi, err := fsys.Open(embedPath)
if err != nil {
return fmt.Errorf("opening embedded file %v: %v", embedPath, err)
}
fo, err := os.Create(path)
if err != nil {
return err
}
// Thank you chengziqing for spotting this
defer fo.Close()
_, err = io.Copy(fo, fi)
return err
}
// sanitize converts windows representation of path to embed.FS representation
func sanitize(embedPath string) string {
return strings.ReplaceAll(embedPath, "\\", "/")
}