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 +119/−0
- Examples/wc.hs +205/−0
- Setup.hs +2/−0
- enumerator.cabal +38/−0
- hs/Data/Enumerator.hs +336/−0
- hs/Data/Enumerator/IO.hs +75/−0
- license.txt +22/−0
+ 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’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’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’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,+-- “outer a” (@aOut@) and “inner a” (@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 “flatten” '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.