Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+138
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}