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 }