diff --git a/Examples/cat.hs b/Examples/cat.hs
new file mode 100644
--- /dev/null
+++ b/Examples/cat.hs
@@ -0,0 +1,119 @@
+-----------------------------------------------------------------------------
+-- |
+-- Copyright: 2010 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-----------------------------------------------------------------------------
+module Main (main) where
+import Prelude as Prelude
+import Control.Exception as E
+import Data.Enumerator
+import qualified Data.ByteString as B
+import qualified Foreign as F
+import System.IO
+import System.Environment (getArgs)
+
+-- The following definitions of 'enumHandle', 'enumFile', and 'iterHandle' are
+-- copied from "Data.Enumerator.IO", with additional comments so they're easier
+-- to understand.
+
+enumHandle :: Integer -- ^ Buffer size
+           -> Handle
+           -> Enumerator SomeException B.ByteString IO b
+enumHandle bufferSize h = Iteratee . io where
+	intSize = fromInteger bufferSize
+	
+	-- Allocate a single buffer for reading from the file
+	io step = F.allocaBytes intSize $ \p -> loop step p
+	
+	-- If more input is required before the enumerator's iteratee can
+	-- yield a result, feed it from the handle. If a different step is
+	-- received ('Error' or 'Yield'), just pass it through.
+	loop (Continue k) = read' k
+	loop step = const $ return step
+	
+	read' k p = do
+		-- While not strictly necessary to proper operation, catching
+		-- exceptions here allows more unified exception handling when
+		-- the enumerator/iteratee is run.
+		eitherN <- E.try $ hGetBuf h p intSize
+		case eitherN of
+			Left err -> return $ Error err
+			
+			-- if 'hGetBuf' returns 0, then the handle has reached
+			-- EOF. The enumerator has two choices:
+			--
+			-- * Send EOF to its iteratee, and return the result
+			-- * Return a Continue, with the current continuation
+			--
+			-- The second is better, because it allows enumerators
+			-- to be composed with (>>==). If EOF is sent, only
+			-- one enumerator can be read at a time.
+			Right 0 -> return $ Continue k
+			
+			-- 'hGetBuf' was at least partially successful, so read
+			-- bytes into a ByteString and pass it through to the
+			-- iteratee.
+			Right n -> do
+				bytes <- B.packCStringLen (p, n)
+				step <- runIteratee (k (Chunks [bytes]))
+				loop step p
+
+enumFile :: FilePath -> Enumerator E.SomeException B.ByteString IO b
+enumFile path s = Iteratee $ do
+	-- Opening the file can be performed either inside or outside of the
+	-- Iteratee. Inside allows exceptions to be caught and propagated
+	-- through the 'Error' step constructor.
+	eitherH <- E.try $ openBinaryFile path ReadMode
+	case eitherH of
+		Left err -> return $ Error err
+		Right h -> finally
+			(runIteratee (enumHandle 4096 h s))
+			(hClose h)
+
+-- 'iterHandle' is the opposite of 'enumHandle', in that it *writes to* a
+-- handle instead of reading from it. An enumerator is a source, an iteratee
+-- is a sink.
+iterHandle :: Handle -> Iteratee SomeException B.ByteString IO ()
+
+-- Most iteratees start in the 'Continue' state, as they need some
+-- input before they can produce any value.
+iterHandle h = continue step where
+	
+	-- This iteratee produces no value; its only purpose is its
+	-- side-effects. When 'EOF' is received, it simply yields ().
+	step EOF = yield () EOF
+	
+	-- When some chunks are received from the Enumeratee, they're written
+	-- to the handle. Any exceptions are caught and reported, as in
+	-- 'enumHandle'.
+	step (Chunks bytes) = Iteratee $ do
+		eitherErr <- E.try $ mapM_ (B.hPut h) bytes
+		return $ case eitherErr of
+			Left err -> Error err
+			_ -> Continue step
+
+main :: IO ()
+main = do
+	-- Our example enumlates standard /bin/cat, where if the argument list
+	-- is empty, data is echoed from stdin.
+	args <- getArgs
+	let enum = if null args
+		then enumHandle 1 stdin
+		else concatEnums (Prelude.map enumFile args)
+	
+	-- This line looks a bit weird, because the (>>==) causes a visual
+	-- "flow" from the iteratee to the enumerator. However, data is
+	-- actually being sent from the enumerator to the iteratee. It's
+	-- better to think of it in terms of continuation passing.
+	res <- run (iterHandle stdout >>== enum)
+	
+	-- Finally, 'run' has returned either an error or the iteratee's
+	-- result. 'iterHandle' doesn't return a useful result, so as long
+	-- as it succeeded the actual value is ignored.
+	case res of
+		Left err -> putStrLn $ "ERROR: " ++ show err
+		Right _ -> return ()
diff --git a/Examples/wc.hs b/Examples/wc.hs
new file mode 100644
--- /dev/null
+++ b/Examples/wc.hs
@@ -0,0 +1,205 @@
+-----------------------------------------------------------------------------
+-- |
+-- Copyright: 2010 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-----------------------------------------------------------------------------
+module Main (main) where
+import Data.Enumerator
+import Data.Enumerator.IO
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+
+-- support imports
+import Control.Exception as E
+import Data.List
+import Data.Bits ((.&.))
+import Data.Word (Word8)
+import Control.Monad (unless, forM_)
+import System.IO
+import System.Console.GetOpt
+import System.Environment
+import System.Exit
+
+-- support wc modes -c (bytes), -m (characters), and -l (lines)
+
+-- iterBytes simply counts how many bytes are in each chunk, accumulates this
+-- count, and returns it when EOF is received
+
+iterBytes :: Monad m => Iteratee e B.ByteString m Integer
+iterBytes = continue (step 0) where
+	step acc EOF = yield acc EOF
+	step acc (Chunks xs) = continue $ step (foldl' foldStep acc xs)
+	foldStep acc bytes = acc + toInteger (B.length bytes)
+
+-- iterLines is similar, except it only counts newlines ('\n')
+
+iterLines :: Monad m => Iteratee e B.ByteString m Integer
+iterLines = continue (step 0) where
+	step acc EOF = yield acc EOF
+	step acc (Chunks xs) = continue $ step (foldl' foldStep acc xs)
+	foldStep acc bytes = acc + countChar '\n' bytes
+	countChar c = B8.foldl (\acc c' -> if c' == c then acc + 1 else acc) 0
+
+-- iterChars is a bit more complicated. It has to decode the input (for now,
+-- assuming UTF-8) before performing any counting. Leftover bytes, not part
+-- of a valid UTF-8 character, are yielded as surplus
+--
+-- Since it's possible for the input file's encoding to be non-UTF8, a bit
+-- of error handling is also required.
+
+iterChars :: Monad m => Iteratee E.SomeException B.ByteString m Integer
+iterChars = continue (step (B.empty, 0)) where
+	step accT chunk = case chunk of
+		EOF -> let (extra, acc) = accT in yield acc (Chunks [extra])
+		(Chunks xs) -> case foldl' foldStep (Just accT) xs of
+			Just accT' -> continue $ step accT'
+			Nothing -> throwError (E.SomeException (E.ErrorCall "Invalid UTF-8"))
+	
+	-- The 'decodeUtf8' function is complicated, and defined later, but
+	-- all it does is decode as much input as possible, then return any
+	-- remaining bytes.
+	
+	foldStep Nothing _ = Nothing
+	foldStep (Just (extra, acc)) bytes = case decodeUtf8 (B.append extra bytes) of
+		Just (text, extra') -> Just (extra', acc + toInteger (T.length text))
+		Nothing -> Nothing
+
+
+main :: IO ()
+main = do
+	(mode, files) <- getMode
+	
+	-- Exactly matching wc's output is too annoying, so this example
+	-- will just print one line per file, and support counting at most
+	-- one statistic per run
+	let iter = case mode of
+		OptionBytes -> iterBytes
+		OptionLines -> iterLines
+		OptionChars -> iterChars
+	
+	forM_ files $ \filename -> do
+		putStr $ filename ++ ": "
+		
+		-- see cat.hs for commented implementation of 'Data.Enumerator.IO.enumFile'
+		eitherStat <- run (iter >>== enumFile filename)
+		putStrLn $ case eitherStat of
+			Left err -> "ERROR: " ++ show err
+			Right stat -> show stat
+
+-- uninteresting option parsing follows
+
+data Option
+	= OptionBytes
+	| OptionChars
+	| OptionLines
+
+optionInfo :: [OptDescr Option]
+optionInfo =
+	[ Option ['c'] ["bytes"] (NoArg OptionBytes) "count bytes"
+	, Option ['m'] ["chars"] (NoArg OptionChars) "count characters"
+	, Option ['l'] ["lines"] (NoArg OptionLines) "count lines"
+	]
+
+usage :: String -> String
+usage name = "Usage: " ++ name ++ " <MODE> [FILES]"
+
+getMode :: IO (Option, [FilePath])
+getMode = do
+	args <- getArgs
+	let (options, files, errors) = getOpt Permute optionInfo args
+	unless (null errors && not (null options) && not (null files)) $ do
+		name <- getProgName
+		hPutStrLn stderr $ concat errors
+		hPutStrLn stderr $ usageInfo (usage name) optionInfo
+		exitFailure
+	
+	return (Prelude.head options, files)
+
+-- Incremental UTF-8 validator / decoder
+
+decodeUtf8 :: B.ByteString -> Maybe (T.Text, B.ByteString)
+decodeUtf8 allBytes = loop B.empty allBytes where
+	
+	loop acc bytes | B.null bytes = Just (T.decodeUtf8 acc, bytes)
+	loop acc bytes = do
+		let (x0, x1, x2, x3) = indexes bytes
+		req <- required x0
+		if req > B.length bytes
+			then Just (T.decodeUtf8 acc, bytes)
+			else loop (B.append acc (B.take req bytes)) (B.drop req bytes)
+	
+	required x0
+		| x0 .&. 0x80 == 0x00 = Just 1
+		| x0 .&. 0xE0 == 0xC0 = Just 2
+		| x0 .&. 0xF0 == 0xE0 = Just 3
+		| x0 .&. 0xF8 == 0xF0 = Just 4
+		| otherwise           = Nothing
+	
+	indexes bytes = (x0, x1, x2, x3) where
+		x0 = B.index bytes 0
+		x1 = B.index bytes 1
+		x2 = B.index bytes 2
+		x3 = B.index bytes 3
+
+-- UTF8 decoding gunk; mostly copied from Data.Text
+
+between :: Word8                -- ^ byte to check
+        -> Word8                -- ^ lower bound
+        -> Word8                -- ^ upper bound
+        -> Bool
+between x y z = x >= y && x <= z
+{-# INLINE between #-}
+
+validate1    :: Word8 -> Bool
+validate1 x1 = between x1 0x00 0x7F
+{-# INLINE validate1 #-}
+
+validate2       :: Word8 -> Word8 -> Bool
+validate2 x1 x2 = between x1 0xC2 0xDF && between x2 0x80 0xBF
+{-# INLINE validate2 #-}
+
+validate3          :: Word8 -> Word8 -> Word8 -> Bool
+{-# INLINE validate3 #-}
+validate3 x1 x2 x3 = validate3_1 ||
+                     validate3_2 ||
+                     validate3_3 ||
+                     validate3_4
+  where
+    validate3_1 = (x1 == 0xE0) &&
+                  between x2 0xA0 0xBF &&
+                  between x3 0x80 0xBF
+    validate3_2 = between x1 0xE1 0xEC &&
+                  between x2 0x80 0xBF &&
+                  between x3 0x80 0xBF
+    validate3_3 = x1 == 0xED &&
+                  between x2 0x80 0x9F &&
+                  between x3 0x80 0xBF
+    validate3_4 = between x1 0xEE 0xEF &&
+                  between x2 0x80 0xBF &&
+                  between x3 0x80 0xBF
+
+validate4             :: Word8 -> Word8 -> Word8 -> Word8 -> Bool
+{-# INLINE validate4 #-}
+validate4 x1 x2 x3 x4 = validate4_1 ||
+                        validate4_2 ||
+                        validate4_3
+  where 
+    validate4_1 = x1 == 0xF0 &&
+                  between x2 0x90 0xBF &&
+                  between x3 0x80 0xBF &&
+                  between x4 0x80 0xBF
+    validate4_2 = between x1 0xF1 0xF3 &&
+                  between x2 0x80 0xBF &&
+                  between x3 0x80 0xBF &&
+                  between x4 0x80 0xBF
+    validate4_3 = x1 == 0xF4 &&
+                  between x2 0x80 0x8F &&
+                  between x3 0x80 0xBF &&
+                  between x4 0x80 0xBF
+
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/enumerator.cabal b/enumerator.cabal
new file mode 100644
--- /dev/null
+++ b/enumerator.cabal
@@ -0,0 +1,38 @@
+name: enumerator
+version: 0.1
+synopsis: Implementation of Oleg Kiselyov's left-fold enumerators
+license: MIT
+license-file: license.txt
+author: John Millikin <jmillikin@gmail.com>
+maintainer: jmillikin@gmail.com
+copyright: Copyright (c) John Millikin 2010
+build-type: Simple
+cabal-version: >=1.6
+category: Data
+stability: experimental
+homepage: http://ianen.org/haskell/enumerator/
+bug-reports: mailto:jmillikin@gmail.com
+tested-with: GHC==6.12.1
+
+description:
+  Based on Oleg Kiselyov's IterateeM: <http://okmij.org/ftp/Haskell/Iteratee/IterateeM.hs>
+
+extra-source-files:
+  Examples/*.hs
+
+source-repository head
+  type: darcs
+  location: http://ianen.org/haskell/enumerator/
+
+library
+  ghc-options: -Wall -fno-warn-unused-do-bind
+  hs-source-dirs: hs
+
+  build-depends:
+      base >=3 && < 5
+    , transformers >= 0.2 && < 0.3
+    , bytestring >= 0.9 && < 0.10
+
+  exposed-modules:
+    Data.Enumerator
+    Data.Enumerator.IO
diff --git a/hs/Data/Enumerator.hs b/hs/Data/Enumerator.hs
new file mode 100644
--- /dev/null
+++ b/hs/Data/Enumerator.hs
@@ -0,0 +1,336 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module: Data.Enumerator
+-- Copyright: 2010 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-- An implementation of Oleg Kiselyov&#x2019;s left-fold enumerators
+--
+-----------------------------------------------------------------------------
+module Data.Enumerator (
+	  -- * Types
+	  Stream (..)
+	, Step (..)
+	, Iteratee (..)
+	, Enumerator
+	, Enumeratee
+	  -- * Primitives
+	  -- ** Combinators
+	  -- | These are common patterns which occur whenever iteratees are
+	  -- being defined.
+	, returnI
+	, yield
+	, continue
+	, throwError
+	, liftI
+	, (>>==)
+	, (==<<)
+	  -- ** Iteratees
+	, consume
+	, isEOF
+	  -- ** Enumerators
+	, enumEOF
+	, enumList
+	, concatEnums
+	  -- ** Enumeratees
+	, checkDone
+	, Data.Enumerator.map
+	, Data.Enumerator.sequence
+	, joinI
+	  -- * Parser combinators
+	  -- | Oleg&#x2019;s original @IterateeM.hs@ includes some basic iteratees
+	  -- for parsing, so this section ports them to the new interface. However,
+	  -- in practice most parsing will be performed with enumerator-based
+	  -- interfaces to existing parser libraries (such as Parsec or Attoparsec).
+	, Data.Enumerator.head
+	, peek
+	, Data.Enumerator.last
+	, Data.Enumerator.length
+	, Data.Enumerator.drop
+	, Data.Enumerator.dropWhile
+	, span
+	, Data.Enumerator.break
+	  -- * Utility functions
+	, run
+	, printChunks
+	) where
+import Data.List (genericDrop, genericLength, genericSplitAt)
+import Data.Monoid (Monoid, mempty, mappend, mconcat)
+import qualified Control.Applicative as A
+import Control.Monad (liftM, ap)
+import qualified Control.Monad.IO.Class as MIO
+import qualified Control.Monad.Trans.Class as MT
+import qualified Control.Exception as E
+import Prelude hiding (span)
+import qualified Prelude as Prelude
+-- | Not to be confused with types from the @Stream@ or
+-- @stream-fusion@ packages, a 'Stream' is a sequence of chunks
+-- generated by an 'Enumerator'. In contrast to Oleg&#x2019;s implementation,
+-- this stream does not support error handling -- errors encountered
+-- while generating a stream are reported in the 'Step' type instead.
+--
+-- @(Chunks [])@ is used to indicate that a stream is still active, but
+-- currently has no available data. Iteratees should ignore empty chunks.
+data Stream a
+	= Chunks [a]
+	| EOF
+	deriving (Show, Eq)
+data Step e a m b
+	-- | The 'Iteratee' is capable of accepting more input. Note that more input
+	-- is not necessarily required; the 'Iteratee' might be able to generate a
+	-- value immediately if it receives 'EOF'.
+	= Continue (Stream a -> Iteratee e a m b)
+	
+	-- | The 'Iteratee' has received enough input to generate a result.
+	-- Included in this value is left-over input, which can be passed to
+	-- composed 'Iteratee's.
+	| Yield b (Stream a)
+	
+	-- | The 'Iteratee' encountered an error which prevents it from proceeding
+	-- further. The type of error will depend on the 'Enumerator' and/or
+	-- 'Iteratee' -- common choices are 'String' and 'E.SomeException'.
+	| Error e
+
+-- | The primary data type for this library, which consumes
+-- input from a 'Stream' until it either generates a value or encounters
+-- an error. Rather than requiring all input at once, an iteratee will
+-- return 'Continue' when it is capable of processing more data.
+--
+-- In general, iteratees begin in the 'Continue' state. As each chunk is
+-- passed to the continuation, the iteratee returns the next step:
+-- 'Continue' for more data, 'Yield' when it's finished, or 'Error' to
+-- abort processing.
+newtype Iteratee e a m b = Iteratee
+	{ runIteratee :: m (Step e a m b)
+	}
+-- | While 'Iteratee's consume data, enumerators generate it. Since
+-- @'Iteratee'@ is an alias for @m ('Step' e a m b)@, 'Enumerator's can
+-- be considered step transformers of type
+-- @'Step' e a m b -> m ('Step' e a m b)@.
+--
+-- 'Enumerator's typically read from an external source (parser, handle,
+-- random generator, etc). They feed chunks into an 'Iteratee' until the
+-- source runs out of data (triggering 'EOF') or the iteratee finishes
+-- processing ('Yield's a value).
+type Enumerator e a m b = Step e a m b -> Iteratee e a m b
+
+-- | In cases where an enumerator acts as both a source and sink, the resulting
+-- type is named an 'Enumeratee'. Enumeratees have two input types,
+-- &#x201c;outer a&#x201d; (@aOut@) and &#x201c;inner a&#x201d; (@aIn@).
+type Enumeratee e aOut aIn m b = Step e aIn m b -> Iteratee e aOut m (Step e aIn m b)
+instance Monoid (Stream a) where
+	mempty = Chunks mempty
+	mappend (Chunks xs) (Chunks ys) = Chunks $ mappend xs ys
+	mappend _ _ = EOF
+
+instance Functor Stream where
+	fmap f (Chunks xs) = Chunks $ fmap f xs
+	fmap _ EOF = EOF
+
+instance Monad Stream where
+	return = Chunks . return
+	Chunks xs >>= f = mconcat $ fmap f xs
+	EOF >>= _ = EOF
+instance Monad m => Monad (Iteratee e a m) where
+	return x = Iteratee . return $ Yield x $ Chunks []
+	{-# INLINE return #-}
+	
+	m >>= f = Iteratee $ runIteratee m >>=
+		\r1 -> case r1 of
+			Continue k -> return $ Continue ((>>= f) . k)
+			Error err -> return $ Error err
+			Yield x (Chunks []) -> runIteratee $ f x
+			Yield x chunk -> runIteratee (f x) >>=
+				\r2 -> case r2 of
+					Continue k -> runIteratee $ k chunk
+					Error err -> return $ Error err
+					Yield x' _ -> return $ Yield x' chunk
+instance Monad m => Functor (Iteratee e a m) where
+	fmap = liftM
+	{-# INLINE fmap #-}
+
+instance Monad m => A.Applicative (Iteratee e a m) where
+	pure = return
+	{-# INLINE pure #-}
+	
+	(<*>) = ap
+	{-# INLINE (<*>) #-}
+instance MT.MonadTrans (Iteratee e a) where
+	lift m = Iteratee $ m >>= runIteratee . return
+	{-# INLINE lift #-}
+
+instance MIO.MonadIO m => MIO.MonadIO (Iteratee e a m) where
+	liftIO = MT.lift . MIO.liftIO
+	{-# INLINE liftIO #-}
+-- | @returnI x = Iteratee (return x)@
+returnI :: Monad m => Step e a m b -> Iteratee e a m b
+returnI = Iteratee . return
+{-# INLINE returnI #-}
+
+-- | @yield x chunk = returnI (Yield x chunk)@
+yield :: Monad m => b -> Stream a -> Iteratee e a m b
+yield x chunk = returnI (Yield x chunk)
+{-# INLINE yield #-}
+
+-- | @continue k = returnI (Continue k)@
+continue :: Monad m => (Stream a -> Iteratee e a m b) -> Iteratee e a m b
+continue = returnI . Continue
+{-# INLINE continue #-}
+
+-- | @throwError err = returnI (Error err)@
+throwError :: Monad m => e -> Iteratee e a m b
+throwError = returnI . Error
+{-# INLINE throwError #-}
+
+-- | @liftI f = continue (returnI . f)@
+liftI :: Monad m => (Stream a -> Step e a m b) -> Iteratee e a m b
+liftI k = continue $ returnI . k
+{-# INLINE liftI #-}
+-- | Equivalent to (>>=), but allows 'Iteratee's with different input types
+-- to be composed.
+(>>==) :: Monad m =>
+	Iteratee e a m b ->
+	(Step e a m b -> Iteratee e a' m b') ->
+	Iteratee e a' m b'
+i >>== f = Iteratee $ runIteratee i >>= runIteratee . f
+{-# INLINE (>>==) #-}
+
+-- | @(==\<\<) = flip (\>\>==)@
+(==<<):: Monad m =>
+	(Step e a m b -> Iteratee e a' m b') ->
+	Iteratee e a m b ->
+	Iteratee e a' m b'
+(==<<) = flip (>>==)
+{-# INLINE (==<<) #-}
+-- | Consume all input until 'EOF', then return consumed input as a list.
+consume :: Monad m => Iteratee e a m [a]
+consume = liftI $ step [] where
+	step acc chunk = case chunk of
+		Chunks [] -> Continue $ returnI . step acc
+		Chunks xs -> Continue $ returnI . (step $ acc ++ xs)
+		EOF -> Yield acc EOF
+-- | Return 'True' if the next 'Stream' is 'EOF'.
+isEOF :: Monad m => Iteratee e a m Bool
+isEOF = liftI $ \c -> case c of
+	EOF -> Yield True c
+	_   -> Yield False c
+-- | The most primitive enumerator; simply sends 'EOF'. The iteratee must
+-- either yield a value or throw an error continuing receiving 'EOF' will
+-- not terminate with any useful value.
+enumEOF :: Monad m => Enumerator e a m b
+enumEOF (Yield x _) = yield x EOF
+enumEOF (Error err) = throwError err
+enumEOF (Continue k) = k EOF >>== check where
+	check (Continue _) = error "enumEOF: divergent iteratee"
+	check s = enumEOF s
+-- | Another small, useful enumerator separates an input list into chunks,
+-- and sends them to the iteratee. This is useful for testing iteratees in pure
+-- code.
+enumList :: Monad m => Integer -> [a] -> Enumerator e a m b
+enumList n xs (Continue k) | not (null xs) = k chunk >>== loop where
+	(s1, s2) = genericSplitAt n xs
+	chunk = Chunks s1
+	loop = enumList n s2
+enumList _ _ step = returnI step
+-- | Compose a list of 'Enumerator's using '(>>==)'
+concatEnums :: Monad m => [Enumerator e a m b] -> Enumerator e a m b
+concatEnums [] s = returnI s
+concatEnums (e:es) s = e s >>== concatEnums es
+-- | 'joinI' is used to &#x201C;flatten&#x201D; 'Enumeratee's into an
+-- 'Iteratee'.
+joinI :: Monad m => Iteratee e a m (Step e a' m b) -> Iteratee e a m b
+joinI outer = outer >>= check where
+	check (Continue k) = k EOF >>== \s -> case s of
+		Continue _ -> error "joinI: divergent iteratee"
+		_ -> check s
+	check (Yield x _) = return x
+	check (Error e) = throwError e
+-- | A common pattern in 'Enumeratee' implementations is to check whether
+-- the inner 'Iteratee' has finished, and if so, to return its output.
+-- 'checkDone' passes its parameter a continuation if the 'Iteratee'
+-- can still consume input, or yields otherwise.
+checkDone :: Monad m =>
+	((Stream a -> Iteratee e a m b) -> Iteratee e a' m (Step e a m b)) ->
+	Enumeratee e a' a m b
+checkDone _ (Yield x chunk) = return $ Yield x chunk
+checkDone f (Continue k) = f k
+checkDone _ (Error err) = throwError err
+{-# INLINE checkDone #-}
+map :: Monad m => (ao -> ai) -> Enumeratee e ao ai m b
+map f = loop where
+	loop = checkDone $ continue . step
+	step k EOF = yield (Continue k) EOF
+	step k (Chunks []) = continue $ step k
+	step k (Chunks xs) = k (Chunks (Prelude.map f xs)) >>== loop
+sequence :: Monad m => Iteratee e ao m ai -> Enumeratee e ao ai m b
+sequence i = loop where
+	loop = checkDone check
+	check k = isEOF >>= \f -> if f
+		then yield (Continue k) EOF
+		else step k
+	step k = i >>= \v -> k (Chunks [v]) >>== loop
+head :: Monad m => Iteratee e a m (Maybe a)
+head = liftI step where
+	step (Chunks []) = Continue $ returnI . step
+	step (Chunks (x:xs)) = Yield (Just x) (Chunks xs)
+	step EOF = Yield Nothing EOF
+peek :: Monad m => Iteratee e a m (Maybe a)
+peek = liftI step where
+	step (Chunks []) = Continue $ returnI . step
+	step chunk@(Chunks (x:_)) = Yield (Just x) chunk
+	step chunk = Yield Nothing chunk
+last :: Monad m => Iteratee e a m (Maybe a)
+last = liftI $ step Nothing where
+	step ret (Chunks xs) = let
+		ret' = case xs of
+			[] -> ret
+			_  -> Just $ Prelude.last xs
+		in Continue $ returnI . step ret'
+	step ret EOF = Yield ret EOF
+length :: Monad m => Iteratee e a m Integer
+length = liftI $ step 0 where
+	step n (Chunks xs) = Continue $ returnI . step (n + genericLength xs)
+	step n EOF = Yield n EOF
+drop :: Monad m => Integer -> Iteratee e a m ()
+drop 0 = return ()
+drop n = liftI $ step n where
+	step n' (Chunks xs)
+		| len xs < n' = Continue $ returnI . step (n' - len xs)
+		| otherwise   = Yield () $ Chunks $ genericDrop n' xs
+	step _ EOF = Yield () EOF
+	len = genericLength
+dropWhile :: Monad m => (a -> Bool) -> Iteratee e a m ()
+dropWhile p = liftI step where
+	step (Chunks xs) = case Prelude.dropWhile p xs of
+		[] -> Continue $ returnI . step
+		xs' -> Yield () $ Chunks xs'
+	step EOF = Yield () EOF
+span :: Monad m => (a -> Bool) -> Iteratee e a m [a]
+span f = liftI $ step [] where
+	step acc (Chunks xs) = case Prelude.span f xs of
+		(_, []) -> Continue $ returnI . step (acc ++ xs)
+		(head', tail') -> Yield (acc ++ head') (Chunks tail')
+	step acc EOF = Yield acc EOF
+
+-- | @break p = 'span' (not . p)@
+break :: Monad m => (a -> Bool) -> Iteratee e a m [a]
+break p = span $ not . p
+-- | Run an iteratee until it finishes, and return either the final value
+-- (if it succeeded) or the error (if it failed).
+run :: Monad m => Iteratee e a m b -> m (Either e b)
+run i = do
+	mStep <- runIteratee $ enumEOF ==<< i
+	case mStep of
+		Error err -> return $ Left err
+		Yield x _ -> return $ Right x
+		Continue _ -> error "run: divergent iteratee"
+-- | Print chunks as they're received from the enumerator, optionally
+-- printing empty chunks.
+printChunks :: (MIO.MonadIO m, Show a) => Bool -> Iteratee e a m ()
+printChunks printEmpty = continue step where
+	step (Chunks []) | not printEmpty = continue step
+	step (Chunks xs) = MIO.liftIO (print xs) >> continue step
+	step EOF = MIO.liftIO (putStrLn "EOF") >> yield () EOF
diff --git a/hs/Data/Enumerator/IO.hs b/hs/Data/Enumerator/IO.hs
new file mode 100644
--- /dev/null
+++ b/hs/Data/Enumerator/IO.hs
@@ -0,0 +1,75 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module: Data.Enumerator.IO
+-- Copyright: 2010 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-- Enumerator-based IO
+--
+-----------------------------------------------------------------------------
+module Data.Enumerator.IO
+	( enumHandle
+	, enumFile
+	, iterFile
+	, iterHandle
+	) where
+import Data.Enumerator
+import Control.Monad.IO.Class (liftIO)
+import qualified Control.Exception as E
+import qualified Data.ByteString as B
+import qualified Foreign as F
+import qualified System.IO as IO
+-- | Read bytes (in chunks of the given buffer size) from the handle, and
+-- stream them to an 'Iteratee'. If an exception occurs during file IO,
+-- enumeration will stop and 'Error' will be returned. Exceptions from the
+-- iteratee are not caught.
+enumHandle :: Integer -- ^ Buffer size
+           -> IO.Handle
+           -> Enumerator E.SomeException B.ByteString IO b
+enumHandle bufferSize h = Iteratee . F.allocaBytes size' . loop where
+	size' = fromInteger bufferSize
+	loop (Continue k) = read' k
+	loop step = const $ return step
+	read' k p = do
+		eitherN <- E.try $ IO.hGetBuf h p size'
+		case eitherN of
+			Left err -> return $ Error err
+			Right 0 -> return $ Continue k
+			Right n -> do
+				bytes <- B.packCStringLen (p, n)
+				step <- runIteratee (k (Chunks [bytes]))
+				loop step p
+-- | Opens a file path in binary mode, and passes the handle to 'enumHandle'.
+-- The file will be closed when the 'Iteratee' finishes.
+enumFile :: FilePath -> Enumerator E.SomeException B.ByteString IO b
+enumFile path s = Iteratee $ do
+	eitherH <- E.try $ IO.openBinaryFile path IO.ReadMode
+	case eitherH of
+		Left err -> return $ Error err
+		Right h -> E.finally
+			(runIteratee (enumHandle 4096 h s))
+			(IO.hClose h)
+-- | Read bytes from a stream and write them to a handle. If an exception
+-- occurs during file IO, enumeration will stop and 'Error' will be
+-- returned.
+iterHandle :: IO.Handle -> Iteratee E.SomeException B.ByteString IO ()
+iterHandle h = continue step where
+	step EOF = yield () EOF
+	step (Chunks bytes) = do
+		eitherErr <- liftIO . E.try $ mapM_ (B.hPut h) bytes
+		case eitherErr of
+			Left err -> throwError err
+			_ -> continue step
+-- | Opens a file path in binary mode, and passes the handle to 'iterHandle'.
+-- The file will be closed when the 'Iteratee' finishes.
+iterFile :: FilePath -> Iteratee E.SomeException B.ByteString IO ()
+iterFile path = Iteratee $ do
+	eitherH <- E.try $ IO.openBinaryFile path IO.WriteMode
+	case eitherH of
+		Left err -> return $ Error err
+		Right h -> E.finally
+			(runIteratee (iterHandle h))
+			(IO.hClose h)
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.
