Adding Afero/utils

Portions ported from Go StdLib ioutils
Portions ported from Hugo's helpers
diff --git a/util/ioutil.go b/util/ioutil.go
new file mode 100644
index 0000000..ada8b12
--- /dev/null
+++ b/util/ioutil.go
@@ -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
+}
diff --git a/util/ioutil_test.go b/util/ioutil_test.go
new file mode 100644
index 0000000..ab2efbf
--- /dev/null
+++ b/util/ioutil_test.go
@@ -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)
+	}
+}
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..a0be68c
--- /dev/null
+++ b/util/util.go
@@ -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
+}
diff --git a/util/util_test.go b/util/util_test.go
new file mode 100644
index 0000000..447fdf6
--- /dev/null
+++ b/util/util_test.go
@@ -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?
+	}
+}