github.com/wangyougui/gf/v2@v2.6.5/os/gfile/gfile.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  // Package gfile provides easy-to-use operations for file system.
     8  package gfile
     9  
    10  import (
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/wangyougui/gf/v2/container/gtype"
    18  	"github.com/wangyougui/gf/v2/errors/gerror"
    19  	"github.com/wangyougui/gf/v2/text/gstr"
    20  	"github.com/wangyougui/gf/v2/util/gconv"
    21  )
    22  
    23  const (
    24  	// Separator for file system.
    25  	// It here defines the separator as variable
    26  	// to allow it modified by developer if necessary.
    27  	Separator = string(filepath.Separator)
    28  
    29  	// DefaultPermOpen is the default perm for file opening.
    30  	DefaultPermOpen = os.FileMode(0666)
    31  
    32  	// DefaultPermCopy is the default perm for file/folder copy.
    33  	DefaultPermCopy = os.FileMode(0755)
    34  )
    35  
    36  var (
    37  	// The absolute file path for main package.
    38  	// It can be only checked and set once.
    39  	mainPkgPath = gtype.NewString()
    40  
    41  	// selfPath is the current running binary path.
    42  	// As it is most commonly used, it is so defined as an internal package variable.
    43  	selfPath = ""
    44  )
    45  
    46  func init() {
    47  	// Initialize internal package variable: selfPath.
    48  	selfPath, _ = exec.LookPath(os.Args[0])
    49  	if selfPath != "" {
    50  		selfPath, _ = filepath.Abs(selfPath)
    51  	}
    52  	if selfPath == "" {
    53  		selfPath, _ = filepath.Abs(os.Args[0])
    54  	}
    55  }
    56  
    57  // Mkdir creates directories recursively with given `path`.
    58  // The parameter `path` is suggested to be an absolute path instead of relative one.
    59  func Mkdir(path string) (err error) {
    60  	if err = os.MkdirAll(path, os.ModePerm); err != nil {
    61  		err = gerror.Wrapf(err, `os.MkdirAll failed for path "%s" with perm "%d"`, path, os.ModePerm)
    62  		return err
    63  	}
    64  	return nil
    65  }
    66  
    67  // Create creates a file with given `path` recursively.
    68  // The parameter `path` is suggested to be absolute path.
    69  func Create(path string) (*os.File, error) {
    70  	dir := Dir(path)
    71  	if !Exists(dir) {
    72  		if err := Mkdir(dir); err != nil {
    73  			return nil, err
    74  		}
    75  	}
    76  	file, err := os.Create(path)
    77  	if err != nil {
    78  		err = gerror.Wrapf(err, `os.Create failed for name "%s"`, path)
    79  	}
    80  	return file, err
    81  }
    82  
    83  // Open opens file/directory READONLY.
    84  func Open(path string) (*os.File, error) {
    85  	file, err := os.Open(path)
    86  	if err != nil {
    87  		err = gerror.Wrapf(err, `os.Open failed for name "%s"`, path)
    88  	}
    89  	return file, err
    90  }
    91  
    92  // OpenFile opens file/directory with custom `flag` and `perm`.
    93  // The parameter `flag` is like: O_RDONLY, O_RDWR, O_RDWR|O_CREATE|O_TRUNC, etc.
    94  func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
    95  	file, err := os.OpenFile(path, flag, perm)
    96  	if err != nil {
    97  		err = gerror.Wrapf(err, `os.OpenFile failed with name "%s", flag "%d", perm "%d"`, path, flag, perm)
    98  	}
    99  	return file, err
   100  }
   101  
   102  // OpenWithFlag opens file/directory with default perm and custom `flag`.
   103  // The default `perm` is 0666.
   104  // The parameter `flag` is like: O_RDONLY, O_RDWR, O_RDWR|O_CREATE|O_TRUNC, etc.
   105  func OpenWithFlag(path string, flag int) (*os.File, error) {
   106  	file, err := OpenFile(path, flag, DefaultPermOpen)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return file, nil
   111  }
   112  
   113  // OpenWithFlagPerm opens file/directory with custom `flag` and `perm`.
   114  // The parameter `flag` is like: O_RDONLY, O_RDWR, O_RDWR|O_CREATE|O_TRUNC, etc.
   115  // The parameter `perm` is like: 0600, 0666, 0777, etc.
   116  func OpenWithFlagPerm(path string, flag int, perm os.FileMode) (*os.File, error) {
   117  	file, err := OpenFile(path, flag, perm)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return file, nil
   122  }
   123  
   124  // Join joins string array paths with file separator of current system.
   125  func Join(paths ...string) string {
   126  	var s string
   127  	for _, path := range paths {
   128  		if s != "" {
   129  			s += Separator
   130  		}
   131  		s += gstr.TrimRight(path, Separator)
   132  	}
   133  	return s
   134  }
   135  
   136  // Exists checks whether given `path` exist.
   137  func Exists(path string) bool {
   138  	if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) {
   139  		return true
   140  	}
   141  	return false
   142  }
   143  
   144  // IsDir checks whether given `path` a directory.
   145  // Note that it returns false if the `path` does not exist.
   146  func IsDir(path string) bool {
   147  	s, err := os.Stat(path)
   148  	if err != nil {
   149  		return false
   150  	}
   151  	return s.IsDir()
   152  }
   153  
   154  // Pwd returns absolute path of current working directory.
   155  // Note that it returns an empty string if retrieving current
   156  // working directory failed.
   157  func Pwd() string {
   158  	path, err := os.Getwd()
   159  	if err != nil {
   160  		return ""
   161  	}
   162  	return path
   163  }
   164  
   165  // Chdir changes the current working directory to the named directory.
   166  // If there is an error, it will be of type *PathError.
   167  func Chdir(dir string) (err error) {
   168  	err = os.Chdir(dir)
   169  	if err != nil {
   170  		err = gerror.Wrapf(err, `os.Chdir failed with dir "%s"`, dir)
   171  	}
   172  	return
   173  }
   174  
   175  // IsFile checks whether given `path` a file, which means it's not a directory.
   176  // Note that it returns false if the `path` does not exist.
   177  func IsFile(path string) bool {
   178  	s, err := Stat(path)
   179  	if err != nil {
   180  		return false
   181  	}
   182  	return !s.IsDir()
   183  }
   184  
   185  // Stat returns a FileInfo describing the named file.
   186  // If there is an error, it will be of type *PathError.
   187  func Stat(path string) (os.FileInfo, error) {
   188  	info, err := os.Stat(path)
   189  	if err != nil {
   190  		err = gerror.Wrapf(err, `os.Stat failed for file "%s"`, path)
   191  	}
   192  	return info, err
   193  }
   194  
   195  // Move renames (moves) `src` to `dst` path.
   196  // If `dst` already exists and is not a directory, it'll be replaced.
   197  func Move(src string, dst string) (err error) {
   198  	err = os.Rename(src, dst)
   199  	if err != nil {
   200  		err = gerror.Wrapf(err, `os.Rename failed from "%s" to "%s"`, src, dst)
   201  	}
   202  	return
   203  }
   204  
   205  // Rename is alias of Move.
   206  // See Move.
   207  func Rename(src string, dst string) error {
   208  	return Move(src, dst)
   209  }
   210  
   211  // DirNames returns sub-file names of given directory `path`.
   212  // Note that the returned names are NOT absolute paths.
   213  func DirNames(path string) ([]string, error) {
   214  	f, err := Open(path)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	list, err := f.Readdirnames(-1)
   219  	_ = f.Close()
   220  	if err != nil {
   221  		err = gerror.Wrapf(err, `Read dir files failed from path "%s"`, path)
   222  		return nil, err
   223  	}
   224  	return list, nil
   225  }
   226  
   227  // Glob returns the names of all files matching pattern or nil
   228  // if there is no matching file. The syntax of patterns is the same
   229  // as in Match. The pattern may describe hierarchical names such as
   230  // /usr/*/bin/ed (assuming the Separator is '/').
   231  //
   232  // Glob ignores file system errors such as I/O errors reading directories.
   233  // The only possible returned error is ErrBadPattern, when pattern
   234  // is malformed.
   235  func Glob(pattern string, onlyNames ...bool) ([]string, error) {
   236  	list, err := filepath.Glob(pattern)
   237  	if err != nil {
   238  		err = gerror.Wrapf(err, `filepath.Glob failed for pattern "%s"`, pattern)
   239  		return nil, err
   240  	}
   241  	if len(onlyNames) > 0 && onlyNames[0] && len(list) > 0 {
   242  		array := make([]string, len(list))
   243  		for k, v := range list {
   244  			array[k] = Basename(v)
   245  		}
   246  		return array, nil
   247  	}
   248  	return list, nil
   249  }
   250  
   251  // Remove deletes all file/directory with `path` parameter.
   252  // If parameter `path` is directory, it deletes it recursively.
   253  //
   254  // It does nothing if given `path` does not exist or is empty.
   255  func Remove(path string) (err error) {
   256  	// It does nothing if `path` is empty.
   257  	if path == "" {
   258  		return nil
   259  	}
   260  	if err = os.RemoveAll(path); err != nil {
   261  		err = gerror.Wrapf(err, `os.RemoveAll failed for path "%s"`, path)
   262  	}
   263  	return
   264  }
   265  
   266  // IsReadable checks whether given `path` is readable.
   267  func IsReadable(path string) bool {
   268  	result := true
   269  	file, err := os.OpenFile(path, os.O_RDONLY, DefaultPermOpen)
   270  	if err != nil {
   271  		result = false
   272  	}
   273  	file.Close()
   274  	return result
   275  }
   276  
   277  // IsWritable checks whether given `path` is writable.
   278  //
   279  // TODO improve performance; use golang.org/x/sys to cross-plat-form
   280  func IsWritable(path string) bool {
   281  	result := true
   282  	if IsDir(path) {
   283  		// If it's a directory, create a temporary file to test whether it's writable.
   284  		tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano())
   285  		if f, err := Create(tmpFile); err != nil || !Exists(tmpFile) {
   286  			result = false
   287  		} else {
   288  			_ = f.Close()
   289  			_ = Remove(tmpFile)
   290  		}
   291  	} else {
   292  		// If it's a file, check if it can open it.
   293  		file, err := os.OpenFile(path, os.O_WRONLY, DefaultPermOpen)
   294  		if err != nil {
   295  			result = false
   296  		}
   297  		_ = file.Close()
   298  	}
   299  	return result
   300  }
   301  
   302  // Chmod is alias of os.Chmod.
   303  // See os.Chmod.
   304  func Chmod(path string, mode os.FileMode) (err error) {
   305  	err = os.Chmod(path, mode)
   306  	if err != nil {
   307  		err = gerror.Wrapf(err, `os.Chmod failed with path "%s" and mode "%s"`, path, mode)
   308  	}
   309  	return
   310  }
   311  
   312  // Abs returns an absolute representation of path.
   313  // If the path is not absolute it will be joined with the current
   314  // working directory to turn it into an absolute path. The absolute
   315  // path name for a given file is not guaranteed to be unique.
   316  // Abs calls Clean on the result.
   317  func Abs(path string) string {
   318  	p, _ := filepath.Abs(path)
   319  	return p
   320  }
   321  
   322  // RealPath converts the given `path` to its absolute path
   323  // and checks if the file path exists.
   324  // If the file does not exist, return an empty string.
   325  func RealPath(path string) string {
   326  	p, err := filepath.Abs(path)
   327  	if err != nil {
   328  		return ""
   329  	}
   330  	if !Exists(p) {
   331  		return ""
   332  	}
   333  	return p
   334  }
   335  
   336  // SelfPath returns absolute file path of current running process(binary).
   337  func SelfPath() string {
   338  	return selfPath
   339  }
   340  
   341  // SelfName returns file name of current running process(binary).
   342  func SelfName() string {
   343  	return Basename(SelfPath())
   344  }
   345  
   346  // SelfDir returns absolute directory path of current running process(binary).
   347  func SelfDir() string {
   348  	return filepath.Dir(SelfPath())
   349  }
   350  
   351  // Basename returns the last element of path, which contains file extension.
   352  // Trailing path separators are removed before extracting the last element.
   353  // If the path is empty, Base returns ".".
   354  // If the path consists entirely of separators, Basename returns a single separator.
   355  //
   356  // Example:
   357  // Basename("/var/www/file.js") -> file.js
   358  // Basename("file.js")          -> file.js
   359  func Basename(path string) string {
   360  	return filepath.Base(path)
   361  }
   362  
   363  // Name returns the last element of path without file extension.
   364  //
   365  // Example:
   366  // Name("/var/www/file.js") -> file
   367  // Name("file.js")          -> file
   368  func Name(path string) string {
   369  	base := filepath.Base(path)
   370  	if i := strings.LastIndexByte(base, '.'); i != -1 {
   371  		return base[:i]
   372  	}
   373  	return base
   374  }
   375  
   376  // Dir returns all but the last element of path, typically the path's directory.
   377  // After dropping the final element, Dir calls Clean on the path and trailing
   378  // slashes are removed.
   379  // If the `path` is empty, Dir returns ".".
   380  // If the `path` is ".", Dir treats the path as current working directory.
   381  // If the `path` consists entirely of separators, Dir returns a single separator.
   382  // The returned path does not end in a separator unless it is the root directory.
   383  //
   384  // Example:
   385  // Dir("/var/www/file.js") -> "/var/www"
   386  // Dir("file.js")          -> "."
   387  func Dir(path string) string {
   388  	if path == "." {
   389  		return filepath.Dir(RealPath(path))
   390  	}
   391  	return filepath.Dir(path)
   392  }
   393  
   394  // IsEmpty checks whether the given `path` is empty.
   395  // If `path` is a folder, it checks if there's any file under it.
   396  // If `path` is a file, it checks if the file size is zero.
   397  //
   398  // Note that it returns true if `path` does not exist.
   399  func IsEmpty(path string) bool {
   400  	stat, err := Stat(path)
   401  	if err != nil {
   402  		return true
   403  	}
   404  	if stat.IsDir() {
   405  		file, err := os.Open(path)
   406  		if err != nil {
   407  			return true
   408  		}
   409  		defer file.Close()
   410  		names, err := file.Readdirnames(-1)
   411  		if err != nil {
   412  			return true
   413  		}
   414  		return len(names) == 0
   415  	} else {
   416  		return stat.Size() == 0
   417  	}
   418  }
   419  
   420  // Ext returns the file name extension used by path.
   421  // The extension is the suffix beginning at the final dot
   422  // in the final element of path; it is empty if there is
   423  // no dot.
   424  // Note: the result contains symbol '.'.
   425  //
   426  // Example:
   427  // Ext("main.go")  => .go
   428  // Ext("api.json") => .json
   429  func Ext(path string) string {
   430  	ext := filepath.Ext(path)
   431  	if p := strings.IndexByte(ext, '?'); p != -1 {
   432  		ext = ext[0:p]
   433  	}
   434  	return ext
   435  }
   436  
   437  // ExtName is like function Ext, which returns the file name extension used by path,
   438  // but the result does not contain symbol '.'.
   439  //
   440  // Example:
   441  // ExtName("main.go")  => go
   442  // ExtName("api.json") => json
   443  func ExtName(path string) string {
   444  	return strings.TrimLeft(Ext(path), ".")
   445  }
   446  
   447  // Temp retrieves and returns the temporary directory of current system.
   448  //
   449  // The optional parameter `names` specifies the sub-folders/sub-files,
   450  // which will be joined with current system separator and returned with the path.
   451  func Temp(names ...string) string {
   452  	path := os.TempDir()
   453  	for _, name := range names {
   454  		path = Join(path, name)
   455  	}
   456  	return path
   457  }