util.go raw

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