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