packages feed

enumerator (empty) → 0.1

raw patch · 7 files changed

+797/−0 lines, 7 filesdep +basedep +bytestringdep +transformerssetup-changed

Dependencies added: base, bytestring, transformers

Files

+ Examples/cat.hs view
@@ -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 ()
+ Examples/wc.hs view
@@ -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+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ enumerator.cabal view
@@ -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
+ hs/Data/Enumerator.hs view
@@ -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
+ hs/Data/Enumerator/IO.hs view
@@ -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)
+ license.txt view
@@ -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.