mirror of https://github.com/spf13/afero.git
Adding Afero/utils
Portions ported from Go StdLib ioutils Portions ported from Hugo's helpers
This commit is contained in:
parent
f50d862134
commit
3a517135a5
|
@ -0,0 +1,232 @@
|
|||
// Copyright ©2015 The Go Authors
|
||||
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// byName implements sort.Interface.
|
||||
type byName []os.FileInfo
|
||||
|
||||
func (f byName) Len() int { return len(f) }
|
||||
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of sorted directory entries.
|
||||
func (a AferoUtilFS) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
return ReadDir(dirname, a.fs)
|
||||
}
|
||||
|
||||
func ReadDir(dirname string, fs afero.Fs) ([]os.FileInfo, error) {
|
||||
f, err := fs.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Sort(byName(list))
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ReadFile reads the file named by filename and returns the contents.
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadFile
|
||||
// reads the whole file, it does not treat an EOF from Read as an error
|
||||
// to be reported.
|
||||
func (a AferoUtilFS) ReadFile(filename string) ([]byte, error) {
|
||||
return ReadFile(filename, a.fs)
|
||||
}
|
||||
|
||||
func ReadFile(filename string, fs afero.Fs) ([]byte, error) {
|
||||
f, err := fs.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
// It's a good but not certain bet that FileInfo will tell us exactly how much to
|
||||
// read, so let's try it but be prepared for the answer to be wrong.
|
||||
var n int64
|
||||
|
||||
if fi, err := f.Stat(); err == nil {
|
||||
// Don't preallocate a huge buffer, just in case.
|
||||
if size := fi.Size(); size < 1e9 {
|
||||
n = size
|
||||
}
|
||||
}
|
||||
// As initial capacity for readAll, use n + a little extra in case Size is zero,
|
||||
// and to avoid another allocation after Read has filled the buffer. The readAll
|
||||
// call will read into its allocated internal buffer cheaply. If the size was
|
||||
// wrong, we'll either waste some space off the end or reallocate as needed, but
|
||||
// in the overwhelmingly common case we'll get it just right.
|
||||
return readAll(f, n+bytes.MinRead)
|
||||
}
|
||||
|
||||
// readAll reads from r until an error or EOF and returns the data it read
|
||||
// from the internal buffer allocated with a specified capacity.
|
||||
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, capacity))
|
||||
// If the buffer overflows, we will get bytes.ErrTooLarge.
|
||||
// Return that as an error. Any other panic remains.
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
|
||||
err = panicErr
|
||||
} else {
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
_, err = buf.ReadFrom(r)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// ReadAll reads from r until an error or EOF and returns the data it read.
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
||||
// defined to read from src until EOF, it does not treat an EOF from Read
|
||||
// as an error to be reported.
|
||||
func ReadAll(r io.Reader) ([]byte, error) {
|
||||
return readAll(r, bytes.MinRead)
|
||||
}
|
||||
|
||||
// WriteFile writes data to a file named by filename.
|
||||
// If the file does not exist, WriteFile creates it with permissions perm;
|
||||
// otherwise WriteFile truncates it before writing.
|
||||
func (a AferoUtilFS) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
return WriteFile(filename, data, perm, a.fs)
|
||||
}
|
||||
|
||||
func WriteFile(filename string, data []byte, perm os.FileMode, fs afero.Fs) error {
|
||||
f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Random number state.
|
||||
// We generate random temporary file names so that there's a good
|
||||
// chance the file doesn't exist yet - keeps the number of tries in
|
||||
// TempFile to a minimum.
|
||||
var rand uint32
|
||||
var randmu sync.Mutex
|
||||
|
||||
func reseed() uint32 {
|
||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||
}
|
||||
|
||||
func nextSuffix() string {
|
||||
randmu.Lock()
|
||||
r := rand
|
||||
if r == 0 {
|
||||
r = reseed()
|
||||
}
|
||||
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||
rand = r
|
||||
randmu.Unlock()
|
||||
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||
}
|
||||
|
||||
// TempFile creates a new temporary file in the directory dir
|
||||
// with a name beginning with prefix, opens the file for reading
|
||||
// and writing, and returns the resulting *afero.File.
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func (a AferoUtilFS) TempFile(dir, prefix string) (f afero.File, err error) {
|
||||
return TempFile(dir, prefix, a.fs)
|
||||
}
|
||||
|
||||
func TempFile(dir, prefix string, fs afero.Fs) (f afero.File, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
name := filepath.Join(dir, prefix+nextSuffix())
|
||||
f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TempDir creates a new temporary directory in the directory dir
|
||||
// with a name beginning with prefix and returns the path of the
|
||||
// new directory. If dir is the empty string, TempDir uses the
|
||||
// default directory for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempDir simultaneously
|
||||
// will not choose the same directory. It is the caller's responsibility
|
||||
// to remove the directory when no longer needed.
|
||||
func (a AferoUtilFS) TempDir(dir, prefix string) (name string, err error) {
|
||||
return TempDir(dir, prefix, a.fs)
|
||||
}
|
||||
func TempDir(dir, prefix string, fs afero.Fs) (name string, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
try := filepath.Join(dir, prefix+nextSuffix())
|
||||
err = fs.Mkdir(try, 0700)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
name = try
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// ©2015 The Go Authors
|
||||
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func checkSize(t *testing.T, path string, size int64) {
|
||||
dir, err := testFS.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
|
||||
}
|
||||
if dir.Size() != size {
|
||||
t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
testFS = &afero.MemMapFs{}
|
||||
fsutil := &AferoUtilFS{fs: testFS}
|
||||
|
||||
testFS.Create("this_exists.go")
|
||||
filename := "rumpelstilzchen"
|
||||
contents, err := fsutil.ReadFile(filename)
|
||||
if err == nil {
|
||||
t.Fatalf("ReadFile %s: error expected, none found", filename)
|
||||
}
|
||||
|
||||
filename = "this_exists.go"
|
||||
contents, err = fsutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||
}
|
||||
|
||||
checkSize(t, filename, int64(len(contents)))
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
testFS = &afero.MemMapFs{}
|
||||
fsutil := &AferoUtilFS{fs: testFS}
|
||||
f, err := fsutil.TempFile("", "ioutil-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
filename := f.Name()
|
||||
data := "Programming today is a race between software engineers striving to " +
|
||||
"build bigger and better idiot-proof programs, and the Universe trying " +
|
||||
"to produce bigger and better idiots. So far, the Universe is winning."
|
||||
|
||||
if err := fsutil.WriteFile(filename, []byte(data), 0644); err != nil {
|
||||
t.Fatalf("WriteFile %s: %v", filename, err)
|
||||
}
|
||||
|
||||
contents, err := fsutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||
}
|
||||
|
||||
if string(contents) != data {
|
||||
t.Fatalf("contents = %q\nexpected = %q", string(contents), data)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
f.Close()
|
||||
testFS.Remove(filename) // ignore error
|
||||
}
|
||||
|
||||
func TestReadDir(t *testing.T) {
|
||||
testFS = &afero.MemMapFs{}
|
||||
testFS.Mkdir("/i-am-a-dir", 0777)
|
||||
testFS.Create("/this_exists.go")
|
||||
dirname := "rumpelstilzchen"
|
||||
_, err := ReadDir(dirname, testFS)
|
||||
if err == nil {
|
||||
t.Fatalf("ReadDir %s: error expected, none found", dirname)
|
||||
}
|
||||
|
||||
dirname = ".."
|
||||
list, err := ReadDir(dirname, testFS)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadDir %s: %v", dirname, err)
|
||||
}
|
||||
|
||||
foundFile := false
|
||||
foundSubDir := false
|
||||
for _, dir := range list {
|
||||
switch {
|
||||
case !dir.IsDir() && dir.Name() == "this_exists.go":
|
||||
foundFile = true
|
||||
case dir.IsDir() && dir.Name() == "i-am-a-dir":
|
||||
foundSubDir = true
|
||||
}
|
||||
}
|
||||
if !foundFile {
|
||||
t.Fatalf("ReadDir %s: this_exists.go file not found", dirname)
|
||||
}
|
||||
if !foundSubDir {
|
||||
t.Fatalf("ReadDir %s: i-am-a-dir directory not found", dirname)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||
// Portions Copyright ©2015 The Hugo Authors
|
||||
//
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
type AferoUtilFS struct {
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
// Filepath separator defined by os.Separator.
|
||||
const FilePathSeparator = string(filepath.Separator)
|
||||
|
||||
// Takes a reader and a path and writes the content
|
||||
func (a AferoUtilFS) WriteReader(path string, r io.Reader) (err error) {
|
||||
return WriteReader(path, r, a.fs)
|
||||
}
|
||||
|
||||
func WriteReader(path string, r io.Reader, fs afero.Fs) (err error) {
|
||||
dir, _ := filepath.Split(path)
|
||||
ospath := filepath.FromSlash(dir)
|
||||
|
||||
if ospath != "" {
|
||||
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||
if err != nil {
|
||||
if err != os.ErrExist {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file, err := fs.Create(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Same as WriteReader but checks to see if file/directory already exists.
|
||||
func (a AferoUtilFS) SafeWriteReader(path string, r io.Reader) (err error) {
|
||||
return SafeWriteReader(path, r, a.fs)
|
||||
}
|
||||
|
||||
func SafeWriteReader(path string, r io.Reader, fs afero.Fs) (err error) {
|
||||
dir, _ := filepath.Split(path)
|
||||
ospath := filepath.FromSlash(dir)
|
||||
|
||||
if ospath != "" {
|
||||
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
exists, err := Exists(path, fs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("%v already exists", path)
|
||||
}
|
||||
|
||||
file, err := fs.Create(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, r)
|
||||
return
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) GetTempDir(subPath string) string {
|
||||
return GetTempDir(subPath, a.fs)
|
||||
}
|
||||
|
||||
// GetTempDir returns the default temp directory with trailing slash
|
||||
// if subPath is not empty then it will be created recursively with mode 777 rwx rwx rwx
|
||||
func GetTempDir(subPath string, fs afero.Fs) string {
|
||||
addSlash := func(p string) string {
|
||||
if FilePathSeparator != p[len(p)-1:] {
|
||||
p = p + FilePathSeparator
|
||||
}
|
||||
return p
|
||||
}
|
||||
dir := addSlash(os.TempDir())
|
||||
|
||||
if subPath != "" {
|
||||
// preserve windows backslash :-(
|
||||
if FilePathSeparator == "\\" {
|
||||
subPath = strings.Replace(subPath, "\\", "____", -1)
|
||||
}
|
||||
dir = dir + UnicodeSanitize((subPath))
|
||||
if FilePathSeparator == "\\" {
|
||||
dir = strings.Replace(dir, "____", "\\", -1)
|
||||
}
|
||||
|
||||
if exists, _ := Exists(dir, fs); exists {
|
||||
return addSlash(dir)
|
||||
}
|
||||
|
||||
err := fs.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dir = addSlash(dir)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// Rewrite string to remove non-standard path characters
|
||||
func UnicodeSanitize(s string) string {
|
||||
source := []rune(s)
|
||||
target := make([]rune, 0, len(source))
|
||||
|
||||
for _, r := range source {
|
||||
if unicode.IsLetter(r) ||
|
||||
unicode.IsDigit(r) ||
|
||||
unicode.IsMark(r) ||
|
||||
r == '.' ||
|
||||
r == '/' ||
|
||||
r == '\\' ||
|
||||
r == '_' ||
|
||||
r == '-' ||
|
||||
r == '%' ||
|
||||
r == ' ' ||
|
||||
r == '#' {
|
||||
target = append(target, r)
|
||||
}
|
||||
}
|
||||
|
||||
return string(target)
|
||||
}
|
||||
|
||||
// Transform characters with accents into plan forms
|
||||
func NeuterAccents(s string) string {
|
||||
t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
|
||||
result, _, _ := transform.String(t, string(s))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isMn(r rune) bool {
|
||||
return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) FileContainsBytes(filename string, subslice []byte) (bool, error) {
|
||||
return FileContainsBytes(filename, subslice, a.fs)
|
||||
}
|
||||
|
||||
// Check if a file contains a specified string.
|
||||
func FileContainsBytes(filename string, subslice []byte, fs afero.Fs) (bool, error) {
|
||||
f, err := fs.Open(filename)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return readerContains(f, subslice), nil
|
||||
}
|
||||
|
||||
// readerContains reports whether subslice is within r.
|
||||
func readerContains(r io.Reader, subslice []byte) bool {
|
||||
|
||||
if r == nil || len(subslice) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
bufflen := len(subslice) * 4
|
||||
halflen := bufflen / 2
|
||||
buff := make([]byte, bufflen)
|
||||
var err error
|
||||
var n, i int
|
||||
|
||||
for {
|
||||
i++
|
||||
if i == 1 {
|
||||
n, err = io.ReadAtLeast(r, buff[:halflen], halflen)
|
||||
} else {
|
||||
if i != 2 {
|
||||
// shift left to catch overlapping matches
|
||||
copy(buff[:], buff[halflen:])
|
||||
}
|
||||
n, err = io.ReadAtLeast(r, buff[halflen:], halflen)
|
||||
}
|
||||
|
||||
if n > 0 && bytes.Contains(buff, subslice) {
|
||||
return true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) DirExists(path string) (bool, error) {
|
||||
return DirExists(path, a.fs)
|
||||
}
|
||||
|
||||
// DirExists checks if a path exists and is a directory.
|
||||
func DirExists(path string, fs afero.Fs) (bool, error) {
|
||||
fi, err := fs.Stat(path)
|
||||
if err == nil && fi.IsDir() {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) IsDir(path string) (bool, error) {
|
||||
return IsDir(path, a.fs)
|
||||
}
|
||||
|
||||
// IsDir checks if a given path is a directory.
|
||||
func IsDir(path string, fs afero.Fs) (bool, error) {
|
||||
fi, err := fs.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fi.IsDir(), nil
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) IsEmpty(path string) (bool, error) {
|
||||
return IsEmpty(path, a.fs)
|
||||
}
|
||||
|
||||
// IsEmpty checks if a given file or directory is empty.
|
||||
func IsEmpty(path string, fs afero.Fs) (bool, error) {
|
||||
if b, _ := Exists(path, fs); !b {
|
||||
return false, fmt.Errorf("%q path does not exist", path)
|
||||
}
|
||||
fi, err := fs.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
f, err := fs.Open(path)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
list, err := f.Readdir(-1)
|
||||
return len(list) == 0, nil
|
||||
}
|
||||
return fi.Size() == 0, nil
|
||||
}
|
||||
|
||||
func (a AferoUtilFS) Exists(path string) (bool, error) {
|
||||
return Exists(path, a.fs)
|
||||
}
|
||||
|
||||
// Check if a file or directory exists.
|
||||
func Exists(path string, fs afero.Fs) (bool, error) {
|
||||
_, err := fs.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||
// Portions Copyright ©2015 The Hugo Authors
|
||||
//
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var testFS = new(afero.MemMapFs)
|
||||
|
||||
func TestDirExists(t *testing.T) {
|
||||
type test struct {
|
||||
input string
|
||||
expected bool
|
||||
}
|
||||
|
||||
// First create a couple directories so there is something in the filesystem
|
||||
//testFS := new(afero.MemMapFs)
|
||||
testFS.MkdirAll("/foo/bar", 0777)
|
||||
|
||||
data := []test{
|
||||
{".", true},
|
||||
{"./", true},
|
||||
{"..", true},
|
||||
{"../", true},
|
||||
{"./..", true},
|
||||
{"./../", true},
|
||||
{"/foo/", true},
|
||||
{"/foo", true},
|
||||
{"/foo/bar", true},
|
||||
{"/foo/bar/", true},
|
||||
{"/", true},
|
||||
{"/some-really-random-directory-name", false},
|
||||
{"/some/really/random/directory/name", false},
|
||||
{"./some-really-random-local-directory-name", false},
|
||||
{"./some/really/random/local/directory/name", false},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
exists, _ := DirExists(filepath.FromSlash(d.input), testFS)
|
||||
if d.expected != exists {
|
||||
t.Errorf("Test %d %q failed. Expected %t got %t", i, d.input, d.expected, exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDir(t *testing.T) {
|
||||
testFS = new(afero.MemMapFs)
|
||||
|
||||
type test struct {
|
||||
input string
|
||||
expected bool
|
||||
}
|
||||
data := []test{
|
||||
{"./", true},
|
||||
{"/", true},
|
||||
{"./this-directory-does-not-existi", false},
|
||||
{"/this-absolute-directory/does-not-exist", false},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
|
||||
exists, _ := IsDir(d.input, testFS)
|
||||
if d.expected != exists {
|
||||
t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
testFS = new(afero.MemMapFs)
|
||||
|
||||
zeroSizedFile, _ := createZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(zeroSizedFile)
|
||||
nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(nonZeroSizedFile)
|
||||
emptyDirectory, _ := createEmptyTempDir()
|
||||
defer deleteTempDir(emptyDirectory)
|
||||
nonEmptyZeroLengthFilesDirectory, _ := createTempDirWithZeroLengthFiles()
|
||||
defer deleteTempDir(nonEmptyZeroLengthFilesDirectory)
|
||||
nonEmptyNonZeroLengthFilesDirectory, _ := createTempDirWithNonZeroLengthFiles()
|
||||
defer deleteTempDir(nonEmptyNonZeroLengthFilesDirectory)
|
||||
nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
|
||||
nonExistentDir := os.TempDir() + "/this/direcotry/does/not/exist/"
|
||||
|
||||
fileDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentFile)
|
||||
dirDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentDir)
|
||||
|
||||
type test struct {
|
||||
input string
|
||||
expectedResult bool
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
data := []test{
|
||||
{zeroSizedFile.Name(), true, nil},
|
||||
{nonZeroSizedFile.Name(), false, nil},
|
||||
{emptyDirectory, true, nil},
|
||||
{nonEmptyZeroLengthFilesDirectory, false, nil},
|
||||
{nonEmptyNonZeroLengthFilesDirectory, false, nil},
|
||||
{nonExistentFile, false, fileDoesNotExist},
|
||||
{nonExistentDir, false, dirDoesNotExist},
|
||||
}
|
||||
for i, d := range data {
|
||||
exists, err := IsEmpty(d.input, testFS)
|
||||
if d.expectedResult != exists {
|
||||
t.Errorf("Test %d %q failed exists. Expected result %t got %t", i, d.input, d.expectedResult, exists)
|
||||
}
|
||||
if d.expectedErr != nil {
|
||||
if d.expectedErr.Error() != err.Error() {
|
||||
t.Errorf("Test %d failed with err. Expected %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
|
||||
}
|
||||
} else {
|
||||
if d.expectedErr != err {
|
||||
t.Errorf("Test %d failed. Expected error %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createZeroSizedFileInTempDir() (afero.File, error) {
|
||||
filePrefix := "_path_test_"
|
||||
f, e := TempFile("", filePrefix, testFS) // dir is os.TempDir()
|
||||
if e != nil {
|
||||
// if there was an error no file was created.
|
||||
// => no requirement to delete the file
|
||||
return nil, e
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func createNonZeroSizedFileInTempDir() (afero.File, error) {
|
||||
f, err := createZeroSizedFileInTempDir()
|
||||
if err != nil {
|
||||
// no file ??
|
||||
}
|
||||
byteString := []byte("byteString")
|
||||
err = WriteFile(f.Name(), byteString, 0644, testFS)
|
||||
if err != nil {
|
||||
// delete the file
|
||||
deleteFileInTempDir(f)
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func deleteFileInTempDir(f afero.File) {
|
||||
err := testFS.Remove(f.Name())
|
||||
if err != nil {
|
||||
// now what?
|
||||
}
|
||||
}
|
||||
|
||||
func createEmptyTempDir() (string, error) {
|
||||
dirPrefix := "_dir_prefix_"
|
||||
d, e := TempDir("", dirPrefix, testFS) // will be in os.TempDir()
|
||||
if e != nil {
|
||||
// no directory to delete - it was never created
|
||||
return "", e
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func createTempDirWithZeroLengthFiles() (string, error) {
|
||||
d, dirErr := createEmptyTempDir()
|
||||
if dirErr != nil {
|
||||
//now what?
|
||||
}
|
||||
filePrefix := "_path_test_"
|
||||
_, fileErr := TempFile(d, filePrefix, testFS) // dir is os.TempDir()
|
||||
if fileErr != nil {
|
||||
// if there was an error no file was created.
|
||||
// but we need to remove the directory to clean-up
|
||||
deleteTempDir(d)
|
||||
return "", fileErr
|
||||
}
|
||||
// the dir now has one, zero length file in it
|
||||
return d, nil
|
||||
|
||||
}
|
||||
|
||||
func createTempDirWithNonZeroLengthFiles() (string, error) {
|
||||
d, dirErr := createEmptyTempDir()
|
||||
if dirErr != nil {
|
||||
//now what?
|
||||
}
|
||||
filePrefix := "_path_test_"
|
||||
f, fileErr := TempFile(d, filePrefix, testFS) // dir is os.TempDir()
|
||||
if fileErr != nil {
|
||||
// if there was an error no file was created.
|
||||
// but we need to remove the directory to clean-up
|
||||
deleteTempDir(d)
|
||||
return "", fileErr
|
||||
}
|
||||
byteString := []byte("byteString")
|
||||
fileErr = WriteFile(f.Name(), byteString, 0644, testFS)
|
||||
if fileErr != nil {
|
||||
// delete the file
|
||||
deleteFileInTempDir(f)
|
||||
// also delete the directory
|
||||
deleteTempDir(d)
|
||||
return "", fileErr
|
||||
}
|
||||
|
||||
// the dir now has one, zero length file in it
|
||||
return d, nil
|
||||
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
zeroSizedFile, _ := createZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(zeroSizedFile)
|
||||
nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(nonZeroSizedFile)
|
||||
emptyDirectory, _ := createEmptyTempDir()
|
||||
defer deleteTempDir(emptyDirectory)
|
||||
nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
|
||||
nonExistentDir := os.TempDir() + "/this/direcotry/does/not/exist/"
|
||||
|
||||
type test struct {
|
||||
input string
|
||||
expectedResult bool
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
data := []test{
|
||||
{zeroSizedFile.Name(), true, nil},
|
||||
{nonZeroSizedFile.Name(), true, nil},
|
||||
{emptyDirectory, true, nil},
|
||||
{nonExistentFile, false, nil},
|
||||
{nonExistentDir, false, nil},
|
||||
}
|
||||
for i, d := range data {
|
||||
exists, err := Exists(d.input, testFS)
|
||||
if d.expectedResult != exists {
|
||||
t.Errorf("Test %d failed. Expected result %t got %t", i, d.expectedResult, exists)
|
||||
}
|
||||
if d.expectedErr != err {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSafeWriteToDisk(t *testing.T) {
|
||||
emptyFile, _ := createZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(emptyFile)
|
||||
tmpDir, _ := createEmptyTempDir()
|
||||
defer deleteTempDir(tmpDir)
|
||||
|
||||
randomString := "This is a random string!"
|
||||
reader := strings.NewReader(randomString)
|
||||
|
||||
fileExists := fmt.Errorf("%v already exists", emptyFile.Name())
|
||||
|
||||
type test struct {
|
||||
filename string
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
nowStr := strconv.FormatInt(now, 10)
|
||||
data := []test{
|
||||
{emptyFile.Name(), fileExists},
|
||||
{tmpDir + "/" + nowStr, nil},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
e := SafeWriteReader(d.filename, reader, testFS)
|
||||
if d.expectedErr != nil {
|
||||
if d.expectedErr.Error() != e.Error() {
|
||||
t.Errorf("Test %d failed. Expected error %q but got %q", i, d.expectedErr.Error(), e.Error())
|
||||
}
|
||||
} else {
|
||||
if d.expectedErr != e {
|
||||
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, e)
|
||||
}
|
||||
contents, _ := ReadFile(d.filename, testFS)
|
||||
if randomString != string(contents) {
|
||||
t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
|
||||
}
|
||||
}
|
||||
reader.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteToDisk(t *testing.T) {
|
||||
emptyFile, _ := createZeroSizedFileInTempDir()
|
||||
defer deleteFileInTempDir(emptyFile)
|
||||
tmpDir, _ := createEmptyTempDir()
|
||||
defer deleteTempDir(tmpDir)
|
||||
|
||||
randomString := "This is a random string!"
|
||||
reader := strings.NewReader(randomString)
|
||||
|
||||
type test struct {
|
||||
filename string
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
nowStr := strconv.FormatInt(now, 10)
|
||||
data := []test{
|
||||
{emptyFile.Name(), nil},
|
||||
{tmpDir + "/" + nowStr, nil},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
e := WriteReader(d.filename, reader, testFS)
|
||||
if d.expectedErr != e {
|
||||
t.Errorf("Test %d failed. WriteToDisk Error Expected %q but got %q", i, d.expectedErr, e)
|
||||
}
|
||||
contents, e := ReadFile(d.filename, testFS)
|
||||
if e != nil {
|
||||
t.Errorf("Test %d failed. Could not read file %s. Reason: %s\n", i, d.filename, e)
|
||||
}
|
||||
if randomString != string(contents) {
|
||||
t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
|
||||
}
|
||||
reader.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTempDir(t *testing.T) {
|
||||
dir := os.TempDir()
|
||||
if FilePathSeparator != dir[len(dir)-1:] {
|
||||
dir = dir + FilePathSeparator
|
||||
}
|
||||
testDir := "hugoTestFolder" + FilePathSeparator
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", dir},
|
||||
{testDir + " Foo bar ", dir + testDir + " Foo bar " + FilePathSeparator},
|
||||
{testDir + "Foo.Bar/foo_Bar-Foo", dir + testDir + "Foo.Bar/foo_Bar-Foo" + FilePathSeparator},
|
||||
{testDir + "fOO,bar:foo%bAR", dir + testDir + "fOObarfoo%bAR" + FilePathSeparator},
|
||||
{testDir + "FOo/BaR.html", dir + testDir + "FOo/BaR.html" + FilePathSeparator},
|
||||
{testDir + "трям/трям", dir + testDir + "трям/трям" + FilePathSeparator},
|
||||
{testDir + "은행", dir + testDir + "은행" + FilePathSeparator},
|
||||
{testDir + "Банковский кассир", dir + testDir + "Банковский кассир" + FilePathSeparator},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
output := GetTempDir(test.input, new(afero.MemMapFs))
|
||||
if output != test.expected {
|
||||
t.Errorf("Expected %#v, got %#v\n", test.expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is very dangerous. Don't use it.
|
||||
func deleteTempDir(d string) {
|
||||
err := os.RemoveAll(d)
|
||||
if err != nil {
|
||||
// now what?
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue