Initial QSfera import
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
# FileSystem Util
|
||||
|
||||
`fsutil` Provide some commonly file system util functions.
|
||||
|
||||
## Install
|
||||
|
||||
```shell
|
||||
go get github.com/gookit/goutil/fsutil
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/fsutil)
|
||||
|
||||
## Find files
|
||||
|
||||
More see [./finder](./finder)
|
||||
|
||||
```go
|
||||
// find all files in dir
|
||||
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
|
||||
fmt.Println(filePath)
|
||||
return nil
|
||||
})
|
||||
|
||||
// find files with filters
|
||||
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
|
||||
fmt.Println(filePath)
|
||||
return nil
|
||||
}, fsutil.ExcludeDotFile)
|
||||
```
|
||||
|
||||
## Functions API
|
||||
|
||||
> **Note**: doc by run `go doc ./fsutil`
|
||||
|
||||
```go
|
||||
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool
|
||||
func CopyFile(srcPath, dstPath string) error
|
||||
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error)
|
||||
func DeleteIfExist(fPath string) error
|
||||
func DeleteIfFileExist(fPath string) error
|
||||
func Dir(fpath string) string
|
||||
func DiscardReader(src io.Reader)
|
||||
func ExcludeDotFile(_ string, ent fs.DirEntry) bool
|
||||
func Expand(pathStr string) string
|
||||
func ExpandPath(pathStr string) string
|
||||
func Extname(fpath string) string
|
||||
func FileExists(path string) bool
|
||||
func FileExt(fpath string) string
|
||||
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
|
||||
func GetContents(in any) []byte
|
||||
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error)
|
||||
func IsAbsPath(aPath string) bool
|
||||
func IsDir(path string) bool
|
||||
func IsFile(path string) bool
|
||||
func IsImageFile(path string) bool
|
||||
func IsZipFile(filepath string) bool
|
||||
func JoinPaths(elem ...string) string
|
||||
func JoinSubPaths(basePath string, elem ...string) string
|
||||
func LineScanner(in any) *bufio.Scanner
|
||||
func MimeType(path string) (mime string)
|
||||
func MkDirs(perm os.FileMode, dirPaths ...string) error
|
||||
func MkParentDir(fpath string) error
|
||||
func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error
|
||||
func Mkdir(dirPath string, perm os.FileMode) error
|
||||
func MustCopyFile(srcPath, dstPath string)
|
||||
func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File
|
||||
func MustReadFile(filePath string) []byte
|
||||
func MustReadReader(r io.Reader) []byte
|
||||
func MustRemove(fPath string)
|
||||
func Name(fpath string) string
|
||||
func NewIOReader(in any) (r io.Reader, err error)
|
||||
func OSTempDir(pattern string) (string, error)
|
||||
func OSTempFile(pattern string) (*os.File, error)
|
||||
func OnlyFindDir(_ string, ent fs.DirEntry) bool
|
||||
func OnlyFindFile(_ string, ent fs.DirEntry) bool
|
||||
func OpenAppendFile(filepath string) (*os.File, error)
|
||||
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error)
|
||||
func OpenReadFile(filepath string) (*os.File, error)
|
||||
func OpenTruncFile(filepath string) (*os.File, error)
|
||||
func PathExists(path string) bool
|
||||
func PathMatch(pattern, s string) bool
|
||||
func PathName(fpath string) string
|
||||
func PutContents(filePath string, data any, fileFlag ...int) (int, error)
|
||||
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error)
|
||||
func QuietRemove(fPath string)
|
||||
func ReadAll(in any) []byte
|
||||
func ReadExistFile(filePath string) []byte
|
||||
func ReadFile(filePath string) []byte
|
||||
func ReadOrErr(in any) ([]byte, error)
|
||||
func ReadReader(r io.Reader) []byte
|
||||
func ReadString(in any) string
|
||||
func ReadStringOrErr(in any) (string, error)
|
||||
func ReaderMimeType(r io.Reader) (mime string)
|
||||
func Realpath(pathStr string) string
|
||||
func Remove(fPath string) error
|
||||
func ResolvePath(pathStr string) string
|
||||
func RmFileIfExist(fPath string) error
|
||||
func RmIfExist(fPath string) error
|
||||
func SearchNameUp(dirPath, name string) string
|
||||
func SearchNameUpx(dirPath, name string) (string, bool)
|
||||
func SlashPath(path string) string
|
||||
func SplitPath(pathStr string) (dir, name string)
|
||||
func Suffix(fpath string) string
|
||||
func TempDir(dir, pattern string) (string, error)
|
||||
func TempFile(dir, pattern string) (*os.File, error)
|
||||
func TextScanner(in any) *scanner.Scanner
|
||||
func ToAbsPath(p string) string
|
||||
func UnixPath(path string) string
|
||||
func Unzip(archive, targetDir string) (err error)
|
||||
func WalkDir(dir string, fn fs.WalkDirFunc) error
|
||||
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error
|
||||
func WriteOSFile(f *os.File, data any) (n int, err error)
|
||||
type FilterFunc func(fPath string, ent fs.DirEntry) bool
|
||||
func ExcludeSuffix(ss ...string) FilterFunc
|
||||
func IncludeSuffix(ss ...string) FilterFunc
|
||||
type HandleFunc func(fPath string, ent fs.DirEntry) error
|
||||
```
|
||||
|
||||
## Code Check & Testing
|
||||
|
||||
```bash
|
||||
gofmt -w -l ./
|
||||
golint ./...
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
|
||||
```shell
|
||||
go test -v ./fsutil/...
|
||||
```
|
||||
|
||||
**Test limit by regexp**:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./fsutil/...
|
||||
```
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// perm for create dir or file
|
||||
var (
|
||||
DefaultDirPerm os.FileMode = 0775
|
||||
DefaultFilePerm os.FileMode = 0665
|
||||
OnlyReadFilePerm os.FileMode = 0444
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFileFlags for create and write
|
||||
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
|
||||
// OnlyReadFileFlags open file for read
|
||||
OnlyReadFileFlags = os.O_RDONLY
|
||||
)
|
||||
|
||||
// alias methods
|
||||
var (
|
||||
DirExist = IsDir
|
||||
FileExist = IsFile
|
||||
PathExist = PathExists
|
||||
)
|
||||
|
||||
// PathExists reports whether the named file or directory exists.
|
||||
func PathExists(path string) bool {
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDir reports whether the named directory exists.
|
||||
func IsDir(path string) bool {
|
||||
if path == "" || len(path) > 468 {
|
||||
return false
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(path); err == nil {
|
||||
return fi.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(path string) bool {
|
||||
return IsFile(path)
|
||||
}
|
||||
|
||||
// IsFile reports whether the named file or directory exists.
|
||||
//
|
||||
// - NOTE: not support symlink file
|
||||
func IsFile(path string) bool {
|
||||
if path == "" || len(path) > 468 {
|
||||
return false
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(path); err == nil {
|
||||
return !fi.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSymlink reports whether the named file is a symlink.
|
||||
func IsSymlink(path string) bool {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fi.Mode()&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
// IsAbsPath is abs path.
|
||||
func IsAbsPath(aPath string) bool {
|
||||
if len(aPath) > 0 {
|
||||
if aPath[0] == '/' {
|
||||
return true
|
||||
}
|
||||
return filepath.IsAbs(aPath)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEmptyDir reports whether the named directory is empty.
|
||||
func IsEmptyDir(dirPath string) bool {
|
||||
f, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Readdirnames(1)
|
||||
return err == io.EOF
|
||||
}
|
||||
|
||||
// ImageMimeTypes refer net/http package
|
||||
var ImageMimeTypes = map[string]string{
|
||||
"bmp": "image/bmp",
|
||||
"gif": "image/gif",
|
||||
"ief": "image/ief",
|
||||
"jpg": "image/jpeg",
|
||||
// "jpe": "image/jpeg",
|
||||
"jpeg": "image/jpeg",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
"ico": "image/x-icon",
|
||||
"webp": "image/webp",
|
||||
}
|
||||
|
||||
// IsImageFile check file is image file.
|
||||
func IsImageFile(path string) bool {
|
||||
mime := MimeType(path)
|
||||
if mime == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, imgMime := range ImageMimeTypes {
|
||||
if imgMime == mime {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsZipFile check is zip file.
|
||||
// from https://blog.csdn.net/wangshubo1989/article/details/71743374
|
||||
func IsZipFile(filepath string) bool {
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buf := make([]byte, 4)
|
||||
if n, err := f.Read(buf); err != nil || n < 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(buf, []byte("PK\x03\x04"))
|
||||
}
|
||||
|
||||
// PathMatch check for a string. alias of path.Match()
|
||||
func PathMatch(pattern, s string) bool {
|
||||
ok, err := path.Match(pattern, s)
|
||||
if err != nil {
|
||||
ok = false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// MimeSniffLen sniff Length, use for detect file mime type
|
||||
MimeSniffLen = 512
|
||||
)
|
||||
|
||||
// NameMatchFunc name matches func, alias of comdef.StringMatchFunc
|
||||
type NameMatchFunc = comdef.StringMatchFunc
|
||||
|
||||
// PathMatchFunc path matches func. alias of comdef.StringMatchFunc
|
||||
type PathMatchFunc = comdef.StringMatchFunc
|
||||
|
||||
// Entry extends fs.DirEntry, add some useful methods
|
||||
type Entry interface {
|
||||
fs.DirEntry
|
||||
// Path gets file/dir full path. eg: "/path/to/file.go"
|
||||
Path() string
|
||||
// Info get file info. like fs.DirEntry.Info(), but will cache result.
|
||||
Info() (fs.FileInfo, error)
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
fs.DirEntry
|
||||
path string
|
||||
stat fs.FileInfo
|
||||
sErr error
|
||||
}
|
||||
|
||||
// NewEntry create a new Entry instance
|
||||
func NewEntry(fPath string, ent fs.DirEntry) Entry {
|
||||
return &entry{
|
||||
path: fPath,
|
||||
DirEntry: ent,
|
||||
}
|
||||
}
|
||||
|
||||
// Path gets full file/dir path. eg: "/path/to/file.go"
|
||||
func (e *entry) Path() string {
|
||||
return e.path
|
||||
}
|
||||
|
||||
// Info gets file info, will cache result
|
||||
func (e *entry) Info() (fs.FileInfo, error) {
|
||||
if e.stat == nil {
|
||||
e.stat, e.sErr = e.DirEntry.Info()
|
||||
}
|
||||
return e.stat, e.sErr
|
||||
}
|
||||
|
||||
// String get string representation
|
||||
func (e *entry) String() string {
|
||||
return strutil.OrCond(e.IsDir(), "dir: ", "file: ") + e.Path()
|
||||
}
|
||||
|
||||
// FileInfo extends fs.FileInfo, add some useful methods
|
||||
type FileInfo interface {
|
||||
fs.FileInfo
|
||||
// Path gets file full path. eg: "/path/to/file.go"
|
||||
Path() string
|
||||
}
|
||||
|
||||
type fileInfo struct {
|
||||
fs.FileInfo
|
||||
fullPath string
|
||||
}
|
||||
|
||||
// NewFileInfo create a new FileInfo instance
|
||||
func NewFileInfo(fPath string, info fs.FileInfo) FileInfo {
|
||||
return &fileInfo{
|
||||
fullPath: fPath,
|
||||
FileInfo: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Path gets file full path. eg: "/path/to/file.go"
|
||||
func (fi *fileInfo) Path() string {
|
||||
return fi.fullPath
|
||||
}
|
||||
|
||||
// FileInfos type for FileInfo slice
|
||||
//
|
||||
// implements sort.Interface:
|
||||
//
|
||||
// sorts by oldest time modified in the file info.
|
||||
// eg: [old_220211, old_220212, old_220213]
|
||||
type FileInfos []FileInfo
|
||||
|
||||
// Len get length
|
||||
func (fis FileInfos) Len() int {
|
||||
return len(fis)
|
||||
}
|
||||
|
||||
// Swap swap values
|
||||
func (fis FileInfos) Swap(i, j int) {
|
||||
fis[i], fis[j] = fis[j], fis[i]
|
||||
}
|
||||
|
||||
// Less check by mod time
|
||||
func (fis FileInfos) Less(i, j int) bool {
|
||||
return fis[j].ModTime().After(fis[i].ModTime())
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// FilePathInDirs get full file path in dirs. return empty string if not found.
|
||||
//
|
||||
// Params:
|
||||
// - file: can be relative path, file name, full path.
|
||||
// - dirs: dir paths
|
||||
func FilePathInDirs(fPath string, dirs ...string) string {
|
||||
fPath = comfunc.ExpandHome(fPath)
|
||||
if FileExists(fPath) {
|
||||
return fPath
|
||||
}
|
||||
|
||||
for _, dirPath := range dirs {
|
||||
fPath = JoinSubPaths(dirPath, fPath)
|
||||
if FileExists(fPath) {
|
||||
return fPath
|
||||
}
|
||||
}
|
||||
return "" // not found
|
||||
}
|
||||
|
||||
// FirstExists check multi paths and return first exists path.
|
||||
func FirstExists(paths ...string) string {
|
||||
return MatchFirst(paths, PathExists, "")
|
||||
}
|
||||
|
||||
// FirstExistsDir check multi paths and return first exists dir.
|
||||
func FirstExistsDir(paths ...string) string {
|
||||
return MatchFirst(paths, IsDir, "")
|
||||
}
|
||||
|
||||
// FirstExistsFile check multi paths and return first exists file.
|
||||
func FirstExistsFile(paths ...string) string {
|
||||
return MatchFirst(paths, IsFile, "")
|
||||
}
|
||||
|
||||
// MatchPaths given paths by custom mather func.
|
||||
func MatchPaths(paths []string, matcher PathMatchFunc) []string {
|
||||
var ret []string
|
||||
for _, p := range paths {
|
||||
if matcher(p) {
|
||||
ret = append(ret, p)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// MatchFirst filter paths by filter func and return first match path.
|
||||
func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string {
|
||||
for _, p := range paths {
|
||||
if matcher(p) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
// FindParentOption options
|
||||
type FindParentOption struct {
|
||||
MaxLevel int // default: 10
|
||||
// NeedDir true: find dirs; false(default): find files
|
||||
NeedDir bool
|
||||
OnlyOne bool // only find one, default: true
|
||||
// Collector func
|
||||
Collector func(fullPath string)
|
||||
// MatchFunc custom matcher func. return false to stop find.
|
||||
MatchFunc func(currentDir string) bool
|
||||
}
|
||||
|
||||
// FindParentOptFn find parent option func
|
||||
type FindParentOptFn func(opt *FindParentOption)
|
||||
|
||||
// FindAllInParentDirs looks for all match file(default)/dir in the current directory and parent directories
|
||||
func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string {
|
||||
var foundPaths []string
|
||||
optFns = append(optFns, func(opt *FindParentOption) {
|
||||
opt.OnlyOne = false
|
||||
})
|
||||
|
||||
FindNameInParentDirs(dirPath, name, func(fullPath string) {
|
||||
foundPaths = append(foundPaths, fullPath)
|
||||
}, optFns...)
|
||||
return foundPaths
|
||||
}
|
||||
|
||||
// FindOneInParentDirs looks for a file(default)/dir in the current directory and parent directories
|
||||
func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string {
|
||||
var foundPath string
|
||||
FindNameInParentDirs(dirPath, name, func(fullPath string) {
|
||||
foundPath = fullPath
|
||||
}, optFns...)
|
||||
return foundPath
|
||||
}
|
||||
|
||||
// FindNameInParentDirs looks for file(default)/dir in the current directory and parent directories
|
||||
func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) {
|
||||
opts := &FindParentOption{
|
||||
MaxLevel: 10,
|
||||
OnlyOne: true,
|
||||
Collector: collectFn,
|
||||
}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
FindInParentDirs(dirPath, func(currentDir string) bool {
|
||||
filePath := filepath.Join(currentDir, name)
|
||||
if fi, err := os.Stat(filePath); err == nil {
|
||||
found := false
|
||||
if fi.IsDir() {
|
||||
found = opts.NeedDir
|
||||
} else {
|
||||
found = !opts.NeedDir
|
||||
}
|
||||
|
||||
if found {
|
||||
opts.Collector(filePath)
|
||||
return !opts.OnlyOne
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, opts.MaxLevel)
|
||||
}
|
||||
|
||||
// FindInParentDirs looks for file/dir in the current directory and parent directories
|
||||
// - MatchFunc custom matcher func. return false to stop find.
|
||||
func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) {
|
||||
currentLv := 1
|
||||
currentDir := ToAbsPath(dirPath)
|
||||
|
||||
for {
|
||||
// Check if the file exists in the current directory
|
||||
if !matchFunc(currentDir) {
|
||||
return
|
||||
}
|
||||
|
||||
// check find level
|
||||
if maxLevel > 0 && currentLv > maxLevel {
|
||||
break
|
||||
}
|
||||
|
||||
// Get parent directory
|
||||
parentDir := filepath.Dir(currentDir)
|
||||
if parentDir == currentDir {
|
||||
// Reached the root, file not found
|
||||
return
|
||||
}
|
||||
|
||||
// Move to parent directory
|
||||
currentLv++
|
||||
currentDir = parentDir
|
||||
}
|
||||
}
|
||||
|
||||
// SearchNameUp find file/dir name in dirPath or parent dirs,
|
||||
// return the name of directory path
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// repoDir := fsutil.SearchNameUp("/path/to/dir", ".git")
|
||||
func SearchNameUp(dirPath, name string) string {
|
||||
dir, _ := SearchNameUpx(dirPath, name)
|
||||
return dir
|
||||
}
|
||||
|
||||
// SearchNameUpx find file/dir name in dirPath or parent dirs,
|
||||
// return the name of directory path and dir is changed.
|
||||
func SearchNameUpx(dirPath, name string) (string, bool) {
|
||||
var level int
|
||||
dirPath = ToAbsPath(dirPath)
|
||||
|
||||
for {
|
||||
namePath := filepath.Join(dirPath, name)
|
||||
if PathExists(namePath) {
|
||||
return dirPath, level > 0
|
||||
}
|
||||
|
||||
level++
|
||||
prevLn := len(dirPath)
|
||||
dirPath = filepath.Dir(dirPath)
|
||||
if prevLn == len(dirPath) {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
||||
// directory in the tree, including root.
|
||||
//
|
||||
// TIP: will recursively found in sub dirs.
|
||||
func WalkDir(dir string, fn fs.WalkDirFunc) error {
|
||||
return filepath.WalkDir(dir, fn)
|
||||
}
|
||||
|
||||
// Glob finds files by glob path pattern. alias of filepath.Glob()
|
||||
// and support filter matched files by name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// files := fsutil.Glob("/path/to/dir/*.go")
|
||||
func Glob(pattern string, fls ...NameMatchFunc) []string {
|
||||
files, _ := filepath.Glob(pattern)
|
||||
if len(fls) == 0 || len(files) == 0 {
|
||||
return files
|
||||
}
|
||||
|
||||
var matched []string
|
||||
for _, file := range files {
|
||||
for _, fn := range fls {
|
||||
if fn(path.Base(file)) {
|
||||
matched = append(matched, file)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// GlobWithFunc find files by glob path pattern, then handle matched file
|
||||
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) {
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, filePath := range files {
|
||||
err = fn(filePath)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
// FilterFunc type for FindInDir
|
||||
//
|
||||
// - return False will skip handle the file.
|
||||
FilterFunc func(fPath string, ent fs.DirEntry) bool
|
||||
|
||||
// HandleFunc type for FindInDir
|
||||
HandleFunc func(fPath string, ent fs.DirEntry) error
|
||||
)
|
||||
|
||||
// OnlyFindDir on find
|
||||
func OnlyFindDir(_ string, ent fs.DirEntry) bool { return ent.IsDir() }
|
||||
|
||||
// OnlyFindFile on find
|
||||
func OnlyFindFile(_ string, ent fs.DirEntry) bool { return !ent.IsDir() }
|
||||
|
||||
// ExcludeNames on find
|
||||
func ExcludeNames(names ...string) FilterFunc {
|
||||
return func(_ string, ent fs.DirEntry) bool {
|
||||
return !arrutil.StringsHas(names, ent.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// IncludeSuffix on find
|
||||
func IncludeSuffix(ss ...string) FilterFunc {
|
||||
return func(_ string, ent fs.DirEntry) bool {
|
||||
return strutil.HasOneSuffix(ent.Name(), ss)
|
||||
}
|
||||
}
|
||||
|
||||
// ExcludeDotFile on find
|
||||
func ExcludeDotFile(_ string, ent fs.DirEntry) bool { return ent.Name()[0] != '.' }
|
||||
|
||||
// ExcludeSuffix on find
|
||||
func ExcludeSuffix(ss ...string) FilterFunc {
|
||||
return func(_ string, ent fs.DirEntry) bool {
|
||||
return !strutil.HasOneSuffix(ent.Name(), ss)
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyFilters handle
|
||||
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
|
||||
for _, filter := range filters {
|
||||
if !filter(fPath, ent) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FindInDir code refer the go pkg: path/filepath.glob()
|
||||
//
|
||||
// - TIP: default will be not found in sub-dir.
|
||||
//
|
||||
// filters: return false will skip the file.
|
||||
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil || !fi.IsDir() {
|
||||
return // ignore I/O error
|
||||
}
|
||||
|
||||
des, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// remove the last '/' char
|
||||
dirLn := len(dir)
|
||||
if dirLn > 1 && dir[dirLn-1] == '/' {
|
||||
dir = dir[:dirLn-1]
|
||||
}
|
||||
|
||||
for _, ent := range des {
|
||||
filePath := dir + "/" + ent.Name()
|
||||
|
||||
// apply filters
|
||||
if len(filters) > 0 && ApplyFilters(filePath, ent, filters) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err1 := handleFn(filePath, ent); err1 != nil {
|
||||
return err1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileInDirs returns the first file path in the given dirs.
|
||||
func FileInDirs(paths []string, names ...string) string {
|
||||
for _, pathDir := range paths {
|
||||
for _, name := range names {
|
||||
file := pathDir + "/" + name
|
||||
if IsFile(file) {
|
||||
return file
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Package fsutil Filesystem util functions, quick create, read and write file. eg: file and dir check, operate
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// PathSep alias of os.PathSeparator
|
||||
const PathSep = os.PathSeparator
|
||||
|
||||
// JoinPaths elements, alias of filepath.Join()
|
||||
func JoinPaths(elem ...string) string {
|
||||
return filepath.Join(elem...)
|
||||
}
|
||||
|
||||
// JoinPaths3 elements, like the filepath.Join()
|
||||
func JoinPaths3(basePath, secPath string, elems ...string) string {
|
||||
return comfunc.JoinPaths3(basePath, secPath, elems)
|
||||
}
|
||||
|
||||
// JoinSubPaths elements, like the filepath.Join()
|
||||
func JoinSubPaths(basePath string, elems ...string) string {
|
||||
return comfunc.JoinPaths2(basePath, elems)
|
||||
}
|
||||
|
||||
// SlashPath alias of filepath.ToSlash
|
||||
func SlashPath(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
|
||||
// UnixPath like of filepath.ToSlash, but always replace
|
||||
func UnixPath(path string) string {
|
||||
if !strings.ContainsRune(path, '\\') {
|
||||
return path
|
||||
}
|
||||
return strings.ReplaceAll(path, "\\", "/")
|
||||
}
|
||||
|
||||
// ToAbsPath convert path to absolute path.
|
||||
// Will expand home dir, if empty will return current work dir
|
||||
//
|
||||
// TIP: will don't check path is really exists
|
||||
func ToAbsPath(p string) string {
|
||||
// return current work dir
|
||||
if len(p) == 0 {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
return wd
|
||||
}
|
||||
|
||||
if IsAbsPath(p) {
|
||||
return p
|
||||
}
|
||||
|
||||
// expand home dir
|
||||
if p[0] == '~' {
|
||||
return comfunc.ExpandHome(p)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
return filepath.Join(wd, p)
|
||||
}
|
||||
|
||||
// Must2 ok for (any, error) result. if it has error, will panic
|
||||
func Must2(_ any, err error) { basefn.MustOK(err) }
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// DirPath get dir path from filepath, without a last name.
|
||||
// eg: "/foo/bar/baz.js" => "/foo/bar"
|
||||
func DirPath(fPath string) string { return filepath.Dir(fPath) }
|
||||
|
||||
// Dir get dir path from filepath, without a last name.
|
||||
// eg: "/foo/bar/baz.js" => "/foo/bar"
|
||||
func Dir(fPath string) string { return filepath.Dir(fPath) }
|
||||
|
||||
// PathName get file/dir name from a full path.
|
||||
// eg: "/foo/bar/baz.js" => "baz.js"
|
||||
func PathName(fPath string) string { return filepath.Base(fPath) }
|
||||
|
||||
// PathNoExt get path from full path, without ext.
|
||||
//
|
||||
// eg: path/to/main.go => "path/to/main"
|
||||
func PathNoExt(fPath string) string {
|
||||
ext := filepath.Ext(fPath)
|
||||
if el := len(ext); el > 0 {
|
||||
return fPath[:len(fPath)-el]
|
||||
}
|
||||
return fPath
|
||||
}
|
||||
|
||||
// Name get file/dir name from full path.
|
||||
//
|
||||
// eg:
|
||||
// "path/to/main.go" => "main.go"
|
||||
// "/foo/bar/baz" => "baz"
|
||||
func Name(fPath string) string {
|
||||
if fPath == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Base(fPath)
|
||||
}
|
||||
|
||||
// NameNoExt get file name from a full path, without an ext.
|
||||
//
|
||||
// eg: path/to/main.go => "main"
|
||||
func NameNoExt(fPath string) string {
|
||||
if fPath == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
fName := filepath.Base(fPath)
|
||||
if pos := strings.LastIndexByte(fName, '.'); pos > 0 {
|
||||
return fName[:pos]
|
||||
}
|
||||
return fName
|
||||
}
|
||||
|
||||
// FileExt get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => ".go"
|
||||
func FileExt(fPath string) string { return filepath.Ext(fPath) }
|
||||
|
||||
// Extname get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => "go"
|
||||
func Extname(fPath string) string {
|
||||
if ext := filepath.Ext(fPath); len(ext) > 0 {
|
||||
return ext[1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Suffix get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => ".go"
|
||||
func Suffix(fPath string) string { return filepath.Ext(fPath) }
|
||||
|
||||
// Expand will parse first `~` to user home dir path.
|
||||
func Expand(pathStr string) string { return comfunc.ExpandHome(pathStr) }
|
||||
|
||||
// ExpandHome will parse first `~` to user home dir path.
|
||||
func ExpandHome(pathStr string) string { return comfunc.ExpandHome(pathStr) }
|
||||
|
||||
// ExpandPath will parse `~` to user home dir path.
|
||||
func ExpandPath(pathStr string) string { return comfunc.ExpandHome(pathStr) }
|
||||
|
||||
// ResolvePath will parse `~` and ENV var in path
|
||||
func ResolvePath(pathStr string) string {
|
||||
pathStr = comfunc.ExpandHome(pathStr)
|
||||
// return comfunc.ParseEnvVar()
|
||||
return os.ExpandEnv(pathStr)
|
||||
}
|
||||
|
||||
// SplitPath splits path immediately following the final Separator, separating it into a directory and file name component
|
||||
func SplitPath(pathStr string) (dir, name string) { return filepath.Split(pathStr) }
|
||||
|
||||
// homeDir cache
|
||||
var _homeDir string
|
||||
|
||||
// UserHomeDir is alias of os.UserHomeDir, but ignore error.(by os.UserHomeDir)
|
||||
func UserHomeDir() string {
|
||||
if _homeDir == "" {
|
||||
_homeDir, _ = os.UserHomeDir()
|
||||
}
|
||||
return _homeDir
|
||||
}
|
||||
|
||||
// HomeDir get user home dir path.
|
||||
func HomeDir() string { return UserHomeDir() }
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
|
||||
// Will expand ~ to home dir, and join with workdir if path is relative path.
|
||||
func Realpath(pathStr string) string {
|
||||
pathStr = comfunc.ExpandHome(pathStr)
|
||||
|
||||
if !IsAbsPath(pathStr) {
|
||||
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
|
||||
}
|
||||
return path.Clean(pathStr)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
|
||||
func Realpath(pathStr string) string {
|
||||
pathStr = comfunc.ExpandHome(pathStr)
|
||||
|
||||
if !IsAbsPath(pathStr) {
|
||||
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
|
||||
}
|
||||
return filepath.Clean(pathStr)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// DetectMime detect a file mime type. alias of MimeType()
|
||||
func DetectMime(path string) string {
|
||||
return MimeType(path)
|
||||
}
|
||||
|
||||
// MimeType get file mime type name. eg "image/png"
|
||||
func MimeType(path string) (mime string) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReaderMimeType(file)
|
||||
}
|
||||
|
||||
// ReaderMimeType get the io.Reader mimeType
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// file, err := os.Open(filepath)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// mime := ReaderMimeType(file)
|
||||
func ReaderMimeType(r io.Reader) (mime string) {
|
||||
var buf [MimeSniffLen]byte
|
||||
n, _ := io.ReadFull(r, buf[:])
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return http.DetectContentType(buf[:n])
|
||||
}
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// Mkdir alias of os.MkdirAll()
|
||||
func Mkdir(dirPath string, perm fs.FileMode) error { return os.MkdirAll(dirPath, perm) }
|
||||
|
||||
// MkdirQuick with default permission 0755.
|
||||
func MkdirQuick(dirPath string) error { return EnsureDir(dirPath) }
|
||||
|
||||
// EnsureDir creates a directory if it doesn't exist
|
||||
func EnsureDir(path string) error {
|
||||
if !DirExist(path) {
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MkDirs batch makes multi dirs at once
|
||||
func MkDirs(perm fs.FileMode, dirPaths ...string) error {
|
||||
for _, dirPath := range dirPaths {
|
||||
if err := os.MkdirAll(dirPath, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MkSubDirs batch makes multi sub-dirs at once
|
||||
func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error {
|
||||
for _, dirName := range subDirs {
|
||||
dirPath := parentDir + "/" + dirName
|
||||
if err := os.MkdirAll(dirPath, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MkParentDir quickly create parent dir for a given path.
|
||||
func MkParentDir(fpath string) error {
|
||||
dirPath := filepath.Dir(fpath)
|
||||
if !IsDir(dirPath) {
|
||||
return os.MkdirAll(dirPath, 0775)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// options for open file
|
||||
// ************************************************************
|
||||
|
||||
// OpenOption for open file
|
||||
type OpenOption struct {
|
||||
// file open flag. see FsCWTFlags
|
||||
Flag int
|
||||
// file perm. see DefaultFilePerm
|
||||
Perm os.FileMode
|
||||
}
|
||||
|
||||
// OpenOptionFunc for open/write file
|
||||
type OpenOptionFunc func(*OpenOption)
|
||||
|
||||
// NewOpenOption create a new OpenOption instance
|
||||
//
|
||||
// Defaults:
|
||||
// - open flags: FsCWTFlags (override write)
|
||||
// - file Perm: DefaultFilePerm
|
||||
func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption {
|
||||
opt := &OpenOption{
|
||||
Flag: FsCWTFlags,
|
||||
Perm: DefaultFilePerm,
|
||||
}
|
||||
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// OpenOptOrNew create a new OpenOption instance if opt is nil
|
||||
func OpenOptOrNew(opt *OpenOption) *OpenOption {
|
||||
if opt == nil {
|
||||
return NewOpenOption()
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithFlag set file open flag
|
||||
func WithFlag(flag int) OpenOptionFunc {
|
||||
return func(opt *OpenOption) {
|
||||
opt.Flag = flag
|
||||
}
|
||||
}
|
||||
|
||||
// WithPerm set file perm
|
||||
func WithPerm(perm os.FileMode) OpenOptionFunc {
|
||||
return func(opt *OpenOption) {
|
||||
opt.Perm = perm
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// open/create files
|
||||
// ************************************************************
|
||||
|
||||
// some commonly flag consts for open file.
|
||||
const (
|
||||
FsCWAFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // create, append write-only
|
||||
FsCWTFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC // create, override write-only
|
||||
FsCWFlags = os.O_CREATE | os.O_WRONLY // create, write-only
|
||||
FsRWFlags = os.O_RDWR // read-write, dont create.
|
||||
FsRFlags = os.O_RDONLY // read-only
|
||||
)
|
||||
|
||||
// OpenFile like os.OpenFile, but will auto create dir.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666)
|
||||
func OpenFile(filePath string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
fileDir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(filePath, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// MustOpenFile like os.OpenFile, but will auto create dir.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// file := MustOpenFile("path/to/file.txt", FsCWFlags, 0666)
|
||||
func MustOpenFile(filePath string, flag int, perm os.FileMode) *os.File {
|
||||
file, err := OpenFile(filePath, flag, perm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// QuickOpenFile like os.OpenFile, open for append write. if not exists, will create it.
|
||||
//
|
||||
// Alias of OpenAppendFile()
|
||||
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) {
|
||||
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
|
||||
return OpenFile(filepath, flag, DefaultFilePerm)
|
||||
}
|
||||
|
||||
// OpenAppendFile like os.OpenFile, open for append write. if not exists, will create it.
|
||||
func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
|
||||
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
|
||||
return OpenFile(filepath, FsCWAFlags, perm)
|
||||
}
|
||||
|
||||
// OpenTruncFile like os.OpenFile, open for override write. if not exists, will create it.
|
||||
func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
|
||||
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
|
||||
return OpenFile(filepath, FsCWTFlags, perm)
|
||||
}
|
||||
|
||||
// OpenReadFile like os.OpenFile, open file for read contents
|
||||
func OpenReadFile(filepath string) (*os.File, error) {
|
||||
return os.OpenFile(filepath, FsRFlags, OnlyReadFilePerm)
|
||||
}
|
||||
|
||||
// CreateFile create file if not exists
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// CreateFile("path/to/file.txt", 0664, 0666)
|
||||
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) {
|
||||
dirPath := filepath.Dir(fpath)
|
||||
if !IsDir(dirPath) {
|
||||
err := os.MkdirAll(dirPath, dirPerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
|
||||
return os.OpenFile(fpath, flag, filePerm)
|
||||
}
|
||||
|
||||
// MustCreateFile create file, will panic on error
|
||||
func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File {
|
||||
file, err := CreateFile(filePath, filePerm, dirPerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// remove files
|
||||
// ************************************************************
|
||||
|
||||
// alias methods
|
||||
var (
|
||||
// MustRm removes the named file or (empty) directory.
|
||||
MustRm = MustRemove
|
||||
// QuietRm removes the named file or (empty) directory.
|
||||
QuietRm = QuietRemove
|
||||
)
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
func Remove(fPath string) error {
|
||||
return os.Remove(fPath)
|
||||
}
|
||||
|
||||
// MustRemove removes the named file or (empty) directory.
|
||||
// NOTICE: will panic on error
|
||||
func MustRemove(fPath string) {
|
||||
if err := os.Remove(fPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// QuietRemove removes the named file or (empty) directory.
|
||||
//
|
||||
// NOTICE: will ignore error
|
||||
func QuietRemove(fPath string) { _ = os.Remove(fPath) }
|
||||
|
||||
// SafeRemoveAll removes path and any children it contains. will ignore error
|
||||
func SafeRemoveAll(path string) {
|
||||
_ = os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// RmIfExist removes the named file or (empty) directory on existing.
|
||||
func RmIfExist(fPath string) error { return DeleteIfExist(fPath) }
|
||||
|
||||
// DeleteIfExist removes the named file or (empty) directory on existing.
|
||||
func DeleteIfExist(fPath string) error {
|
||||
if PathExists(fPath) {
|
||||
return os.Remove(fPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RmFileIfExist removes the named file on existing.
|
||||
func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) }
|
||||
|
||||
// DeleteIfFileExist removes the named file on existing.
|
||||
func DeleteIfFileExist(fPath string) error {
|
||||
if IsFile(fPath) {
|
||||
return os.Remove(fPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSub removes all sub files and dirs of dirPath, but not remove dirPath.
|
||||
func RemoveSub(dirPath string, fns ...FilterFunc) error {
|
||||
return FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error {
|
||||
if ent.IsDir() {
|
||||
if err := RemoveSub(fPath, fns...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// skip rm not empty subdir
|
||||
if !IsEmptyDir(fPath) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return os.Remove(fPath)
|
||||
}, fns...)
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// other operates
|
||||
// ************************************************************
|
||||
|
||||
// Unzip a zip archive
|
||||
// from https://blog.csdn.net/wangshubo1989/article/details/71743374
|
||||
func Unzip(archive, targetDir string) (err error) {
|
||||
reader, err := zip.OpenReader(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(targetDir, DefaultDirPerm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range reader.File {
|
||||
if strings.Contains(file.Name, "..") {
|
||||
return fmt.Errorf("illegal file path in zip: %v", file.Name)
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(targetDir, file.Name)
|
||||
|
||||
if file.FileInfo().IsDir() {
|
||||
err = os.MkdirAll(fullPath, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
_ = fileReader.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(targetFile, fileReader)
|
||||
|
||||
// close all
|
||||
_ = fileReader.Close()
|
||||
targetFile.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// NewIOReader instance by input file path or io.Reader
|
||||
func NewIOReader(in any) (r io.Reader, err error) {
|
||||
switch typIn := in.(type) {
|
||||
case string: // as file path
|
||||
return OpenReadFile(typIn)
|
||||
case io.Reader:
|
||||
return typIn, nil
|
||||
}
|
||||
return nil, errors.New("invalid input type, allow: string, io.Reader")
|
||||
}
|
||||
|
||||
// DiscardReader anything from the reader
|
||||
func DiscardReader(src io.Reader) {
|
||||
_, _ = io.Copy(io.Discard, src)
|
||||
}
|
||||
|
||||
// ReadFile read file contents, will panic on error
|
||||
func ReadFile(filePath string) []byte { return MustReadFile(filePath) }
|
||||
|
||||
// MustReadFile read file contents, will panic on error
|
||||
func MustReadFile(filePath string) []byte {
|
||||
bs, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// ReadReader read contents from io.Reader, will panic on error
|
||||
func ReadReader(r io.Reader) []byte { return MustReadReader(r) }
|
||||
|
||||
// MustReadReader read contents from io.Reader, will panic on error
|
||||
func MustReadReader(r io.Reader) []byte {
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// ReadString read contents from path or io.Reader, will panic on in type error
|
||||
func ReadString(in any) string { return string(GetContents(in)) }
|
||||
|
||||
// ReadStringOrErr read contents from path or io.Reader, will panic on in type error
|
||||
func ReadStringOrErr(in any) (string, error) {
|
||||
r, err := NewIOReader(in)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// ReadAll read contents from path or io.Reader, will panic on in type error
|
||||
func ReadAll(in any) []byte { return MustRead(in) }
|
||||
|
||||
// GetContents read contents from path or io.Reader, will panic on in type error
|
||||
func GetContents(in any) []byte { return MustRead(in) }
|
||||
|
||||
// MustRead read contents from path or io.Reader, will panic on in type error
|
||||
func MustRead(in any) []byte { return basefn.Must(ReadOrErr(in)) }
|
||||
|
||||
// ReadOrErr read contents from path or io.Reader, will panic on in type error
|
||||
func ReadOrErr(in any) ([]byte, error) {
|
||||
r, err := NewIOReader(in)
|
||||
defer func() {
|
||||
if r != nil {
|
||||
if file, ok := r.(*os.File); ok {
|
||||
err = file.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
// ReadExistFile read file contents if existed, will panic on error
|
||||
func ReadExistFile(filePath string) []byte {
|
||||
if IsFile(filePath) {
|
||||
bs, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TextScanner from filepath or io.Reader, will panic on in type error.
|
||||
// Will scan parse text to tokens: Ident, Int, Float, Char, String, RawString, Comment, etc.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := fsutil.TextScanner("/path/to/file")
|
||||
// for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
|
||||
// fmt.Printf("%s: %s\n", s.Position, s.TokenText())
|
||||
// }
|
||||
func TextScanner(in any) *scanner.Scanner {
|
||||
var s scanner.Scanner
|
||||
r, err := NewIOReader(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s.Init(r)
|
||||
s.Filename = "text-scanner"
|
||||
return &s
|
||||
}
|
||||
|
||||
// LineScanner create from filepath or io.Reader, will panic on in type error.
|
||||
// Will scan and parse text to lines.
|
||||
//
|
||||
// s := fsutil.LineScanner("/path/to/file")
|
||||
// for s.Scan() {
|
||||
// fmt.Println(s.Text())
|
||||
// }
|
||||
func LineScanner(in any) *bufio.Scanner {
|
||||
r, err := NewIOReader(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bufio.NewScanner(r)
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// ************************************************************
|
||||
// temp file or dir
|
||||
// ************************************************************
|
||||
|
||||
// OSTempFile create a temp file on os.TempDir()
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.OSTempFile("example.*.txt")
|
||||
func OSTempFile(pattern string) (*os.File, error) {
|
||||
return os.CreateTemp(os.TempDir(), pattern)
|
||||
}
|
||||
|
||||
// TempFile is like os.CreateTemp, but can custom temp dir.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // create temp file on os.TempDir()
|
||||
// fsutil.TempFile("", "example.*.txt")
|
||||
// // create temp file on "testdata" dir
|
||||
// fsutil.TempFile("testdata", "example.*.txt")
|
||||
func TempFile(dir, pattern string) (*os.File, error) {
|
||||
return os.CreateTemp(dir, pattern)
|
||||
}
|
||||
|
||||
// OSTempDir creates a new temp dir on os.TempDir and return the temp dir path
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.OSTempDir("example.*")
|
||||
func OSTempDir(pattern string) (string, error) {
|
||||
return os.MkdirTemp(os.TempDir(), pattern)
|
||||
}
|
||||
|
||||
// TempDir creates a new temp dir and return the temp dir path
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.TempDir("", "example.*")
|
||||
// fsutil.TempDir("testdata", "example.*")
|
||||
func TempDir(dir, pattern string) (string, error) {
|
||||
return os.MkdirTemp(dir, pattern)
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// write, copy files
|
||||
// ************************************************************
|
||||
|
||||
// MustSave create file and write contents to file, panic on error.
|
||||
//
|
||||
// - data type allow: string, []byte, io.Reader
|
||||
//
|
||||
// default option see NewOpenOption()
|
||||
func MustSave(filePath string, data any, optFns ...OpenOptionFunc) {
|
||||
basefn.MustOK(SaveFile(filePath, data, optFns...))
|
||||
}
|
||||
|
||||
// SaveFile create file and write contents to file. will auto create dir.
|
||||
//
|
||||
// - data type allow: string, []byte, io.Reader
|
||||
//
|
||||
// default option see NewOpenOption()
|
||||
func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error {
|
||||
opt := NewOpenOption(optFns...)
|
||||
return WriteFile(filePath, data, opt.Perm, opt.Flag)
|
||||
}
|
||||
|
||||
// WriteData Quick write any data to file, alias of PutContents
|
||||
func WriteData(filePath string, data any, fileFlag ...int) (int, error) {
|
||||
return PutContents(filePath, data, fileFlag...)
|
||||
}
|
||||
|
||||
// PutContents create file and write contents to file at once. Will auto create dir
|
||||
//
|
||||
// data type allows: string, []byte, io.Reader
|
||||
//
|
||||
// Tip: file flag default is FsCWTFlags (override write)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.PutContents(filePath, contents, fsutil.FsCWAFlags) // append write
|
||||
// fsutil.Must2(fsutil.PutContents(filePath, contents)) // panic on error
|
||||
func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
|
||||
f, err := QuickOpenFile(filePath, basefn.FirstOr(fileFlag, FsCWTFlags))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return WriteOSFile(f, data)
|
||||
}
|
||||
|
||||
// WriteFile create file and write contents to file, can set perm for a file.
|
||||
//
|
||||
// data type allows: string, []byte, io.Reader
|
||||
//
|
||||
// Tip: file flag default is FsCWTFlags (override write)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.WriteFile(filePath, contents, fsutil.DefaultFilePerm, fsutil.FsCWAFlags)
|
||||
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error {
|
||||
flag := basefn.FirstOr(fileFlag, FsCWTFlags)
|
||||
f, err := OpenFile(filePath, flag, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = WriteOSFile(f, data)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteOSFile write data to give os.File, then close file.
|
||||
//
|
||||
// data type allows: string, []byte, io.Reader
|
||||
func WriteOSFile(f *os.File, data any) (n int, err error) {
|
||||
switch typData := data.(type) {
|
||||
case []byte:
|
||||
n, err = f.Write(typData)
|
||||
case string:
|
||||
n, err = f.WriteString(typData)
|
||||
case io.Reader: // eg: buffer
|
||||
var n64 int64
|
||||
n64, err = io.Copy(f, typData)
|
||||
n = int(n64)
|
||||
default:
|
||||
_ = f.Close()
|
||||
panic("WriteFile: data type only allow: []byte, string, io.Reader")
|
||||
}
|
||||
|
||||
if err1 := f.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// CopyFile copy a file to another file path.
|
||||
func CopyFile(srcPath, dstPath string) error {
|
||||
srcFile, err := os.OpenFile(srcPath, FsRFlags, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
// create and open file
|
||||
dstFile, err := QuickOpenFile(dstPath, FsCWTFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
_, err = io.Copy(dstFile, srcFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// MustCopyFile copy file to another path.
|
||||
func MustCopyFile(srcPath, dstPath string) {
|
||||
err := CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateContents read file contents, call handleFn(contents) handle, then write updated contents to file
|
||||
func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error {
|
||||
osFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer osFile.Close()
|
||||
|
||||
// read file contents
|
||||
if bs, err1 := io.ReadAll(osFile); err1 == nil {
|
||||
bs = handleFn(bs)
|
||||
_, err = osFile.Write(bs)
|
||||
} else {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateSymlink creates a symbolic link
|
||||
func CreateSymlink(target, linkPath string) error {
|
||||
// Check if the link already exists
|
||||
if IsFile(linkPath) {
|
||||
// Remove existing link/file
|
||||
if err := os.Remove(linkPath); err != nil {
|
||||
return fmt.Errorf("failed to remove existing symlink: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return os.Symlink(target, linkPath)
|
||||
}
|
||||
Reference in New Issue
Block a user