omnicodec-0.5.0.2: src/Data/ByteString/Iteratee.hs
-- |
-- Module : Data.ByteString.Iteratee
-- Copyright : (c) Magnus Therning 2009
-- License : BSD
-- Maintainer : Magnus Therning <magnus@therning.org>
--
-- An implementation of iteratee-based IO for @ByteString@s only, inspired by
-- code based on Oleg Kiselyov's paper <http://okmij.org/ftp/Haskell/Iteratee/>.
module Data.ByteString.Iteratee
(
-- * Types
Iteratee
, Enumerator
, Enumeratee
-- * Enumerators
, enumHandle
-- * Iteratees
-- ** Iteratee utilities
, run
-- ** Basic iteratees
, identity
, Data.ByteString.Iteratee.head
, heads
, peek
, Data.ByteString.Iteratee.dropWhile
, Data.ByteString.Iteratee.takeWhile
, Data.ByteString.Iteratee.drop
, Data.ByteString.Iteratee.take
-- ** Misc iteratees
, applyToN
, takeUntilDelim
-- ** Sinks
, sinkHandle
, sinkFile
-- * Enumeratees
, applyFun
) where
import Data.ByteString as BS
import Data.Word
import System.IO
import Control.Exception as CE
import System.IO.Error
import Data.ByteString.Iteratee.Internals as DBII
import Control.Monad.Trans
run :: Monad m => Iteratee m a -> m a
run iter = do
step <- runIteratee iter Eof
case step of
Done a _ -> return a
NeedAnotherChunk _ -> error "run: Iterator didn't finish on Eof"
-- | An enumerator of a file handle. It uses 'hGetNonBlocking' in order to
-- pass in available data as quickly as possible. Seeking is not supported.
enumHandle :: MonadIO m => Handle -> Enumerator m a
enumHandle h iter = let
tryRead i = do
res <- liftIO $ catchEof $ BS.hGetNonBlocking h 1024
case res of
Nothing -> doStepEof i
Just d -> if BS.null d
then waitAndRetry i
else doStep i d
doStepEof i = runIteratee i Eof >>= checkIfDone return
doStep i d = runIteratee i (Chunk d) >>= checkIfDone tryRead
waitAndRetry i = do
res <- liftIO $ catchEof $ hWaitForInput h (-1)
case res of
Nothing -> doStepEof i
Just _ -> tryRead i
catchEof a = do
res <- CE.try a
case res of
Left e -> if isEOFError e
then return Nothing
else throw e
Right v -> return $ Just v
in tryRead iter
-- | The identity iteratee, doesn't do anything.
identity :: Monad m => Iteratee m ()
identity = return ()
-- | Attempt to read the next element in the stream.
head :: Monad m => Iteratee m (Maybe Word8)
head = Iteratee step
where
step Eof = return $ Done Nothing Eof
step (Chunk bs) = if BS.null bs
then return $ NeedAnotherChunk Data.ByteString.Iteratee.head
else return $ Done (Just $ BS.head bs) (Chunk $ BS.tail bs)
-- | Attempts to match the give sequence of elements against the elements in
-- the stream. The matched elements are removed from the stream. Return the
-- number of elements that were matched.
heads :: Monad m => [Word8] -> Iteratee m Int
heads l = Iteratee $ step 0 l
where
step a _ Eof = return $ Done a Eof
step a [] s = return $ Done a s
step a l'@(w:ws) s@(Chunk bs) = if BS.null bs
then return $ NeedAnotherChunk $ Iteratee $ step a l'
else if w == BS.head bs
then step (succ a) ws (Chunk $ BS.tail bs)
else return $ Done a s
-- | Look at the first element in the stream, without removing it.
peek :: Monad m => Iteratee m (Maybe Word8)
peek = Iteratee step
where
step Eof = return $ Done Nothing Eof
step s@(Chunk bs) = if BS.null bs
then return $ NeedAnotherChunk $ Iteratee step
else return $ Done (Just $ BS.head bs) s
-- | Take elements off the stream while the predicate is true.
takeWhile :: Monad m => (Word8 -> Bool) -> Iteratee m ByteString
takeWhile test = Iteratee $ step empty
where
step a Eof = return $ Done a Eof
step a (Chunk bs) = let
(p1, p2) = BS.span test bs
in if BS.null p2
then return $ NeedAnotherChunk $ Iteratee $ step (a `append` p1)
else return $ Done (a `append` p1) (Chunk p2)
-- | Skip elements in the stream while the predicate is true.
dropWhile :: Monad m => (Word8 -> Bool) -> Iteratee m ()
dropWhile test = Iteratee step
where
step Eof = return $ Done () Eof
step (Chunk bs) = let
(_, p2) = BS.span test bs
in if BS.null p2
then return $ NeedAnotherChunk $ Data.ByteString.Iteratee.dropWhile test
else return $ Done () (Chunk p2)
-- | Take @n@ elements off the stream.
take :: Monad m => Int -> Iteratee m ByteString
take n = Iteratee $ step n empty
where
step 0 a s = return $ Done a s
step _ a Eof = return $ Done a Eof
step i a (Chunk bs) = let
(p1, p2) = BS.splitAt i bs
l1 = BS.length p1
in if BS.null p2
then return $ NeedAnotherChunk $ Iteratee $ step (i - l1) (a `append` p1)
else return $ Done (a `append` p1) (Chunk p2)
-- | Skip @n@ elements in the stream.
drop :: Monad m => Int -> Iteratee m ()
drop n = Iteratee $ step n
where
step 0 s = return $ Done () s
step _ Eof = return $ Done () Eof
step i (Chunk bs) = let
(p1, p2) = BS.splitAt i bs
l1 = BS.length p1
in if BS.null p2
then return $ NeedAnotherChunk $ Iteratee $ step (i - l1)
else return $ Done () (Chunk p2)
-- | Apply the given iteratee to a chunk of at most length @n@. Returns @Just
-- a@ on success, and @Nothing@ otherwise (i.e. if the iteratee required more
-- than @n@ elements). Only on success will the @n@ elements be consumed.
--
-- This iteratee is useful to implement restrictions on length, e.g. that the
-- first line must be at most 250 bytes.
--
-- /N.B./ This iteratee might "eat" an 'EOF', it's important to make sure that
-- the stream still is properly terminated.
applyToN :: Monad m => Int -> Iteratee m a -> Iteratee m (Maybe a)
applyToN n iter = let
wrap i = Iteratee $ \ s -> do
step <- runIteratee i s
case step of
NeedAnotherChunk i' -> return . NeedAnotherChunk $ wrap i'
Done v r -> do
step' <- runIteratee iter (Chunk v)
case step' of
Done v' r' ->
return $ Done (Just v') (combineChunks r' r)
NeedAnotherChunk _ ->
return $ Done Nothing (combineChunks (Chunk v) r)
-- partial definition only, relying on the usage above, "r" above is
-- always the second argument, and only it can ever be Eof
combineChunks c@(Chunk bs) Eof
| BS.null bs = Eof -- local take will return empty on EOF, so keep it
| otherwise = c
combineChunks (Chunk lbs) (Chunk rbs) = Chunk $ lbs `append` rbs
combineChunks Eof _ = error "applyToN: internal error"
in wrap (Data.ByteString.Iteratee.take n)
-- | Take elements until the given delimiter is encountered. The characters of
-- the delimiter are discarded.
takeUntilDelim :: Monad m => [Word8] -> Iteratee m ByteString
takeUntilDelim d = let
h = Prelude.head d
l = Prelude.length d
step1 a = do
r <- Data.ByteString.Iteratee.takeWhile (/= h)
m <- heads d
step2 (a `append` r) m
step2 a m
| m == 0 = return a
| m < l = step1 (a `append` (pack $ Prelude.take m d))
| otherwise = return a
in step1 empty
sinkHandle :: MonadIO m => Handle -> Iteratee m ()
sinkHandle h = Iteratee step
where
step Eof = return $ Done () Eof
step (Chunk bs) = do
liftIO $ hPut h bs
return $ NeedAnotherChunk $ Iteratee step
sinkFile :: MonadIO m => FilePath -> Iteratee m ()
sinkFile fp = do
h <- liftIO $ openBinaryFile fp ReadMode
sinkHandle h
liftIO $ hClose h
-- | An enumeratee which applies a function to each chunk before passing it on.
applyFun :: Monad m => (ByteString -> ByteString ) -> Enumeratee m a
applyFun f i = Iteratee $ step i
where
step iter Eof = do
ir <- runIteratee iter Eof
case ir of
Done a _ -> return $ Done (Iteratee $ \ _ -> return $ Done a Eof) Eof
NeedAnotherChunk _ -> error "applyFun: internal iteratee diverges on Eof"
step iter (Chunk bs) = let
nbs = f bs
in do
ir <- runIteratee iter (Chunk nbs)
case ir of
Done a _ -> return $ Done (Iteratee $ \ _ -> return $ Done a (Chunk BS.empty)) (Chunk BS.empty)
NeedAnotherChunk niter -> return $ NeedAnotherChunk $ Iteratee (step niter)