-- | -- Module: Data.Enumerator.Binary -- Copyright: 2010-2011 John Millikin -- License: MIT -- -- Maintainer: jmillikin@gmail.com -- Portability: portable -- -- Byte-oriented alternatives to "Data.Enumerator.List". Note that the -- enumeratees in this module must unpack their inputs to work properly. If -- you do not need to handle leftover input on a byte-by-byte basis, the -- chunk-oriented versions will be much faster. -- -- This module is intended to be imported qualified: -- -- @ -- import qualified Data.Enumerator.Binary as EB -- @ -- -- Since: 0.4.5 module Data.Enumerator.Binary ( -- * IO enumHandle , enumHandleRange , enumFile , enumFileRange , iterHandle -- * List analogues -- ** Folds , fold , foldM -- ** Maps , Data.Enumerator.Binary.map , Data.Enumerator.Binary.mapM , Data.Enumerator.Binary.mapM_ , Data.Enumerator.Binary.concatMap , concatMapM -- ** Accumulating maps , mapAccum , mapAccumM , concatMapAccum , concatMapAccumM -- ** Infinite streams , Data.Enumerator.Binary.iterate , iterateM , Data.Enumerator.Binary.repeat , repeatM -- ** Bounded streams , Data.Enumerator.Binary.replicate , replicateM , generateM , unfold , unfoldM -- ** Dropping input , Data.Enumerator.Binary.drop , Data.Enumerator.Binary.dropWhile , Data.Enumerator.Binary.filter , filterM -- ** Consumers , Data.Enumerator.Binary.head , head_ , Data.Enumerator.Binary.take , takeWhile , consume -- ** Zipping , zip , zip3 , zip4 , zip5 , zip6 , zip7 , zipWith , zipWith3 , zipWith4 , zipWith5 , zipWith6 , zipWith7 -- ** Unsorted , require , isolate , splitWhen ) where import Prelude hiding (head, drop, takeWhile, mapM_, zip, zip3, zipWith, zipWith3) import qualified Control.Exception as Exc import qualified Control.Monad as CM import Control.Monad (liftM) import Control.Monad.IO.Class (MonadIO) import Control.Monad.Trans.Class (lift) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import Data.Monoid (mappend) import Data.Word (Word8) import qualified System.IO as IO import System.IO.Error (isEOFError) import Data.Enumerator hiding ( head, drop, iterateM, repeatM, replicateM , generateM, filterM, consume, foldM , concatMapM) import qualified Data.Enumerator.List as EL -- | Consume the entire input stream with a strict left fold, one byte -- at a time. -- -- Since: 0.4.8 fold :: Monad m => (b -> Word8 -> b) -> b -> Iteratee B.ByteString m b fold step = EL.fold (B.foldl' step) -- | Consume the entire input stream with a strict monadic left fold, one -- byte at a time. -- -- Since: 0.4.8 foldM :: Monad m => (b -> Word8 -> m b) -> b -> Iteratee B.ByteString m b foldM step = EL.foldM (\b bytes -> CM.foldM step b (B.unpack bytes)) -- | Enumerates a stream of bytes by repeatedly applying a function to -- some state. -- -- Similar to 'Data.Enumerator.Binary.iterate'. -- -- Since: 0.4.8 unfold :: Monad m => (s -> Maybe (Word8, s)) -> s -> Enumerator B.ByteString m b unfold f = checkContinue1 $ \loop s k -> case f s of Nothing -> continue k Just (b, s') -> k (Chunks [B.singleton b]) >>== loop s' -- | Enumerates a stream of bytes by repeatedly applying a computation to -- some state. -- -- Similar to 'iterateM'. -- -- Since: 0.4.8 unfoldM :: Monad m => (s -> m (Maybe (Word8, s))) -> s -> Enumerator B.ByteString m b unfoldM f = checkContinue1 $ \loop s k -> do fs <- lift (f s) case fs of Nothing -> continue k Just (b, s') -> k (Chunks [B.singleton b]) >>== loop s' -- | @'Data.Enumerator.Binary.map' f@ applies /f/ to each input byte and -- feeds the resulting outputs to the inner iteratee. -- -- Since: 0.4.8 map :: Monad m => (Word8 -> Word8) -> Enumeratee B.ByteString B.ByteString m b map f = Data.Enumerator.Binary.concatMap (\x -> B.singleton (f x)) -- | @'Data.Enumerator.Binary.mapM' f@ applies /f/ to each input byte and -- feeds the resulting outputs to the inner iteratee. -- -- Since: 0.4.8 mapM :: Monad m => (Word8 -> m Word8) -> Enumeratee B.ByteString B.ByteString m b mapM f = Data.Enumerator.Binary.concatMapM (\x -> liftM B.singleton (f x)) -- | @'Data.Enumerator.Binary.mapM_' f@ applies /f/ to each input byte, and -- discards the results. -- -- Since: 0.4.11 mapM_ :: Monad m => (Word8 -> m ()) -> Iteratee B.ByteString m () mapM_ f = foldM (\_ x -> f x >> return ()) () -- | @'Data.Enumerator.Binary.concatMap' f@ applies /f/ to each input byte -- and feeds the resulting outputs to the inner iteratee. -- -- Since: 0.4.8 concatMap :: Monad m => (Word8 -> B.ByteString) -> Enumeratee B.ByteString B.ByteString m b concatMap f = Data.Enumerator.Binary.concatMapM (return . f) -- | @'concatMapM' f@ applies /f/ to each input byte and feeds the -- resulting outputs to the inner iteratee. -- -- Since: 0.4.8 concatMapM :: Monad m => (Word8 -> m B.ByteString) -> Enumeratee B.ByteString B.ByteString m b concatMapM f = checkDone (continue . step) where step k EOF = yield (Continue k) EOF step k (Chunks xs) = loop k (BL.unpack (BL.fromChunks xs)) loop k [] = continue (step k) loop k (x:xs) = do fx <- lift (f x) k (Chunks [fx]) >>== checkDoneEx (Chunks [B.pack xs]) (\k' -> loop k' xs) -- | Similar to 'Data.Enumerator.Binary.concatMap', but with a stateful step -- function. -- -- Since: 0.4.11 concatMapAccum :: Monad m => (s -> Word8 -> (s, B.ByteString)) -> s -> Enumeratee B.ByteString B.ByteString m b concatMapAccum f s0 = checkDone (continue . step s0) where step _ k EOF = yield (Continue k) EOF step s k (Chunks xs) = loop s k xs loop s k [] = continue (step s k) loop s k (x:xs) = case B.uncons x of Nothing -> loop s k xs Just (b, x') -> case f s b of (s', ai) -> k (Chunks [ai]) >>== checkDoneEx (Chunks (x':xs)) (\k' -> loop s' k' (x':xs)) -- | Similar to 'concatMapM', but with a stateful step function. -- -- Since: 0.4.11 concatMapAccumM :: Monad m => (s -> Word8 -> m (s, B.ByteString)) -> s -> Enumeratee B.ByteString B.ByteString m b concatMapAccumM f s0 = checkDone (continue . step s0) where step _ k EOF = yield (Continue k) EOF step s k (Chunks xs) = loop s k xs loop s k [] = continue (step s k) loop s k (x:xs) = case B.uncons x of Nothing -> loop s k xs Just (b, x') -> do (s', ai) <- lift (f s b) k (Chunks [ai]) >>== checkDoneEx (Chunks (x':xs)) (\k' -> loop s' k' (x':xs)) -- | Similar to 'Data.Enumerator.Binary.map', but with a stateful step -- function. -- -- Since: 0.4.9 mapAccum :: Monad m => (s -> Word8 -> (s, Word8)) -> s -> Enumeratee B.ByteString B.ByteString m b mapAccum f = concatMapAccum (\s w -> case f s w of (s', w') -> (s', B.singleton w')) -- | Similar to 'Data.Enumerator.Binary.mapM', but with a stateful step -- function. -- -- Since: 0.4.9 mapAccumM :: Monad m => (s -> Word8 -> m (s, Word8)) -> s -> Enumeratee B.ByteString B.ByteString m b mapAccumM f = concatMapAccumM (\s w -> do (s', w') <- f s w return (s', B.singleton w')) -- | @'Data.Enumerator.Binary.iterate' f x@ enumerates an infinite stream of -- repeated applications of /f/ to /x/. -- -- Analogous to 'Prelude.iterate'. -- -- Since: 0.4.8 iterate :: Monad m => (Word8 -> Word8) -> Word8 -> Enumerator B.ByteString m b iterate f = checkContinue1 $ \loop s k -> k (Chunks [B.singleton s]) >>== loop (f s) -- | Similar to 'Data.Enumerator.Binary.iterate', except the iteration -- function is monadic. -- -- Since: 0.4.8 iterateM :: Monad m => (Word8 -> m Word8) -> Word8 -> Enumerator B.ByteString m b iterateM f base = worker (return base) where worker = checkContinue1 $ \loop m_byte k -> do byte <- lift m_byte k (Chunks [B.singleton byte]) >>== loop (f byte) -- | Enumerates an infinite stream of a single byte. -- -- Analogous to 'Prelude.repeat'. -- -- Since: 0.4.8 repeat :: Monad m => Word8 -> Enumerator B.ByteString m b repeat byte = EL.repeat (B.singleton byte) -- | Enumerates an infinite stream of byte. Each byte is computed by the -- underlying monad. -- -- Since: 0.4.8 repeatM :: Monad m => m Word8 -> Enumerator B.ByteString m b repeatM next = EL.repeatM (liftM B.singleton next) -- | @'Data.Enumerator.Binary.replicate' n x@ enumerates a stream containing -- /n/ copies of /x/. -- -- Since: 0.4.8 replicate :: Monad m => Integer -> Word8 -> Enumerator B.ByteString m b replicate n byte = EL.replicate n (B.singleton byte) -- | @'replicateM' n m_x@ enumerates a stream of /n/ bytes, with each byte -- computed by /m_x/. -- -- Since: 0.4.8 replicateM :: Monad m => Integer -> m Word8 -> Enumerator B.ByteString m b replicateM n next = EL.replicateM n (liftM B.singleton next) -- | Like 'repeatM', except the computation may terminate the stream by -- returning 'Nothing'. -- -- Since: 0.4.8 generateM :: Monad m => m (Maybe Word8) -> Enumerator B.ByteString m b generateM next = EL.generateM (liftM (liftM B.singleton) next) -- | Applies a predicate to the stream. The inner iteratee only receives -- characters for which the predicate is @True@. -- -- Since: 0.4.8 filter :: Monad m => (Word8 -> Bool) -> Enumeratee B.ByteString B.ByteString m b filter p = Data.Enumerator.Binary.concatMap (\x -> B.pack [x | p x]) -- | Applies a monadic predicate to the stream. The inner iteratee only -- receives bytes for which the predicate returns @True@. -- -- Since: 0.4.8 filterM :: Monad m => (Word8 -> m Bool) -> Enumeratee B.ByteString B.ByteString m b filterM p = Data.Enumerator.Binary.concatMapM (\x -> liftM B.pack (CM.filterM p [x])) -- | @'Data.Enumerator.Binary.take' n@ extracts the next /n/ bytes from the -- stream, as a lazy -- ByteString. -- -- Since: 0.4.5 take :: Monad m => Integer -> Iteratee B.ByteString m BL.ByteString take n | n <= 0 = return BL.empty take n = continue (loop id n) where loop acc n' (Chunks xs) = iter where lazy = BL.fromChunks xs len = toInteger (BL.length lazy) iter = if len < n' then continue (loop (acc . (BL.append lazy)) (n' - len)) else let (xs', extra) = BL.splitAt (fromInteger n') lazy in yield (acc xs') (toChunks extra) loop acc _ EOF = yield (acc BL.empty) EOF -- | @'takeWhile' p@ extracts input from the stream until the first byte which -- does not match the predicate. -- -- Since: 0.4.5 takeWhile :: Monad m => (Word8 -> Bool) -> Iteratee B.ByteString m BL.ByteString takeWhile p = continue (loop id) where loop acc (Chunks []) = continue (loop acc) loop acc (Chunks xs) = iter where lazy = BL.fromChunks xs (xs', extra) = BL.span p lazy iter = if BL.null extra then continue (loop (acc . (BL.append lazy))) else yield (acc xs') (toChunks extra) loop acc EOF = yield (acc BL.empty) EOF -- | @'consume' = 'takeWhile' (const True)@ -- -- Since: 0.4.5 consume :: Monad m => Iteratee B.ByteString m BL.ByteString consume = continue (loop id) where loop acc (Chunks []) = continue (loop acc) loop acc (Chunks xs) = iter where lazy = BL.fromChunks xs iter = continue (loop (acc . (BL.append lazy))) loop acc EOF = yield (acc BL.empty) EOF -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. -- -- Analogous to 'Data.List.zip'. -- -- Since: 0.4.14 zip :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m (b1, b2) zip i1 i2 = continue step where step (Chunks []) = continue step step stream@(Chunks _) = do let enumStream s = case s of Continue k -> k stream Yield b extra -> yield b (mappend extra stream) Error err -> throwError err s1 <- lift (runIteratee (enumStream ==<< i1)) s2 <- lift (runIteratee (enumStream ==<< i2)) case (s1, s2) of (Continue k1, Continue k2) -> zip (continue k1) (continue k2) (Yield b1 _, Continue k2) -> zip (yield b1 (Chunks [])) (continue k2) (Continue k1, Yield b2 _) -> zip (continue k1) (yield b2 (Chunks [])) (Yield b1 ex1, Yield b2 ex2) -> yield (b1, b2) (shorter ex1 ex2) (Error err, _) -> throwError err (_, Error err) -> throwError err step EOF = do b1 <- enumEOF =<< lift (runIteratee i1) b2 <- enumEOF =<< lift (runIteratee i2) return (b1, b2) shorter c1@(Chunks xs) c2@(Chunks ys) = let xs' = B.concat xs ys' = B.concat ys in if B.length xs' < B.length ys' then c1 else c2 shorter _ _ = EOF -- | Pass input from a stream through three iteratees at once. Excess input is -- yielded if it was not consumed by any iteratee. -- -- Analogous to 'Data.List.zip3'. -- -- Since: 0.4.14 zip3 :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m (b1, b2, b3) zip3 i1 i2 i3 = do (b1, (b2, b3)) <- zip i1 (zip i2 i3) return (b1, b2, b3) {-# INLINE zip3 #-} -- | Pass input from a stream through four iteratees at once. Excess input is -- yielded if it was not consumed by any iteratee. -- -- Analogous to 'Data.List.zip4'. -- -- Since: 0.4.14 zip4 :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m (b1, b2, b3, b4) zip4 i1 i2 i3 i4 = do (b1, (b2, b3, b4)) <- zip i1 (zip3 i2 i3 i4) return (b1, b2, b3, b4) {-# INLINE zip4 #-} -- | Pass input from a stream through five iteratees at once. Excess input is -- yielded if it was not consumed by any iteratee. -- -- Analogous to 'Data.List.zip5'. -- -- Since: 0.4.14 zip5 :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m (b1, b2, b3, b4, b5) zip5 i1 i2 i3 i4 i5 = do (b1, (b2, b3, b4, b5)) <- zip i1 (zip4 i2 i3 i4 i5) return (b1, b2, b3, b4, b5) {-# INLINE zip5 #-} -- | Pass input from a stream through six iteratees at once. Excess input is -- yielded if it was not consumed by any iteratee. -- -- Analogous to 'Data.List.zip6'. -- -- Since: 0.4.14 zip6 :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m b6 -> Iteratee B.ByteString m (b1, b2, b3, b4, b5, b6) zip6 i1 i2 i3 i4 i5 i6 = do (b1, (b2, b3, b4, b5, b6)) <- zip i1 (zip5 i2 i3 i4 i5 i6) return (b1, b2, b3, b4, b5, b6) {-# INLINE zip6 #-} -- | Pass input from a stream through seven iteratees at once. Excess input is -- yielded if it was not consumed by any iteratee. -- -- Analogous to 'Data.List.zip7'. -- -- Since: 0.4.14 zip7 :: Monad m => Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m b6 -> Iteratee B.ByteString m b7 -> Iteratee B.ByteString m (b1, b2, b3, b4, b5, b6, b7) zip7 i1 i2 i3 i4 i5 i6 i7 = do (b1, (b2, b3, b4, b5, b6, b7)) <- zip i1 (zip6 i2 i3 i4 i5 i6 i7) return (b1, b2, b3, b4, b5, b6, b7) {-# INLINE zip7 #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith'. -- -- Since: 0.4.14 zipWith :: Monad m => (b1 -> b2 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m c zipWith f i1 i2 = do (b1, b2) <- zip i1 i2 return (f b1 b2) {-# INLINE zipWith #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith3'. -- -- Since: 0.4.14 zipWith3 :: Monad m => (b1 -> b2 -> b3 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m c zipWith3 f i1 i2 i3 = do (b1, b2, b3) <- zip3 i1 i2 i3 return (f b1 b2 b3) {-# INLINE zipWith3 #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith4'. -- -- Since: 0.4.14 zipWith4 :: Monad m => (b1 -> b2 -> b3 -> b4 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m c zipWith4 f i1 i2 i3 i4 = do (b1, b2, b3, b4) <- zip4 i1 i2 i3 i4 return (f b1 b2 b3 b4) {-# INLINE zipWith4 #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith5'. -- -- Since: 0.4.14 zipWith5 :: Monad m => (b1 -> b2 -> b3 -> b4 -> b5 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m c zipWith5 f i1 i2 i3 i4 i5 = do (b1, b2, b3, b4, b5) <- zip5 i1 i2 i3 i4 i5 return (f b1 b2 b3 b4 b5) {-# INLINE zipWith5 #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith6'. -- -- Since: 0.4.14 zipWith6 :: Monad m => (b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m b6 -> Iteratee B.ByteString m c zipWith6 f i1 i2 i3 i4 i5 i6 = do (b1, b2, b3, b4, b5, b6) <- zip6 i1 i2 i3 i4 i5 i6 return (f b1 b2 b3 b4 b5 b6) {-# INLINE zipWith6 #-} -- | Pass input from a stream through two iteratees at once. Excess input is -- yielded if it was not consumed by either iteratee. Output from the -- iteratees is combined with a user-provided function. -- -- Analogous to 'Data.List.zipWith7'. -- -- Since: 0.4.14 zipWith7 :: Monad m => (b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7 -> c) -> Iteratee B.ByteString m b1 -> Iteratee B.ByteString m b2 -> Iteratee B.ByteString m b3 -> Iteratee B.ByteString m b4 -> Iteratee B.ByteString m b5 -> Iteratee B.ByteString m b6 -> Iteratee B.ByteString m b7 -> Iteratee B.ByteString m c zipWith7 f i1 i2 i3 i4 i5 i6 i7 = do (b1, b2, b3, b4, b5, b6, b7) <- zip7 i1 i2 i3 i4 i5 i6 i7 return (f b1 b2 b3 b4 b5 b6 b7) {-# INLINE zipWith7 #-} -- | Get the next byte from the stream, or 'Nothing' if the stream has -- ended. -- -- Since: 0.4.5 head :: Monad m => Iteratee B.ByteString m (Maybe Word8) head = continue loop where loop (Chunks xs) = case BL.uncons (BL.fromChunks xs) of Just (char, extra) -> yield (Just char) (toChunks extra) Nothing -> head loop EOF = yield Nothing EOF -- | Get the next element from the stream, or raise an error if the stream -- has ended. -- -- Since: 0.4.14 head_ :: Monad m => Iteratee B.ByteString m Word8 head_ = head >>= \x -> case x of Just x' -> return x' Nothing -> throwError (Exc.ErrorCall "head_: stream has ended") -- | @'drop' n@ ignores /n/ bytes of input from the stream. -- -- Since: 0.4.5 drop :: Monad m => Integer -> Iteratee B.ByteString m () drop n | n <= 0 = return () drop n = continue (loop n) where loop n' (Chunks xs) = iter where lazy = BL.fromChunks xs len = toInteger (BL.length lazy) iter = if len < n' then drop (n' - len) else yield () (toChunks (BL.drop (fromInteger n') lazy)) loop _ EOF = yield () EOF -- | @'Data.Enumerator.Binary.dropWhile' p@ ignores input from the stream -- until the first byte which does not match the predicate. -- -- Since: 0.4.5 dropWhile :: Monad m => (Word8 -> Bool) -> Iteratee B.ByteString m () dropWhile p = continue loop where loop (Chunks xs) = iter where lazy = BL.dropWhile p (BL.fromChunks xs) iter = if BL.null lazy then continue loop else yield () (toChunks lazy) loop EOF = yield () EOF -- | @'require' n@ buffers input until at least /n/ bytes are available, or -- throws an error if the stream ends early. -- -- Since: 0.4.5 require :: Monad m => Integer -> Iteratee B.ByteString m () require n | n <= 0 = return () require n = continue (loop id n) where loop acc n' (Chunks xs) = iter where lazy = BL.fromChunks xs len = toInteger (BL.length lazy) iter = if len < n' then continue (loop (acc . (BL.append lazy)) (n' - len)) else yield () (toChunks (acc lazy)) loop _ _ EOF = throwError (Exc.ErrorCall "require: Unexpected EOF") -- | @'isolate' n@ reads at most /n/ bytes from the stream, and passes them -- to its iteratee. If the iteratee finishes early, bytes continue to be -- consumed from the outer stream until /n/ have been consumed. -- -- Since: 0.4.5 isolate :: Monad m => Integer -> Enumeratee B.ByteString B.ByteString m b isolate n step | n <= 0 = return step isolate n (Continue k) = continue loop where loop (Chunks []) = continue loop loop (Chunks xs) = iter where lazy = BL.fromChunks xs len = toInteger (BL.length lazy) iter = if len <= n then k (Chunks xs) >>== isolate (n - len) else let (s1, s2) = BL.splitAt (fromInteger n) lazy in k (toChunks s1) >>== (\step -> yield step (toChunks s2)) loop EOF = k EOF >>== (\step -> yield step EOF) isolate n step = drop n >> return step -- | Split on bytes satisfying a given predicate. -- -- Since: 0.4.8 splitWhen :: Monad m => (Word8 -> Bool) -> Enumeratee B.ByteString B.ByteString m b splitWhen p = loop where loop = checkDone step step k = isEOF >>= \eof -> if eof then yield (Continue k) EOF else do lazy <- takeWhile (not . p) let bytes = B.concat (BL.toChunks lazy) eof <- isEOF drop 1 if BL.null lazy && eof then yield (Continue k) EOF else k (Chunks [bytes]) >>== loop -- | 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. -- -- This enumerator blocks until at least one byte is available from the -- handle, and might read less than the maximum buffer size in some -- cases. -- -- The handle should be opened with no encoding, and in 'IO.ReadMode' or -- 'IO.ReadWriteMode'. -- -- Since: 0.4.5 enumHandle :: MonadIO m => Integer -- ^ Buffer size -> IO.Handle -> Enumerator B.ByteString m b enumHandle bufferSize h = checkContinue0 $ \loop k -> do let intSize = fromInteger bufferSize bytes <- tryIO (getBytes h intSize) if B.null bytes then continue k else k (Chunks [bytes]) >>== loop -- | 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. -- -- This enumerator blocks until at least one byte is available from the -- handle, and might read less than the maximum buffer size in some -- cases. -- -- The handle should be opened with no encoding, and in 'IO.ReadMode' or -- 'IO.ReadWriteMode'. -- -- If an offset is specified, the handle will be seeked to that offset -- before reading. If the handle cannot be seeked, an error will be -- thrown. -- -- If a maximum count is specified, the number of bytes read will not -- exceed that count. -- -- Since: 0.4.8 enumHandleRange :: MonadIO m => Integer -- ^ Buffer size -> Maybe Integer -- ^ Offset -> Maybe Integer -- ^ Maximum count -> IO.Handle -> Enumerator B.ByteString m b enumHandleRange bufferSize offset count h s = seek >> enum where seek = case offset of Nothing -> return () Just off -> tryIO (IO.hSeek h IO.AbsoluteSeek off) enum = case count of Just n -> enumRange n s Nothing -> enumHandle bufferSize h s enumRange = checkContinue1 $ \loop n k -> let rem = fromInteger (min bufferSize n) keepGoing = do bytes <- tryIO (getBytes h rem) if B.null bytes then continue k else feed bytes feed bs = k (Chunks [bs]) >>== loop (n - (toInteger (B.length bs))) in if rem <= 0 then continue k else keepGoing getBytes :: IO.Handle -> Int -> IO B.ByteString getBytes h n = do hasInput <- Exc.catch (IO.hWaitForInput h (-1)) (\err -> if isEOFError err then return False else Exc.throwIO err) if hasInput then B.hGetNonBlocking h n else return B.empty -- | Opens a file path in binary mode, and passes the handle to -- 'enumHandle'. The file will be closed when enumeration finishes. -- -- Since: 0.4.5 enumFile :: FilePath -> Enumerator B.ByteString IO b enumFile path = enumFileRange path Nothing Nothing -- | Opens a file path in binary mode, and passes the handle to -- 'enumHandleRange'. The file will be closed when enumeration finishes. -- -- Since: 0.4.8 enumFileRange :: FilePath -> Maybe Integer -- ^ Offset -> Maybe Integer -- ^ Maximum count -> Enumerator B.ByteString IO b enumFileRange path offset count step = do h <- tryIO (IO.openBinaryFile path IO.ReadMode) let iter = enumHandleRange 4096 offset count h step Iteratee (Exc.finally (runIteratee iter) (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. -- -- The handle should be opened with no encoding, and in 'IO.WriteMode' or -- 'IO.ReadWriteMode'. -- -- Since: 0.4.5 iterHandle :: MonadIO m => IO.Handle -> Iteratee B.ByteString m () iterHandle h = continue step where step EOF = yield () EOF step (Chunks []) = continue step step (Chunks bytes) = do tryIO (CM.mapM_ (B.hPut h) bytes) continue step toChunks :: BL.ByteString -> Stream B.ByteString toChunks = Chunks . BL.toChunks