Skip to content

Commit

Permalink
✨ feat: fsutil - add UpdateContents() and add more unit test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Jul 14, 2023
1 parent 4affd6a commit 73b905b
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 65 deletions.
35 changes: 0 additions & 35 deletions fsutil/fsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,13 @@
package fsutil

import (
"io"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/gookit/goutil/internal/comfunc"
)

// DetectMime detect 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])
}

// JoinPaths elements, alias of filepath.Join()
func JoinPaths(elem ...string) string {
return filepath.Join(elem...)
Expand Down
23 changes: 0 additions & 23 deletions fsutil/fsutil_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fsutil_test

import (
"bytes"
"io/fs"
"testing"

Expand All @@ -19,28 +18,6 @@ func TestMain(m *testing.M) {
m.Run()
}

func TestMimeType(t *testing.T) {
assert.Eq(t, "", fsutil.DetectMime(""))
assert.Eq(t, "", fsutil.MimeType("not-exist"))
assert.Eq(t, "image/jpeg", fsutil.MimeType("testdata/test.jpg"))

buf := new(bytes.Buffer)
buf.Write([]byte("\xFF\xD8\xFF"))
assert.Eq(t, "image/jpeg", fsutil.ReaderMimeType(buf))
buf.Reset()

buf.Write([]byte("text"))
assert.Eq(t, "text/plain; charset=utf-8", fsutil.ReaderMimeType(buf))
buf.Reset()

buf.Write([]byte(""))
assert.Eq(t, "", fsutil.ReaderMimeType(buf))
buf.Reset()

assert.True(t, fsutil.IsImageFile("testdata/test.jpg"))
assert.False(t, fsutil.IsImageFile("testdata/not-exists"))
}

func TestTempDir(t *testing.T) {
dir, err := fsutil.TempDir("testdata", "temp.*")
assert.NoErr(t, err)
Expand Down
86 changes: 86 additions & 0 deletions fsutil/mime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package fsutil

import (
"io"
"mime"
"net/http"
"os"
"strings"
)

// refer https://www.freeformatter.com/mime-types-list.html
var builtinMimeTypes = map[string]string{
".xml": "application/xml",
}

func init() {
// register builtin mime types
for ext, mimeTyp := range builtinMimeTypes {
_ = mime.AddExtensionType(ext, mimeTyp)
}
}

// ExtsByMimeType returns the extensions known to be associated with the MIME type typ.
//
// returns like: [".html"] [".jpg", ".jpeg]
func ExtsByMimeType(mimeTyp string) ([]string, error) {
return mime.ExtensionsByType(mimeTyp)
}

// ExtByMimeType returns an extension known to be associated with the MIME type typ.
//
// allow with a default ext on not found.
func ExtByMimeType(mimeTyp, defExt string) (string, error) {
ss, err := mime.ExtensionsByType(mimeTyp)
if err != nil {
if defExt != "" {
return defExt, nil
}
return "", err
}

if len(ss) == 0 {
return defExt, nil
}

// always return the best match
for _, ext := range ss {
if strings.Index(mimeTyp, ext[1:]) > 0 {
return ext, nil
}
}
return ss[0], nil
}

// DetectMime detect 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])
}
69 changes: 69 additions & 0 deletions fsutil/mime_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package fsutil_test

import (
"bytes"
"testing"

"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
)

func TestExtByMimeType(t *testing.T) {
tests := []struct {
mime string
ok bool
exts []string
ext string
}{
{"application/json", true, []string{".json"}, ".json"},
{"application/xml", true, []string{".xml"}, ".xml"},
{"application/x-yaml", true, []string{".yaml", ".yml"}, ".yaml"},
{"text/plain; charset=utf-8", true, []string{".asc", ".txt"}, ".txt"},
}

for _, tt := range tests {
es, err := fsutil.ExtsByMimeType(tt.mime)
if tt.ok {
assert.NoErr(t, err)
assert.Eq(t, tt.exts, es)
} else {
assert.Err(t, err)
assert.Empty(t, es)
}

ext, err := fsutil.ExtByMimeType(tt.mime, "")
if tt.ok {
assert.NoErr(t, err)
assert.Eq(t, tt.ext, ext)
} else {
assert.Err(t, err)
assert.Empty(t, ext)
}
}

ext, err := fsutil.ExtByMimeType("application/not-exists", "default")
assert.NoErr(t, err)
assert.Eq(t, "default", ext)
}

func TestMimeType(t *testing.T) {
assert.Eq(t, "", fsutil.DetectMime(""))
assert.Eq(t, "", fsutil.MimeType("not-exist"))
assert.Eq(t, "image/jpeg", fsutil.MimeType("testdata/test.jpg"))

buf := new(bytes.Buffer)
buf.Write([]byte("\xFF\xD8\xFF"))
assert.Eq(t, "image/jpeg", fsutil.ReaderMimeType(buf))
buf.Reset()

buf.Write([]byte("text"))
assert.Eq(t, "text/plain; charset=utf-8", fsutil.ReaderMimeType(buf))
buf.Reset()

buf.Write([]byte(""))
assert.Eq(t, "", fsutil.ReaderMimeType(buf))
buf.Reset()

assert.True(t, fsutil.IsImageFile("testdata/test.jpg"))
assert.False(t, fsutil.IsImageFile("testdata/not-exists"))
}
13 changes: 7 additions & 6 deletions fsutil/operate.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ func WithPerm(perm os.FileMode) OpenOptionFunc {
// open/create files
// ************************************************************

// some commonly flag consts for open file
// 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
)

Expand All @@ -119,13 +120,13 @@ const (
// Usage:
//
// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666)
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := path.Dir(filepath)
func OpenFile(filePath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := path.Dir(filePath)
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
return nil, err
}

file, err := os.OpenFile(filepath, flag, perm)
file, err := os.OpenFile(filePath, flag, perm)
if err != nil {
return nil, err
}
Expand All @@ -137,8 +138,8 @@ func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
// 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)
func MustOpenFile(filePath string, flag int, perm os.FileMode) *os.File {
file, err := OpenFile(filePath, flag, perm)
if err != nil {
panic(err)
}
Expand Down
12 changes: 11 additions & 1 deletion fsutil/operate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fsutil_test

import (
"os"
"strings"
"testing"

"github.com/gookit/goutil/envutil"
Expand Down Expand Up @@ -99,9 +100,18 @@ func TestQuickOpenFile(t *testing.T) {
}

func TestMustOpenFile(t *testing.T) {
fpath := "testdata/must-open-file.txt"

assert.Panics(t, func() {
fsutil.MustOpenFile("/path-not-exists", os.O_RDONLY, 0666)
fsutil.MustOpenFile(fpath, os.O_RDONLY, 0666)
})

_, err := fsutil.PutContents(fpath, strings.NewReader("must-open-file"))
assert.NoErr(t, err)

of := fsutil.MustOpenFile(fpath, fsutil.FsRWFlags, 0600)
assert.Eq(t, "must-open-file", fsutil.ReadString(of))
assert.NoErr(t, of.Close())
}

func TestOpenAppendFile(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions fsutil/opread_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/goutil/testutil/fakeobj"
)

func TestDiscardReader(t *testing.T) {
Expand All @@ -22,7 +23,16 @@ func TestDiscardReader(t *testing.T) {

assert.Empty(t, fsutil.ReadReader(sr))
assert.Empty(t, fsutil.ReadAll(sr))
}

func TestReadReader(t *testing.T) {
fr := fakeobj.NewReader()
assert.Empty(t, fsutil.ReadReader(fr))

assert.Panics(t, func() {
fr.ErrOnRead = true
fsutil.ReadReader(fr)
})
}

func TestGetContents(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions fsutil/opwrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,21 @@ func MustCopyFile(srcPath, dstPath string) {
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, 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
}
10 changes: 10 additions & 0 deletions fsutil/opwrite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func TestMustCopyFile(t *testing.T) {
assert.Eq(t, []byte("hello"), fsutil.GetContents(dstPath))
assert.Eq(t, "hello", fsutil.ReadString(dstPath))

assert.Panics(t, func() {
fsutil.MustCopyFile("testdata/not-exists-file", "")
})

str, err := fsutil.ReadStringOrErr(dstPath)
assert.NoErr(t, err)
assert.Eq(t, "hello", str)
Expand Down Expand Up @@ -61,3 +65,9 @@ func TestMustSave(t *testing.T) {
fsutil.MustSave(testFile, []string{"hello"})
})
}

func TestUpdateContents(t *testing.T) {
err := fsutil.UpdateContents("testdata/not-exists-file", nil)
assert.Err(t, err)

}

0 comments on commit 73b905b

Please sign in to comment.