diff --git a/Filesystem/Enumerator.hs b/Filesystem/Enumerator.hs
new file mode 100644
--- /dev/null
+++ b/Filesystem/Enumerator.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE CPP #-}
+
+-- |
+-- Module: Filesystem.Enumerator
+-- Copyright: 2011 John Millikin
+--            2011 Michael Snoyman
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-- Enumerator-based API for manipulating the filesystem.
+module Filesystem.Enumerator
+	( traverse
+	, enumDirectory
+	) where
+
+import           Prelude hiding (FilePath)
+
+import           Control.Exception (finally)
+import           Control.Monad.IO.Class (MonadIO)
+import           Data.Enumerator
+import           Filesystem
+import           Filesystem.Path ((</>))
+import           Filesystem.Path.CurrentOS (FilePath, encodeString, decodeString)
+
+#ifdef CABAL_OS_WINDOWS
+import qualified System.Win32 as Win32
+#else
+import qualified System.Posix as Posix
+#endif
+
+-- | Starting at some root directory, traverse the filesystem and enumerate
+-- every file (or symlink to a file) found.
+--
+-- Note: the option of whether to follow symlinks is currently only checked
+-- on POSIX platforms, as the @Win32@ package does not support querying
+-- symlink status. On Windows, symlinks will always be followed.
+traverse :: MonadIO m
+         => Bool -- ^ Follow directory symlinks (only used on POSIX platforms)
+         -> FilePath -- ^ Root directory
+         -> Enumerator FilePath m a
+traverse followSymlinks root s = tryIO (listDirectory root) >>= (\ps -> loop ps s) where
+	loop (p:ps) step@(Continue k) = do
+		isFile' <- tryIO (isFile p)
+		if isFile'
+			then k (Chunks [p]) >>== loop ps
+			else do
+				follow' <- tryIO (follow p)
+				if follow'
+					then traverse followSymlinks p step >>== loop ps
+					else loop ps step
+	loop _ step = returnI step
+
+	follow :: FilePath -> IO Bool
+#ifdef CABAL_OS_WINDOWS
+	follow = isDirectory
+#else
+	follow p = do
+		let path = encodeString p
+		stat <- if followSymlinks
+			then Posix.getFileStatus path
+			else Posix.getSymbolicLinkStatus path
+		return (Posix.isDirectory stat)
+#endif
+
+-- | Enumerate entries in a directory. Entries are returned with their full
+-- path. Entries are read from the directory handle as needed, so this is safe
+-- to use with very large directories.
+enumDirectory :: FilePath -> Enumerator FilePath IO a
+enumDirectory root step = do
+	let boring str = str == "." || str == ".."
+	
+#ifdef CABAL_OS_WINDOWS
+	let search = root </> decodeString "*"
+	(h, findData) <- tryIO (Win32.findFirstFile (encodeString search))
+	let close = Win32.findClose h
+	let iter = checkContinue0 $ \loop k -> do
+		raw <- tryIO (Win32.getFindDataFileName findData)
+		let path = root </> decodeString raw
+		let checkNext s = do
+			hasNext <- tryIO (Win32.findNextFile h findData)
+			if hasNext
+				then loop s
+				else returnI s
+		if boring raw
+			then checkNext (Continue k)
+			else k (Chunks [path]) >>== checkNext
+#else
+	dir <- tryIO (Posix.openDirStream (encodeString root))
+	let close = Posix.closeDirStream dir
+	let iter = checkContinue0 $ \loop k -> do
+		raw <- tryIO (Posix.readDirStream dir)
+		let path = root </> decodeString raw
+		case raw of
+			"" -> continue k
+			_ | boring raw -> loop (Continue k)
+			_ ->  k (Chunks [path]) >>== loop
+#endif
+	Iteratee (finally (runIteratee (iter step)) close)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/filesystem-enumerator.cabal b/filesystem-enumerator.cabal
new file mode 100644
--- /dev/null
+++ b/filesystem-enumerator.cabal
@@ -0,0 +1,37 @@
+name: filesystem-enumerator
+version: 0.1
+synopsis: Enumerator-based API for manipulating the filesystem.
+license: MIT
+license-file: license.txt
+author: John Millikin <jmillikin@gmail.com>, Michael Snoyman <michael@snoyman.com>
+maintainer: jmillikin@gmail.com
+build-type: Simple
+cabal-version: >= 1.6
+category: System, Enumerator
+stability: experimental
+bug-reports: mailto:jmillikin@gmail.com
+homepage: https://john-millikin.com/software/filesystem-enumerator/
+tested-with: GHC==6.12.1
+
+source-repository head
+  type: bazaar
+  location: https://john-millikin.com/software/filesystem-enumerator/
+
+library
+  ghc-options: -Wall
+
+  build-depends:
+      base >= 3 && < 5
+    , enumerator >= 0.4 && < 0.5
+    , system-filepath >= 0.4 && < 0.5
+    , system-fileio >= 0.3 && < 0.4
+    , transformers >= 0.2 && < 0.3
+
+  if os(windows)
+    cpp-options: -DCABAL_OS_WINDOWS
+    build-depends: Win32
+  else
+    build-depends: unix
+
+  exposed-modules:
+    Filesystem.Enumerator
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2010 John Millikin
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
