github.com/enetx/g@v1.0.80/dir.go (about)

     1  package g
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/fs"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  // SkipWalk is used as a return value from the walker function to indicate that
    12  // the file or directory named in the call should be skipped. It is not returned
    13  // as an error by any function.
    14  var SkipWalk = errors.New("skip")
    15  
    16  // StopWalk is used as a return value from the walker function to indicate that
    17  // all remaining files and directories should be skipped. It is not returned
    18  // as an error by any function.
    19  var StopWalk = errors.New("stop")
    20  
    21  // NewDir returns a new Dir instance with the given path.
    22  func NewDir(path String) *Dir { return &Dir{path: path} }
    23  
    24  // Chown changes the ownership of the directory to the specified UID and GID.
    25  // It uses os.Chown to modify ownership and returns a Result[*Dir] indicating success or failure.
    26  func (d *Dir) Chown(uid, gid int) Result[*Dir] {
    27  	err := os.Chown(d.path.Std(), uid, gid)
    28  	if err != nil {
    29  		return Err[*Dir](err)
    30  	}
    31  
    32  	return Ok(d)
    33  }
    34  
    35  // Stat retrieves information about the directory represented by the Dir instance.
    36  // It returns a Result[fs.FileInfo] containing details about the directory's metadata.
    37  func (d *Dir) Stat() Result[fs.FileInfo] {
    38  	if d.Path().IsErr() {
    39  		return Err[fs.FileInfo](d.Path().Err())
    40  	}
    41  
    42  	return ResultOf(os.Stat(d.Path().Ok().Std()))
    43  }
    44  
    45  // Lstat retrieves information about the symbolic link represented by the Dir instance.
    46  // It returns a Result[fs.FileInfo] containing details about the symbolic link's metadata.
    47  // Unlike Stat, Lstat does not follow the link and provides information about the link itself.
    48  func (d *Dir) Lstat() Result[fs.FileInfo] {
    49  	return ResultOf(os.Lstat(d.Path().Ok().Std()))
    50  }
    51  
    52  // IsLink checks if the directory is a symbolic link.
    53  func (d *Dir) IsLink() bool {
    54  	stat := d.Lstat()
    55  	return stat.IsOk() && stat.Ok().Mode()&os.ModeSymlink != 0
    56  }
    57  
    58  // CreateTemp creates a new temporary directory in the specified directory with the
    59  // specified name pattern and returns a Result, which contains a pointer to the Dir
    60  // or an error if the operation fails.
    61  // If no directory is specified, the default directory for temporary directories is used.
    62  // If no name pattern is specified, the default pattern "*" is used.
    63  //
    64  // Parameters:
    65  //
    66  // - args ...String: A variadic parameter specifying the directory and/or name
    67  // pattern for the temporary directory.
    68  //
    69  // Returns:
    70  //
    71  // - *Dir: A pointer to the Dir representing the temporary directory.
    72  //
    73  // Example usage:
    74  //
    75  // d := g.NewDir("")
    76  // tmpdir := d.CreateTemp()                     // Creates a temporary directory with default settings
    77  // tmpdirWithDir := d.CreateTemp("mydir")       // Creates a temporary directory in "mydir" directory
    78  // tmpdirWithPattern := d.CreateTemp("", "tmp") // Creates a temporary directory with "tmp" pattern
    79  func (*Dir) CreateTemp(args ...String) Result[*Dir] {
    80  	dir := ""
    81  	pattern := "*"
    82  
    83  	if len(args) != 0 {
    84  		if len(args) > 1 {
    85  			pattern = args[1].Std()
    86  		}
    87  
    88  		dir = args[0].Std()
    89  	}
    90  
    91  	tmpDir, err := os.MkdirTemp(dir, pattern)
    92  	if err != nil {
    93  		return Err[*Dir](err)
    94  	}
    95  
    96  	return Ok(NewDir(String(tmpDir)))
    97  }
    98  
    99  // Temp returns the default directory to use for temporary files.
   100  //
   101  // On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
   102  // On Windows, it uses GetTempPath, returning the first non-empty
   103  // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
   104  // On Plan 9, it returns /tmp.
   105  //
   106  // The directory is neither guaranteed to exist nor have accessible
   107  // permissions.
   108  func (*Dir) Temp() *Dir { return NewDir(String(os.TempDir())) }
   109  
   110  // Remove attempts to delete the directory and its contents.
   111  // It returns a Result, which contains either the *Dir or an error.
   112  // If the directory does not exist, Remove returns a successful Result with *Dir set.
   113  // Any error that occurs during removal will be of type *PathError.
   114  func (d *Dir) Remove() Result[*Dir] {
   115  	if err := os.RemoveAll(d.ToString().Std()); err != nil {
   116  		return Err[*Dir](err)
   117  	}
   118  
   119  	return Ok(d)
   120  }
   121  
   122  // Copy copies the contents of the current directory to the destination directory.
   123  //
   124  // Parameters:
   125  //
   126  // - dest (String): The destination directory where the contents of the current directory should be copied.
   127  //
   128  // - followLinks (optional): A boolean indicating whether to follow symbolic links during the walk.
   129  // If true, symbolic links are followed; otherwise, they are skipped.
   130  //
   131  // Returns:
   132  //
   133  // - Result[*Dir]: A Result type containing either a pointer to a new Dir instance representing the destination directory or an error.
   134  //
   135  // Example usage:
   136  //
   137  //	sourceDir := g.NewDir("path/to/source")
   138  //	destinationDirResult := sourceDir.Copy("path/to/destination")
   139  //	if destinationDirResult.IsErr() {
   140  //		// Handle error
   141  //	}
   142  //	destinationDir := destinationDirResult.Ok()
   143  func (d *Dir) Copy(dest String, followLinks ...bool) Result[*Dir] {
   144  	follow := true
   145  	if len(followLinks) != 0 {
   146  		follow = followLinks[0]
   147  	}
   148  
   149  	root := d.Path()
   150  	if root.IsErr() {
   151  		return Err[*Dir](root.Err())
   152  	}
   153  
   154  	walker := func(f *File) error {
   155  		path := f.Path()
   156  		if path.IsErr() {
   157  			return path.Err()
   158  		}
   159  
   160  		relpath, err := filepath.Rel(root.Ok().Std(), path.Ok().Std())
   161  		if err != nil {
   162  			return err
   163  		}
   164  
   165  		destpath := NewDir(dest).Join(String(relpath))
   166  		if destpath.IsErr() {
   167  			return destpath.Err()
   168  		}
   169  
   170  		stat := f.Stat()
   171  		if stat.IsErr() {
   172  			return stat.Err()
   173  		}
   174  
   175  		if stat.Ok().IsDir() {
   176  			if !follow && f.IsLink() {
   177  				return SkipWalk
   178  			}
   179  
   180  			return NewDir(destpath.Ok()).CreateAll(stat.Ok().Mode()).Err()
   181  		}
   182  
   183  		return f.Copy(destpath.Ok(), stat.Ok().Mode()).Err()
   184  	}
   185  
   186  	if err := d.Walk(walker); err != nil {
   187  		return Err[*Dir](err)
   188  	}
   189  
   190  	return Ok(NewDir(dest))
   191  }
   192  
   193  // Create creates a new directory with the specified mode (optional).
   194  //
   195  // Parameters:
   196  //
   197  // - mode (os.FileMode, optional): The file mode for the new directory.
   198  // If not provided, it defaults to DirDefault (0755).
   199  //
   200  // Returns:
   201  //
   202  // - *Dir: A pointer to the Dir instance on which the method was called.
   203  //
   204  // Example usage:
   205  //
   206  //	dir := g.NewDir("path/to/directory")
   207  //	createdDir := dir.Create(0755) // Optional mode argument
   208  func (d *Dir) Create(mode ...os.FileMode) Result[*Dir] {
   209  	dmode := DirDefault
   210  	if len(mode) != 0 {
   211  		dmode = mode[0]
   212  	}
   213  
   214  	if err := os.Mkdir(d.path.Std(), dmode); err != nil {
   215  		return Err[*Dir](err)
   216  	}
   217  
   218  	return Ok(d)
   219  }
   220  
   221  // Join joins the current directory path with the given path elements, returning the joined path.
   222  //
   223  // Parameters:
   224  //
   225  // - elem (...String): One or more String values representing path elements to
   226  // be joined with the current directory path.
   227  //
   228  // Returns:
   229  //
   230  // - String: The resulting joined path as an String.
   231  //
   232  // Example usage:
   233  //
   234  //	dir := g.NewDir("path/to/directory")
   235  //	joinedPath := dir.Join("subdir", "file.txt")
   236  func (d *Dir) Join(elem ...String) Result[String] {
   237  	path := d.Path()
   238  	if path.IsErr() {
   239  		return Err[String](path.Err())
   240  	}
   241  
   242  	paths := SliceOf(elem...).Insert(0, path.Ok()).ToStringSlice()
   243  
   244  	return Ok(String(filepath.Join(paths...)))
   245  }
   246  
   247  // SetPath sets the path of the current directory.
   248  //
   249  // Parameters:
   250  //
   251  // - path (String): The new path to be set for the current directory.
   252  //
   253  // Returns:
   254  //
   255  // - *Dir: A pointer to the updated Dir instance with the new path.
   256  //
   257  // Example usage:
   258  //
   259  //	dir := g.NewDir("path/to/directory")
   260  //	dir.SetPath("new/path/to/directory")
   261  func (d *Dir) SetPath(path String) *Dir {
   262  	d.path = path
   263  	return d
   264  }
   265  
   266  // CreateAll creates all directories along the given path, with the specified mode (optional).
   267  //
   268  // Parameters:
   269  //
   270  // - mode ...os.FileMode (optional): The file mode to be used when creating the directories.
   271  // If not provided, it defaults to the value of DirDefault constant (0755).
   272  //
   273  // Returns:
   274  //
   275  // - *Dir: A pointer to the Dir instance representing the created directories.
   276  //
   277  // Example usage:
   278  //
   279  //	dir := g.NewDir("path/to/directory")
   280  //	dir.CreateAll()
   281  //	dir.CreateAll(0755)
   282  func (d *Dir) CreateAll(mode ...os.FileMode) Result[*Dir] {
   283  	if d.Exist() {
   284  		return Ok(d)
   285  	}
   286  
   287  	dmode := DirDefault
   288  	if len(mode) != 0 {
   289  		dmode = mode[0]
   290  	}
   291  
   292  	path := d.Path()
   293  	if path.IsErr() {
   294  		return Err[*Dir](path.Err())
   295  	}
   296  
   297  	err := os.MkdirAll(path.Ok().Std(), dmode)
   298  	if err != nil {
   299  		return Err[*Dir](err)
   300  	}
   301  
   302  	return Ok(d)
   303  }
   304  
   305  // Rename renames the current directory to the new path.
   306  //
   307  // Parameters:
   308  //
   309  // - newpath String: The new path for the directory.
   310  //
   311  // Returns:
   312  //
   313  // - *Dir: A pointer to the Dir instance representing the renamed directory.
   314  // If an error occurs, the original Dir instance is returned with the error stored in d.err,
   315  // which can be checked using the Error() method.
   316  //
   317  // Example usage:
   318  //
   319  //	dir := g.NewDir("path/to/directory")
   320  //	dir.Rename("path/to/new_directory")
   321  func (d *Dir) Rename(newpath String) Result[*Dir] {
   322  	ps := String(os.PathSeparator)
   323  	_, np := newpath.TrimSuffix(ps).Split(ps).Collect().Pop()
   324  
   325  	if rd := NewDir(np.Join(ps)).CreateAll(); rd.IsErr() {
   326  		return rd
   327  	}
   328  
   329  	if err := os.Rename(d.path.Std(), newpath.Std()); err != nil {
   330  		return Err[*Dir](err)
   331  	}
   332  
   333  	return Ok(NewDir(newpath))
   334  }
   335  
   336  // Move function simply calls [Dir.Rename]
   337  func (d *Dir) Move(newpath String) Result[*Dir] { return d.Rename(newpath) }
   338  
   339  // Path returns the absolute path of the current directory.
   340  //
   341  // Returns:
   342  //
   343  // - String: The absolute path of the current directory as an String.
   344  // If an error occurs while converting the path to an absolute path,
   345  // the error is stored in d.err, which can be checked using the Error() method.
   346  //
   347  // Example usage:
   348  //
   349  //	dir := g.NewDir("path/to/directory")
   350  //	absPath := dir.Path()
   351  func (d *Dir) Path() Result[String] {
   352  	path, err := filepath.Abs(d.path.Std())
   353  	if err != nil {
   354  		return Err[String](err)
   355  	}
   356  
   357  	return Ok(String(path))
   358  }
   359  
   360  // Exist checks if the current directory exists.
   361  //
   362  // Returns:
   363  //
   364  // - bool: true if the current directory exists, false otherwise.
   365  //
   366  // Example usage:
   367  //
   368  //	dir := g.NewDir("path/to/directory")
   369  //	exists := dir.Exist()
   370  func (d *Dir) Exist() bool {
   371  	path := d.Path()
   372  	if path.IsErr() {
   373  		return false
   374  	}
   375  
   376  	_, err := os.Stat(path.Ok().Std())
   377  
   378  	return !os.IsNotExist(err)
   379  }
   380  
   381  // Read reads the content of the current directory and returns a slice of File instances.
   382  //
   383  // Returns:
   384  //
   385  // - []*File: A slice of File instances representing the files and directories
   386  // in the current directory.
   387  //
   388  // Example usage:
   389  //
   390  //	dir := g.NewDir("path/to/directory")
   391  //	files := dir.Read()
   392  
   393  func (d *Dir) Read() Result[Slice[*File]] {
   394  	entries, err := os.ReadDir(d.path.Std())
   395  	if err != nil {
   396  		return Err[Slice[*File]](err)
   397  	}
   398  
   399  	files := NewSlice[*File](0, Int(len(entries)))
   400  
   401  	for _, entry := range entries {
   402  		dpath := d.Path()
   403  		if dpath.IsErr() {
   404  			return Err[Slice[*File]](dpath.Err())
   405  		}
   406  
   407  		file := NewDir(dpath.Ok()).Join(String(entry.Name()))
   408  		if file.IsErr() {
   409  			return Err[Slice[*File]](file.Err())
   410  		}
   411  
   412  		files = files.Append(NewFile(file.Ok()))
   413  	}
   414  
   415  	return Ok(files)
   416  }
   417  
   418  // Glob matches files in the current directory using the path pattern and
   419  // returns a slice of File instances.
   420  //
   421  // Returns:
   422  //
   423  // - []*File: A slice of File instances representing the files that match the
   424  // provided pattern in the current directory.
   425  //
   426  // Example usage:
   427  //
   428  //	dir := g.NewDir("path/to/directory/*.txt")
   429  //	files := dir.Glob()
   430  func (d *Dir) Glob() Result[Slice[*File]] {
   431  	matches, err := filepath.Glob(d.path.Std())
   432  	if err != nil {
   433  		return Err[Slice[*File]](err)
   434  	}
   435  
   436  	files := NewSlice[*File](0, Int(len(matches)))
   437  
   438  	for _, match := range matches {
   439  		file := NewFile(String(match)).Path()
   440  		if file.IsErr() {
   441  			return Err[Slice[*File]](file.Err())
   442  		}
   443  
   444  		files = files.Append(NewFile(file.Ok()))
   445  	}
   446  
   447  	return Ok(files)
   448  }
   449  
   450  // Walk recursively traverses the directory structure and applies the walker function to each file and directory.
   451  //
   452  // Parameters:
   453  //
   454  // - walker: A function that takes a *File as an argument and returns an error.
   455  // It is applied to each file and directory encountered during the walk.
   456  //
   457  // Returns:
   458  //
   459  // - error: An error indicating any issues that occurred during the walk. If no errors occurred, it returns nil.
   460  func (d *Dir) Walk(walker func(f *File) error) error {
   461  	entries := d.Read()
   462  	if entries.IsErr() {
   463  		return entries.Err()
   464  	}
   465  
   466  	for _, entry := range entries.Ok() {
   467  		if err := walker(entry); err != nil {
   468  			switch {
   469  			case errors.Is(err, SkipWalk):
   470  				continue
   471  			case errors.Is(err, StopWalk):
   472  				return nil
   473  			default:
   474  				return err
   475  			}
   476  		}
   477  
   478  		stat := entry.Stat()
   479  		if stat.IsErr() {
   480  			return stat.Err()
   481  		}
   482  
   483  		if stat.Ok().IsDir() {
   484  			entryPath := entry.Path()
   485  			if entryPath.IsErr() {
   486  				return entryPath.Err()
   487  			}
   488  
   489  			subdir := NewDir(entryPath.Ok())
   490  
   491  			if err := subdir.Walk(walker); err != nil {
   492  				return err
   493  			}
   494  		}
   495  	}
   496  
   497  	return nil
   498  }
   499  
   500  // ToString returns the String representation of the current directory's path.
   501  func (d *Dir) ToString() String { return d.path }
   502  
   503  // Print prints the content of the Dir to the standard output (console)
   504  // and returns the Dir unchanged.
   505  func (d *Dir) Print() *Dir { fmt.Println(d); return d }