github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/util.go (about)

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