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>
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
28 "golang.org/x/text/transform"
29 "golang.org/x/text/unicode/norm"
32 // Filepath separator defined by os.Separator.
33 const FilePathSeparator = string(filepath.Separator)
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)
40 func WriteReader(fs Fs, path string, r io.Reader) (err error) {
41 dir, _ := filepath.Split(path)
42 ospath := filepath.FromSlash(dir)
45 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
47 if err != os.ErrExist {
53 file, err := fs.Create(path)
59 _, err = io.Copy(file, r)
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)
68 func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) {
69 dir, _ := filepath.Split(path)
70 ospath := filepath.FromSlash(dir)
73 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
79 exists, err := Exists(fs, path)
84 return fmt.Errorf("%v already exists", path)
87 file, err := fs.Create(path)
93 _, err = io.Copy(file, r)
97 func (a Afero) GetTempDir(subPath string) string {
98 return GetTempDir(a.Fs, subPath)
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
110 dir := addSlash(os.TempDir())
113 // preserve windows backslash :-(
114 if FilePathSeparator == "\\" {
115 subPath = strings.Replace(subPath, "\\", "____", -1)
117 dir = dir + UnicodeSanitize((subPath))
118 if FilePathSeparator == "\\" {
119 dir = strings.Replace(dir, "____", "\\", -1)
122 if exists, _ := Exists(fs, dir); exists {
126 err := fs.MkdirAll(dir, 0777)
135 // Rewrite string to remove non-standard path characters
136 func UnicodeSanitize(s string) string {
138 target := make([]rune, 0, len(source))
140 for _, r := range source {
141 if unicode.IsLetter(r) ||
142 unicode.IsDigit(r) ||
152 target = append(target, r)
156 return string(target)
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))
167 func isMn(r rune) bool {
168 return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
171 func (a Afero) FileContainsBytes(filename string, subslice []byte) (bool, error) {
172 return FileContainsBytes(a.Fs, filename, subslice)
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)
183 return readerContainsAny(f, subslice), nil
186 func (a Afero) FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) {
187 return FileContainsAnyBytes(a.Fs, filename, subslices)
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)
198 return readerContainsAny(f, subslices...), nil
201 // readerContains reports whether any of the subslices is within r.
202 func readerContainsAny(r io.Reader, subslices ...[]byte) bool {
204 if r == nil || len(subslices) == 0 {
210 for _, sl := range subslices {
211 if len(sl) > largestSlice {
212 largestSlice = len(sl)
216 if largestSlice == 0 {
220 bufflen := largestSlice * 4
221 halflen := bufflen / 2
222 buff := make([]byte, bufflen)
229 n, err = io.ReadAtLeast(r, buff[:halflen], halflen)
232 // shift left to catch overlapping matches
233 copy(buff[:], buff[halflen:])
235 n, err = io.ReadAtLeast(r, buff[halflen:], halflen)
239 for _, sl := range subslices {
240 if bytes.Contains(buff, sl) {
253 func (a Afero) DirExists(path string) (bool, error) {
254 return DirExists(a.Fs, path)
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() {
263 if os.IsNotExist(err) {
269 func (a Afero) IsDir(path string) (bool, error) {
270 return IsDir(a.Fs, path)
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)
279 return fi.IsDir(), nil
282 func (a Afero) IsEmpty(path string) (bool, error) {
283 return IsEmpty(a.Fs, path)
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)
291 fi, err := fs.Stat(path)
296 f, err := fs.Open(path)
301 list, err := f.Readdir(-1)
302 return len(list) == 0, nil
304 return fi.Size() == 0, nil
307 func (a Afero) Exists(path string) (bool, error) {
308 return Exists(a.Fs, path)
311 // Check if a file or directory exists.
312 func Exists(fs Fs, path string) (bool, error) {
313 _, err := fs.Stat(path)
317 if os.IsNotExist(err) {
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)