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 }