|
1 // Copyright ©2015 Steve Francia <spf@spf13.com> |
|
2 // Portions Copyright ©2015 The Hugo Authors |
|
3 // Portions Copyright 2016-present Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> |
|
4 // |
|
5 // Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 // you may not use this file except in compliance with the License. |
|
7 // You may obtain a copy of the License at |
|
8 // |
|
9 // http://www.apache.org/licenses/LICENSE-2.0 |
|
10 // |
|
11 // Unless required by applicable law or agreed to in writing, software |
|
12 // distributed under the License is distributed on an "AS IS" BASIS, |
|
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 // See the License for the specific language governing permissions and |
|
15 // limitations under the License. |
|
16 |
|
17 package afero |
|
18 |
|
19 import ( |
|
20 "bytes" |
|
21 "fmt" |
|
22 "io" |
|
23 "os" |
|
24 "path/filepath" |
|
25 "strings" |
|
26 "unicode" |
|
27 |
|
28 "golang.org/x/text/transform" |
|
29 "golang.org/x/text/unicode/norm" |
|
30 ) |
|
31 |
|
32 // Filepath separator defined by os.Separator. |
|
33 const FilePathSeparator = string(filepath.Separator) |
|
34 |
|
35 // Takes a reader and a path and writes the content |
|
36 func (a Afero) WriteReader(path string, r io.Reader) (err error) { |
|
37 return WriteReader(a.Fs, path, r) |
|
38 } |
|
39 |
|
40 func WriteReader(fs Fs, path string, r io.Reader) (err error) { |
|
41 dir, _ := filepath.Split(path) |
|
42 ospath := filepath.FromSlash(dir) |
|
43 |
|
44 if ospath != "" { |
|
45 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r |
|
46 if err != nil { |
|
47 if err != os.ErrExist { |
|
48 return err |
|
49 } |
|
50 } |
|
51 } |
|
52 |
|
53 file, err := fs.Create(path) |
|
54 if err != nil { |
|
55 return |
|
56 } |
|
57 defer file.Close() |
|
58 |
|
59 _, err = io.Copy(file, r) |
|
60 return |
|
61 } |
|
62 |
|
63 // Same as WriteReader but checks to see if file/directory already exists. |
|
64 func (a Afero) SafeWriteReader(path string, r io.Reader) (err error) { |
|
65 return SafeWriteReader(a.Fs, path, r) |
|
66 } |
|
67 |
|
68 func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) { |
|
69 dir, _ := filepath.Split(path) |
|
70 ospath := filepath.FromSlash(dir) |
|
71 |
|
72 if ospath != "" { |
|
73 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r |
|
74 if err != nil { |
|
75 return |
|
76 } |
|
77 } |
|
78 |
|
79 exists, err := Exists(fs, path) |
|
80 if err != nil { |
|
81 return |
|
82 } |
|
83 if exists { |
|
84 return fmt.Errorf("%v already exists", path) |
|
85 } |
|
86 |
|
87 file, err := fs.Create(path) |
|
88 if err != nil { |
|
89 return |
|
90 } |
|
91 defer file.Close() |
|
92 |
|
93 _, err = io.Copy(file, r) |
|
94 return |
|
95 } |
|
96 |
|
97 func (a Afero) GetTempDir(subPath string) string { |
|
98 return GetTempDir(a.Fs, subPath) |
|
99 } |
|
100 |
|
101 // GetTempDir returns the default temp directory with trailing slash |
|
102 // if subPath is not empty then it will be created recursively with mode 777 rwx rwx rwx |
|
103 func GetTempDir(fs Fs, subPath string) string { |
|
104 addSlash := func(p string) string { |
|
105 if FilePathSeparator != p[len(p)-1:] { |
|
106 p = p + FilePathSeparator |
|
107 } |
|
108 return p |
|
109 } |
|
110 dir := addSlash(os.TempDir()) |
|
111 |
|
112 if subPath != "" { |
|
113 // preserve windows backslash :-( |
|
114 if FilePathSeparator == "\\" { |
|
115 subPath = strings.Replace(subPath, "\\", "____", -1) |
|
116 } |
|
117 dir = dir + UnicodeSanitize((subPath)) |
|
118 if FilePathSeparator == "\\" { |
|
119 dir = strings.Replace(dir, "____", "\\", -1) |
|
120 } |
|
121 |
|
122 if exists, _ := Exists(fs, dir); exists { |
|
123 return addSlash(dir) |
|
124 } |
|
125 |
|
126 err := fs.MkdirAll(dir, 0777) |
|
127 if err != nil { |
|
128 panic(err) |
|
129 } |
|
130 dir = addSlash(dir) |
|
131 } |
|
132 return dir |
|
133 } |
|
134 |
|
135 // Rewrite string to remove non-standard path characters |
|
136 func UnicodeSanitize(s string) string { |
|
137 source := []rune(s) |
|
138 target := make([]rune, 0, len(source)) |
|
139 |
|
140 for _, r := range source { |
|
141 if unicode.IsLetter(r) || |
|
142 unicode.IsDigit(r) || |
|
143 unicode.IsMark(r) || |
|
144 r == '.' || |
|
145 r == '/' || |
|
146 r == '\\' || |
|
147 r == '_' || |
|
148 r == '-' || |
|
149 r == '%' || |
|
150 r == ' ' || |
|
151 r == '#' { |
|
152 target = append(target, r) |
|
153 } |
|
154 } |
|
155 |
|
156 return string(target) |
|
157 } |
|
158 |
|
159 // Transform characters with accents into plain forms. |
|
160 func NeuterAccents(s string) string { |
|
161 t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) |
|
162 result, _, _ := transform.String(t, string(s)) |
|
163 |
|
164 return result |
|
165 } |
|
166 |
|
167 func isMn(r rune) bool { |
|
168 return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks |
|
169 } |
|
170 |
|
171 func (a Afero) FileContainsBytes(filename string, subslice []byte) (bool, error) { |
|
172 return FileContainsBytes(a.Fs, filename, subslice) |
|
173 } |
|
174 |
|
175 // Check if a file contains a specified byte slice. |
|
176 func FileContainsBytes(fs Fs, filename string, subslice []byte) (bool, error) { |
|
177 f, err := fs.Open(filename) |
|
178 if err != nil { |
|
179 return false, err |
|
180 } |
|
181 defer f.Close() |
|
182 |
|
183 return readerContainsAny(f, subslice), nil |
|
184 } |
|
185 |
|
186 func (a Afero) FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) { |
|
187 return FileContainsAnyBytes(a.Fs, filename, subslices) |
|
188 } |
|
189 |
|
190 // Check if a file contains any of the specified byte slices. |
|
191 func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, error) { |
|
192 f, err := fs.Open(filename) |
|
193 if err != nil { |
|
194 return false, err |
|
195 } |
|
196 defer f.Close() |
|
197 |
|
198 return readerContainsAny(f, subslices...), nil |
|
199 } |
|
200 |
|
201 // readerContains reports whether any of the subslices is within r. |
|
202 func readerContainsAny(r io.Reader, subslices ...[]byte) bool { |
|
203 |
|
204 if r == nil || len(subslices) == 0 { |
|
205 return false |
|
206 } |
|
207 |
|
208 largestSlice := 0 |
|
209 |
|
210 for _, sl := range subslices { |
|
211 if len(sl) > largestSlice { |
|
212 largestSlice = len(sl) |
|
213 } |
|
214 } |
|
215 |
|
216 if largestSlice == 0 { |
|
217 return false |
|
218 } |
|
219 |
|
220 bufflen := largestSlice * 4 |
|
221 halflen := bufflen / 2 |
|
222 buff := make([]byte, bufflen) |
|
223 var err error |
|
224 var n, i int |
|
225 |
|
226 for { |
|
227 i++ |
|
228 if i == 1 { |
|
229 n, err = io.ReadAtLeast(r, buff[:halflen], halflen) |
|
230 } else { |
|
231 if i != 2 { |
|
232 // shift left to catch overlapping matches |
|
233 copy(buff[:], buff[halflen:]) |
|
234 } |
|
235 n, err = io.ReadAtLeast(r, buff[halflen:], halflen) |
|
236 } |
|
237 |
|
238 if n > 0 { |
|
239 for _, sl := range subslices { |
|
240 if bytes.Contains(buff, sl) { |
|
241 return true |
|
242 } |
|
243 } |
|
244 } |
|
245 |
|
246 if err != nil { |
|
247 break |
|
248 } |
|
249 } |
|
250 return false |
|
251 } |
|
252 |
|
253 func (a Afero) DirExists(path string) (bool, error) { |
|
254 return DirExists(a.Fs, path) |
|
255 } |
|
256 |
|
257 // DirExists checks if a path exists and is a directory. |
|
258 func DirExists(fs Fs, path string) (bool, error) { |
|
259 fi, err := fs.Stat(path) |
|
260 if err == nil && fi.IsDir() { |
|
261 return true, nil |
|
262 } |
|
263 if os.IsNotExist(err) { |
|
264 return false, nil |
|
265 } |
|
266 return false, err |
|
267 } |
|
268 |
|
269 func (a Afero) IsDir(path string) (bool, error) { |
|
270 return IsDir(a.Fs, path) |
|
271 } |
|
272 |
|
273 // IsDir checks if a given path is a directory. |
|
274 func IsDir(fs Fs, path string) (bool, error) { |
|
275 fi, err := fs.Stat(path) |
|
276 if err != nil { |
|
277 return false, err |
|
278 } |
|
279 return fi.IsDir(), nil |
|
280 } |
|
281 |
|
282 func (a Afero) IsEmpty(path string) (bool, error) { |
|
283 return IsEmpty(a.Fs, path) |
|
284 } |
|
285 |
|
286 // IsEmpty checks if a given file or directory is empty. |
|
287 func IsEmpty(fs Fs, path string) (bool, error) { |
|
288 if b, _ := Exists(fs, path); !b { |
|
289 return false, fmt.Errorf("%q path does not exist", path) |
|
290 } |
|
291 fi, err := fs.Stat(path) |
|
292 if err != nil { |
|
293 return false, err |
|
294 } |
|
295 if fi.IsDir() { |
|
296 f, err := fs.Open(path) |
|
297 if err != nil { |
|
298 return false, err |
|
299 } |
|
300 defer f.Close() |
|
301 list, err := f.Readdir(-1) |
|
302 return len(list) == 0, nil |
|
303 } |
|
304 return fi.Size() == 0, nil |
|
305 } |
|
306 |
|
307 func (a Afero) Exists(path string) (bool, error) { |
|
308 return Exists(a.Fs, path) |
|
309 } |
|
310 |
|
311 // Check if a file or directory exists. |
|
312 func Exists(fs Fs, path string) (bool, error) { |
|
313 _, err := fs.Stat(path) |
|
314 if err == nil { |
|
315 return true, nil |
|
316 } |
|
317 if os.IsNotExist(err) { |
|
318 return false, nil |
|
319 } |
|
320 return false, err |
|
321 } |
|
322 |
|
323 func FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string { |
|
324 combinedPath := filepath.Join(basePathFs.path, relativePath) |
|
325 if parent, ok := basePathFs.source.(*BasePathFs); ok { |
|
326 return FullBaseFsPath(parent, combinedPath) |
|
327 } |
|
328 |
|
329 return combinedPath |
|
330 } |