Add FileSystem and WalkFS

Closes #1
diff --git a/filesystem.go b/filesystem.go
new file mode 100644
index 0000000..f1c4805
--- /dev/null
+++ b/filesystem.go
@@ -0,0 +1,36 @@
+package fs
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+// FileSystem defines the methods of an abstract filesystem.
+type FileSystem interface {
+
+	// ReadDir reads the directory named by dirname and returns a
+	// list of directory entries.
+	ReadDir(dirname string) ([]os.FileInfo, error)
+
+	// Lstat returns a FileInfo describing the named file. If the file is a
+	// symbolic link, the returned FileInfo describes the symbolic link. Lstat
+	// makes no attempt to follow the link.
+	Lstat(name string) (os.FileInfo, error)
+
+	// Join joins any number of path elements into a single path, adding a
+	// separator if necessary. The result is Cleaned; in particular, all
+	// empty strings are ignored.
+	//
+	// The separator is FileSystem specific.
+	Join(elem ...string) string
+}
+
+// fs represents a FileSystem provided by the os package.
+type fs struct{}
+
+func (f *fs) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) }
+
+func (f *fs) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
+
+func (f *fs) Join(elem ...string) string { return filepath.Join(elem...) }
diff --git a/walk.go b/walk.go
index 36541c9..6ffa1e0 100644
--- a/walk.go
+++ b/walk.go
@@ -2,9 +2,7 @@
 package fs
 
 import (
-	"io/ioutil"
 	"os"
-	"path/filepath"
 )
 
 // Walker provides a convenient interface for iterating over the
@@ -15,6 +13,7 @@
 // but means that for very large directories Walker can be inefficient.
 // Walker does not follow symbolic links.
 type Walker struct {
+	fs      FileSystem
 	cur     item
 	stack   []item
 	descend bool
@@ -28,8 +27,16 @@
 
 // Walk returns a new Walker rooted at root.
 func Walk(root string) *Walker {
-	info, err := os.Lstat(root)
-	return &Walker{stack: []item{{root, info, err}}}
+	return WalkFS(root, new(fs))
+}
+
+// WalkFS returns a new Walker rooted at root on the FileSystem fs.
+func WalkFS(root string, fs FileSystem) *Walker {
+	info, err := fs.Lstat(root)
+	return &Walker{
+		fs:    fs,
+		stack: []item{{root, info, err}},
+	}
 }
 
 // Step advances the Walker to the next file or directory,
@@ -38,13 +45,13 @@
 // It returns false when the walk stops at the end of the tree.
 func (w *Walker) Step() bool {
 	if w.descend && w.cur.err == nil && w.cur.info.IsDir() {
-		list, err := ioutil.ReadDir(w.cur.path)
+		list, err := w.fs.ReadDir(w.cur.path)
 		if err != nil {
 			w.cur.err = err
 			w.stack = append(w.stack, w.cur)
 		} else {
 			for i := len(list) - 1; i >= 0; i-- {
-				path := filepath.Join(w.cur.path, list[i].Name())
+				path := w.fs.Join(w.cur.path, list[i].Name())
 				w.stack = append(w.stack, item{path, list[i], nil})
 			}
 		}