--- /dev/null
+package files
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// serialFile implements Node, and reads from a path on the OS filesystem.
+// No more than one file will be opened at a time (directories will advance
+// to the next file when NextFile() is called).
+type serialFile struct {
+ path string
+ files []os.FileInfo
+ stat os.FileInfo
+ handleHiddenFiles bool
+}
+
+type serialIterator struct {
+ files []os.FileInfo
+ handleHiddenFiles bool
+ path string
+
+ curName string
+ curFile Node
+
+ err error
+}
+
+// TODO: test/document limitations
+func NewSerialFile(path string, hidden bool, stat os.FileInfo) (Node, error) {
+ switch mode := stat.Mode(); {
+ case mode.IsRegular():
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ return NewReaderPathFile(path, file, stat)
+ case mode.IsDir():
+ // for directories, stat all of the contents first, so we know what files to
+ // open when NextFile() is called
+ contents, err := ioutil.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+ return &serialFile{path, contents, stat, hidden}, nil
+ case mode&os.ModeSymlink != 0:
+ target, err := os.Readlink(path)
+ if err != nil {
+ return nil, err
+ }
+ return NewLinkFile(target, stat), nil
+ default:
+ return nil, fmt.Errorf("unrecognized file type for %s: %s", path, mode.String())
+ }
+}
+
+func (it *serialIterator) Name() string {
+ return it.curName
+}
+
+func (it *serialIterator) Node() Node {
+ return it.curFile
+}
+
+func (it *serialIterator) Next() bool {
+ // if there aren't any files left in the root directory, we're done
+ if len(it.files) == 0 {
+ return false
+ }
+
+ stat := it.files[0]
+ it.files = it.files[1:]
+ for !it.handleHiddenFiles && strings.HasPrefix(stat.Name(), ".") {
+ if len(it.files) == 0 {
+ return false
+ }
+
+ stat = it.files[0]
+ it.files = it.files[1:]
+ }
+
+ // open the next file
+ filePath := filepath.ToSlash(filepath.Join(it.path, stat.Name()))
+
+ // recursively call the constructor on the next file
+ // if it's a regular file, we will open it as a ReaderFile
+ // if it's a directory, files in it will be opened serially
+ sf, err := NewSerialFile(filePath, it.handleHiddenFiles, stat)
+ if err != nil {
+ it.err = err
+ return false
+ }
+
+ it.curName = stat.Name()
+ it.curFile = sf
+ return true
+}
+
+func (it *serialIterator) Err() error {
+ return it.err
+}
+
+func (f *serialFile) Entries() DirIterator {
+ return &serialIterator{
+ path: f.path,
+ files: f.files,
+ handleHiddenFiles: f.handleHiddenFiles,
+ }
+}
+
+func (f *serialFile) NextFile() (string, Node, error) {
+ // if there aren't any files left in the root directory, we're done
+ if len(f.files) == 0 {
+ return "", nil, io.EOF
+ }
+
+ stat := f.files[0]
+ f.files = f.files[1:]
+
+ for !f.handleHiddenFiles && strings.HasPrefix(stat.Name(), ".") {
+ if len(f.files) == 0 {
+ return "", nil, io.EOF
+ }
+
+ stat = f.files[0]
+ f.files = f.files[1:]
+ }
+
+ // open the next file
+ filePath := filepath.ToSlash(filepath.Join(f.path, stat.Name()))
+
+ // recursively call the constructor on the next file
+ // if it's a regular file, we will open it as a ReaderFile
+ // if it's a directory, files in it will be opened serially
+ sf, err := NewSerialFile(filePath, f.handleHiddenFiles, stat)
+ if err != nil {
+ return "", nil, err
+ }
+
+ return stat.Name(), sf, nil
+}
+
+func (f *serialFile) Close() error {
+ return nil
+}
+
+func (f *serialFile) Stat() os.FileInfo {
+ return f.stat
+}
+
+func (f *serialFile) Size() (int64, error) {
+ if !f.stat.IsDir() {
+ //something went terribly, terribly wrong
+ return 0, errors.New("serialFile is not a directory")
+ }
+
+ var du int64
+ err := filepath.Walk(f.path, func(p string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if fi != nil && fi.Mode().IsRegular() {
+ du += fi.Size()
+ }
+ return nil
+ })
+
+ return du, err
+}
+
+var _ Directory = &serialFile{}
+var _ DirIterator = &serialIterator{}