github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/file.go (about)

     1  package f
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"os/exec"
    12  	"os/user"
    13  	"path"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  )
    18  
    19  // CurrentUserName user.Current Username.
    20  func CurrentUserName() string {
    21  	currentUser, err := user.Current()
    22  	if err != nil {
    23  		return ""
    24  	}
    25  	return currentUser.Username
    26  }
    27  
    28  // CurrentUserHomeDir user.Current HomeDir or $HOME.
    29  func CurrentUserHomeDir() string {
    30  	currentUser, err := user.Current()
    31  	if err != nil {
    32  		return os.Getenv("HOME")
    33  	}
    34  	return currentUser.HomeDir
    35  }
    36  
    37  // CurrentFile gets compiled executable file name.
    38  func CurrentFile() string {
    39  	p, _ := filepath.Abs(os.Args[0])
    40  	_, p = path.Split(filepath.ToSlash(p))
    41  	return p
    42  }
    43  
    44  // CurrentPath gets compiled executable file absolute path.
    45  func CurrentPath() string {
    46  	p, _ := filepath.Abs(os.Args[0])
    47  	return p
    48  }
    49  
    50  // CurrentDir gets compiled executable file directory.
    51  func CurrentDir() string {
    52  	p, _ := filepath.Abs(os.Args[0])
    53  	return filepath.Dir(p)
    54  }
    55  
    56  // RelativePath gets relative path.
    57  func RelativePath(targetPath string) string {
    58  	basePath, _ := filepath.Abs("./")
    59  	rel, _ := filepath.Rel(basePath, targetPath)
    60  	return strings.Replace(rel, `\`, `/`, -1)
    61  }
    62  
    63  // IsAbsPath is abs path.
    64  func IsAbsPath(filepath string) bool {
    65  	return path.IsAbs(filepath)
    66  }
    67  
    68  // IsSanePath it's sane path.
    69  func IsSanePath(path string) bool {
    70  	if path == ".." || strings.HasPrefix(path, "../") {
    71  		return false
    72  	}
    73  	return true
    74  }
    75  
    76  // IsDir reports whether the named directory exists.
    77  func IsDir(path string) bool {
    78  	if path == "" {
    79  		return false
    80  	}
    81  
    82  	if fi, err := os.Stat(path); err == nil {
    83  		return fi.IsDir()
    84  	}
    85  	return false
    86  }
    87  
    88  // IsFile reports whether the named file or directory exists.
    89  func IsFile(path string) bool {
    90  	if path == "" {
    91  		return false
    92  	}
    93  
    94  	if fi, err := os.Stat(path); err == nil {
    95  		return !fi.IsDir()
    96  	}
    97  	return false
    98  }
    99  
   100  // IsExeFile returns true if searches for an executable named file in the
   101  // directories named by the PATH environment variable.
   102  func IsExeFile(path string) bool {
   103  	if path == "" {
   104  		return false
   105  	}
   106  
   107  	if lp, err := exec.LookPath(path); err == nil {
   108  		if fi, err := os.Stat(lp); err == nil {
   109  			return !fi.IsDir()
   110  		}
   111  	}
   112  	return false
   113  }
   114  
   115  // FileIsSymlink file is symlink.
   116  func FileIsSymlink(fi os.FileInfo) bool {
   117  	return fi.Mode()&os.ModeSymlink != 0
   118  }
   119  
   120  // FileExists reports whether the named file or directory exists.
   121  func FileExists(name string) (existed bool) {
   122  	existed, _ = FileExist(name)
   123  	return
   124  }
   125  
   126  // FileExist reports whether the named file or directory exists.
   127  func FileExist(name string) (existed bool, isDir bool) {
   128  	info, err := os.Stat(name)
   129  	if err != nil {
   130  		return !os.IsNotExist(err), false
   131  	}
   132  	return true, info.IsDir()
   133  }
   134  
   135  // FileExistMultipleTopLevels returns true if the paths do not
   136  // share a common top-level folder.
   137  func FileExistMultipleTopLevels(paths []string) bool {
   138  	if len(paths) < 2 {
   139  		return false
   140  	}
   141  	var lastTop string
   142  	for _, p := range paths {
   143  		p = strings.TrimPrefix(strings.Replace(p, `\`, "/", -1), "/")
   144  		for {
   145  			next := path.Dir(p)
   146  			if next == "." {
   147  				break
   148  			}
   149  			p = next
   150  		}
   151  		if lastTop == "" {
   152  			lastTop = p
   153  		}
   154  		if p != lastTop {
   155  			return true
   156  		}
   157  	}
   158  	return false
   159  }
   160  
   161  // FileWithin returns true if sub is within or equal to parent.
   162  func FileWithin(parent, sub string) bool {
   163  	rel, err := filepath.Rel(parent, sub)
   164  	if err != nil {
   165  		return false
   166  	}
   167  	return !strings.Contains(rel, "..")
   168  }
   169  
   170  // FolderNameFromFileName returns a name for a folder
   171  // that is suitable based on the filename, which will
   172  // be stripped of its extensions.
   173  func FolderNameFromFileName(filename string) string {
   174  	base := filepath.Base(filename)
   175  	firstDot := strings.Index(base, ".")
   176  	if firstDot > -1 {
   177  		return base[:firstDot]
   178  	}
   179  	return base
   180  }
   181  
   182  // MakeNameInArchive returns the filename for the file given by fpath to be used within
   183  // the archive. sourceInfo is the zipFileCustomInfo obtained by calling os.Stat on source, and baseDir
   184  // is an optional base directory that becomes the root of the archive. fpath should be the
   185  // unaltered file path of the file given to a filepath.WalkFunc.
   186  func MakeNameInArchive(sourceInfo os.FileInfo, source, baseDir, fpath string) (string, error) {
   187  	name := filepath.Base(fpath) // start with the file or dir name
   188  	if sourceInfo.IsDir() {
   189  		// preserve internal directory structure; that's the path components
   190  		// between the source directory's leaf and this file's leaf
   191  		dir, err := filepath.Rel(filepath.Dir(source), filepath.Dir(fpath))
   192  		if err != nil {
   193  			return "", err
   194  		}
   195  		// prepend the internal directory structure to the leaf name,
   196  		// and convert path separators to forward slashes as per spec
   197  		name = path.Join(filepath.ToSlash(dir), name)
   198  	}
   199  	return path.Join(baseDir, name), nil // prepend the base directory
   200  }
   201  
   202  // SearchFile Search a file in paths.
   203  // this is often used in search config file in /etc ~/
   204  func SearchFile(filename string, paths ...string) (fullPath string) {
   205  	for _, name := range paths {
   206  		fullPath = filepath.Join(name, filename)
   207  		existed, _ := FileExist(fullPath)
   208  		if existed {
   209  			return
   210  		}
   211  	}
   212  	return
   213  }
   214  
   215  // MatchFile like command grep -E
   216  // for example: MatchFile(`^hello`, "hello.txt")
   217  // \n is striped while read
   218  func MatchFile(patten string, filename string) (lines []string, err error) {
   219  	var re *regexp.Regexp
   220  	re, err = regexp.Compile(patten)
   221  	if err != nil {
   222  		return
   223  	}
   224  
   225  	var fd *os.File
   226  	fd, err = os.Open(filename)
   227  	if err != nil {
   228  		return
   229  	}
   230  
   231  	lines = make([]string, 0)
   232  	reader := bufio.NewReader(fd)
   233  	prefix := ""
   234  	isLongLine := false
   235  	for {
   236  		byteLine, isPrefix, er := reader.ReadLine()
   237  		if er != nil && er != io.EOF {
   238  			return nil, er
   239  		}
   240  		if er == io.EOF {
   241  			break
   242  		}
   243  		line := string(byteLine)
   244  		if isPrefix {
   245  			prefix += line
   246  			continue
   247  		} else {
   248  			isLongLine = true
   249  		}
   250  
   251  		line = prefix + line
   252  		if isLongLine {
   253  			prefix = ""
   254  		}
   255  		if re.MatchString(line) {
   256  			lines = append(lines, line)
   257  		}
   258  	}
   259  	return lines, nil
   260  }
   261  
   262  // WalkDirs traverses the directory, return to the relative path.
   263  // You can specify the suffix.
   264  func WalkDirs(targetPath string, suffixes ...string) (dirList []string) {
   265  	if !filepath.IsAbs(targetPath) {
   266  		targetPath, _ = filepath.Abs(targetPath)
   267  	}
   268  	_ = filepath.Walk(targetPath, func(retPath string, f os.FileInfo, err error) error {
   269  		if err != nil {
   270  			return err
   271  		}
   272  		if !f.IsDir() {
   273  			return nil
   274  		}
   275  		if len(suffixes) == 0 {
   276  			dirList = append(dirList, RelativePath(retPath))
   277  			return nil
   278  		}
   279  		_path := RelativePath(retPath)
   280  		for _, suffix := range suffixes {
   281  			if strings.HasSuffix(_path, suffix) {
   282  				dirList = append(dirList, _path)
   283  			}
   284  		}
   285  		return nil
   286  	})
   287  	return
   288  }
   289  
   290  // FilepathSplitExt splits the filename into a pair (root, ext) such that root + ext == filename,
   291  // and ext is empty or begins with a period and contains at most one period.
   292  // Leading periods on the basename are ignored; splitext('.cshrc') returns ('', '.cshrc').
   293  func FilepathSplitExt(filename string, slashInsensitive ...bool) (root, ext string) {
   294  	insensitive := false
   295  	if len(slashInsensitive) > 0 {
   296  		insensitive = slashInsensitive[0]
   297  	}
   298  	if insensitive {
   299  		filename = FilepathSlashInsensitive(filename)
   300  	}
   301  	for i := len(filename) - 1; i >= 0 && !os.IsPathSeparator(filename[i]); i-- {
   302  		if filename[i] == '.' {
   303  			return filename[:i], filename[i:]
   304  		}
   305  	}
   306  	return filename, ""
   307  }
   308  
   309  // FilepathStem returns the stem of filename.
   310  // Example:
   311  //  FilepathStem("/root/dir/sub/file.ext") // output "file"
   312  // NOTE:
   313  //  If slashInsensitive is empty, default is false.
   314  func FilepathStem(filename string, slashInsensitive ...bool) string {
   315  	insensitive := false
   316  	if len(slashInsensitive) > 0 {
   317  		insensitive = slashInsensitive[0]
   318  	}
   319  	if insensitive {
   320  		filename = FilepathSlashInsensitive(filename)
   321  	}
   322  	base := filepath.Base(filename)
   323  	for i := len(base) - 1; i >= 0; i-- {
   324  		if base[i] == '.' {
   325  			return base[:i]
   326  		}
   327  	}
   328  	return base
   329  }
   330  
   331  // FilepathSlashInsensitive ignore the difference between the slash and the backslash,
   332  // and convert to the same as the current system.
   333  func FilepathSlashInsensitive(path string) string {
   334  	if filepath.Separator == '/' {
   335  		return strings.Replace(path, "\\", "/", -1)
   336  	}
   337  	return strings.Replace(path, "/", "\\", -1)
   338  }
   339  
   340  // FilepathContains checks if the basePath path contains the subPaths.
   341  func FilepathContains(basePath string, subPaths []string) error {
   342  	name, err := filepath.Abs(basePath)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	for _, p := range subPaths {
   347  		p, err = filepath.Abs(p)
   348  		if err != nil {
   349  			return err
   350  		}
   351  		rel, err := filepath.Rel(name, p)
   352  		if err != nil {
   353  			return err
   354  		}
   355  		if strings.HasPrefix(rel, "..") {
   356  			return fmt.Errorf("%s is not include %s", name, p)
   357  		}
   358  	}
   359  	return nil
   360  }
   361  
   362  // FilepathAbsolute returns the absolute paths.
   363  func FilepathAbsolute(paths []string) ([]string, error) {
   364  	return StringsConvert(paths, func(p string) (string, error) {
   365  		return filepath.Abs(p)
   366  	})
   367  }
   368  
   369  // FilepathAbsoluteMap returns the absolute paths map.
   370  func FilepathAbsoluteMap(paths []string) (map[string]string, error) {
   371  	return StringsConvertMap(paths, func(p string) (string, error) {
   372  		return filepath.Abs(p)
   373  	})
   374  }
   375  
   376  // FilepathRelative returns the relative paths.
   377  func FilepathRelative(basePath string, targetPaths []string) ([]string, error) {
   378  	name, err := filepath.Abs(basePath)
   379  	if err != nil {
   380  		return nil, err
   381  	}
   382  	return StringsConvert(targetPaths, func(p string) (string, error) {
   383  		return filepathRelative(name, p)
   384  	})
   385  }
   386  
   387  // FilepathRelativeMap returns the relative paths map.
   388  func FilepathRelativeMap(basePath string, targetPaths []string) (map[string]string, error) {
   389  	name, err := filepath.Abs(basePath)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	return StringsConvertMap(targetPaths, func(p string) (string, error) {
   394  		return filepathRelative(name, p)
   395  	})
   396  }
   397  
   398  func filepathRelative(basePath, targetPath string) (string, error) {
   399  	abs, err1 := filepath.Abs(targetPath)
   400  	if err1 != nil {
   401  		return "", err1
   402  	}
   403  	rel, err2 := filepath.Rel(basePath, abs)
   404  	if err2 != nil {
   405  		return "", err2
   406  	}
   407  	if strings.HasPrefix(rel, "..") {
   408  		return "", fmt.Errorf("%s is not include %s", basePath, abs)
   409  	}
   410  	return rel, nil
   411  }
   412  
   413  // FilepathDistinct removes the same path and return in the original order.
   414  // If toAbs is true, return the result to absolute paths.
   415  func FilepathDistinct(paths []string, toAbs bool) ([]string, error) {
   416  	m := make(map[string]bool, len(paths))
   417  	ret := make([]string, 0, len(paths))
   418  	for _, p := range paths {
   419  		abs, err := filepath.Abs(p)
   420  		if err != nil {
   421  			return nil, err
   422  		}
   423  		if m[abs] {
   424  			continue
   425  		}
   426  		m[abs] = true
   427  		if toAbs {
   428  			ret = append(ret, abs)
   429  		} else {
   430  			ret = append(ret, p)
   431  		}
   432  	}
   433  	return ret, nil
   434  }
   435  
   436  // FilepathToSlash returns the result of replacing each separator character
   437  // in path with a slash ('/') character. Multiple separators are
   438  // replaced by multiple slashes.
   439  func FilepathToSlash(paths []string) []string {
   440  	ret, _ := StringsConvert(paths, func(p string) (string, error) {
   441  		return filepath.ToSlash(p), nil
   442  	})
   443  	return ret
   444  }
   445  
   446  // FilepathFromSlash returns the result of replacing each slash ('/') character
   447  // in path with a separator character. Multiple slashes are replaced
   448  // by multiple separators.
   449  func FilepathFromSlash(paths []string) []string {
   450  	ret, _ := StringsConvert(paths, func(p string) (string, error) {
   451  		return filepath.FromSlash(p), nil
   452  	})
   453  	return ret
   454  }
   455  
   456  // FilepathSame checks if the two paths are the same.
   457  func FilepathSame(path1, path2 string) (bool, error) {
   458  	if path1 == path2 {
   459  		return true, nil
   460  	}
   461  	p1, err1 := filepath.Abs(path1)
   462  	if err1 != nil {
   463  		return false, err1
   464  	}
   465  	p2, err2 := filepath.Abs(path2)
   466  	if err2 != nil {
   467  		return false, err2
   468  	}
   469  	return p1 == p2, nil
   470  }
   471  
   472  // Mkdir creates a new directory if does not exist.
   473  // with the specified name and permission bits (before umask).
   474  // If there is an error, it will be of type *PathError.
   475  func Mkdir(path string, perm ...os.FileMode) error {
   476  	var exists, isDir bool
   477  	if info, err := os.Stat(path); err != nil {
   478  		exists = !os.IsNotExist(err)
   479  	} else {
   480  		exists, isDir = true, info.IsDir()
   481  	}
   482  
   483  	if exists {
   484  		if isDir {
   485  			return nil
   486  		}
   487  
   488  		if err := os.Remove(path); err != nil {
   489  			return err
   490  		}
   491  	}
   492  
   493  	var fm os.FileMode = 0755
   494  	if len(perm) > 0 {
   495  		fm = perm[0]
   496  	}
   497  
   498  	return os.Mkdir(path, fm)
   499  }
   500  
   501  // MkdirCurrent creates a new directory under the current directory
   502  // with the specified name and permission
   503  // bits (before umask).
   504  // If there is an error, it will be of type *PathError.
   505  func MkdirCurrent(name string, perm ...os.FileMode) error {
   506  	return Mkdir(filepath.Join(CurrentDir(), name), perm...)
   507  }
   508  
   509  // MkdirAll creates a directory named path,
   510  // along with any necessary parents, and returns nil,
   511  // or else returns an error.
   512  // The permission bits perm (before umask) are used for all
   513  // directories that MkdirAll creates.
   514  // If path is already a directory, MkdirAll does nothing
   515  // and returns nil.
   516  // If perm is empty, default use 0755.
   517  func MkdirAll(path string, perm ...os.FileMode) error {
   518  	var fm os.FileMode = 0755
   519  	if len(perm) > 0 {
   520  		fm = perm[0]
   521  	}
   522  	return os.MkdirAll(path, fm)
   523  }
   524  
   525  // WriteFile writes file, and automatically creates the directory if necessary.
   526  // NOTE:
   527  //  If perm is empty, automatically determine the file permissions based on extension.
   528  func WriteFile(filename string, data []byte, perm ...os.FileMode) error {
   529  	filename = filepath.FromSlash(filename)
   530  	err := MkdirAll(filepath.Dir(filename))
   531  	if err != nil {
   532  		return err
   533  	}
   534  	if len(perm) > 0 {
   535  		return ioutil.WriteFile(filename, data, perm[0])
   536  	}
   537  	var ext string
   538  	if idx := strings.LastIndex(filename, "."); idx != -1 {
   539  		ext = filename[idx:]
   540  	}
   541  	switch ext {
   542  	case ".sh", ".py", ".rb", ".bat", ".com", ".vbs", ".htm", ".run", ".App", ".exe", ".reg":
   543  		return ioutil.WriteFile(filename, data, 0755)
   544  	default:
   545  		return ioutil.WriteFile(filename, data, 0644)
   546  	}
   547  }
   548  
   549  // RewriteFile rewrites the file.
   550  func RewriteFile(filename string, fn func(content []byte) (newContent []byte, err error)) error {
   551  	f, err := os.OpenFile(filename, os.O_RDWR, 0777)
   552  	if err != nil {
   553  		return err
   554  	}
   555  	defer func() { _ = f.Close() }()
   556  	content, err1 := ioutil.ReadAll(f)
   557  	if err1 != nil {
   558  		return err1
   559  	}
   560  	newContent, err2 := fn(content)
   561  	if err2 != nil {
   562  		return err2
   563  	}
   564  	if bytes.Equal(content, newContent) {
   565  		return nil
   566  	}
   567  	_, _ = f.Seek(0, 0)
   568  	_ = f.Truncate(0)
   569  	_, err = f.Write(newContent)
   570  	return err
   571  }
   572  
   573  // RewriteToFile rewrites the file to newFilename.
   574  // If newFilename already exists and is not a directory, replaces it.
   575  func RewriteToFile(filename, newFilename string, fn func(content []byte) (newContent []byte, err error)) error {
   576  	f, err := os.Open(filename)
   577  	if err != nil {
   578  		return err
   579  	}
   580  	defer func() { _ = f.Close() }()
   581  	info, err1 := f.Stat()
   582  	if err1 != nil {
   583  		return err1
   584  	}
   585  	cnt, err2 := ioutil.ReadAll(f)
   586  	if err2 != nil {
   587  		return err2
   588  	}
   589  	newContent, err3 := fn(cnt)
   590  	if err3 != nil {
   591  		return err3
   592  	}
   593  	return WriteFile(newFilename, newContent, info.Mode())
   594  }
   595  
   596  // ReplaceFile replaces the bytes selected by [start, end] with the new content.
   597  func ReplaceFile(filename string, start, end int, newContent string) error {
   598  	if start < 0 || (end >= 0 && start > end) {
   599  		return nil
   600  	}
   601  	return RewriteFile(filename, func(content []byte) ([]byte, error) {
   602  		if end < 0 || end > len(content) {
   603  			end = len(content)
   604  		}
   605  		if start > end {
   606  			start = end
   607  		}
   608  		return bytes.Replace(content, content[start:end], ToBytes(newContent), 1), nil
   609  	})
   610  }
   611  
   612  // MimeType get File Mime Type name. eg "image/png"
   613  func MimeType(path string) (mime string) {
   614  	if path == "" {
   615  		return
   616  	}
   617  
   618  	file, err := os.Open(path)
   619  	if err != nil {
   620  		return
   621  	}
   622  
   623  	return ReaderMimeType(file)
   624  }
   625  
   626  // ReaderMimeType get the io.Reader mimeType
   627  // Usage:
   628  // 	file, err := os.Open(filepath)
   629  // 	if err != nil {
   630  // 		return
   631  // 	}
   632  //	mime := ReaderMimeType(file)
   633  func ReaderMimeType(r io.Reader) (mime string) {
   634  	var buf [MimeSniffLen]byte
   635  	n, _ := io.ReadFull(r, buf[:])
   636  	if n == 0 {
   637  		return ""
   638  	}
   639  
   640  	return http.DetectContentType(buf[:n])
   641  }
   642  
   643  // IsImageFile check file is image file.
   644  func IsImageFile(path string) bool {
   645  	mime := MimeType(path)
   646  	if mime == "" {
   647  		return false
   648  	}
   649  
   650  	for _, imgMime := range ImageMimeTypes {
   651  		if imgMime == mime {
   652  			return true
   653  		}
   654  	}
   655  	return false
   656  }