{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE RankNTypes #-} -- | Convert a stream of blaze-builder @Builder@s into a stream of @ByteString@s. -- -- Works with both blaze-builder < 0.4's @Builder@s and -- 'Data.ByteString.Builder.Builder'. -- -- Adapted from blaze-builder-enumerator, written by myself and Simon Meier. -- -- Note that the functions here can work in any monad built on top of @IO@ or -- @ST@. -- -- Since 1.1.7.0 -- module Data.Conduit.ByteString.Builder ( -- * Conduits from builders to bytestrings builderToByteString , unsafeBuilderToByteString , builderToByteStringWith -- ** Flush , builderToByteStringFlush , builderToByteStringWithFlush -- * Buffers , Buffer -- ** Status information , freeSize , sliceSize , bufferSize -- ** Creation and modification , allocBuffer , reuseBuffer , nextSlice -- ** Conversion to bytestings , unsafeFreezeBuffer , unsafeFreezeNonEmptyBuffer -- * Buffer allocation strategies , BufferAllocStrategy , allNewBuffersStrategy , reuseBufferStrategy ) where import Data.Conduit import Control.Monad (unless, liftM) import Control.Monad.Trans.Class (lift, MonadTrans) import qualified Data.ByteString as S import Control.Monad.Primitive (PrimMonad, unsafePrimToPrim) import Control.Monad.Base (MonadBase, liftBase) import Data.Streaming.ByteString.Builder.Class unsafeLiftIO :: (MonadBase base m, PrimMonad base) => IO a -> m a unsafeLiftIO = liftBase . unsafePrimToPrim -- | Incrementally execute builders and pass on the filled chunks as -- bytestrings. builderToByteString :: (MonadBase base m, PrimMonad base, StreamingBuilder b) => Conduit b m S.ByteString builderToByteString = builderToByteStringWith defaultStrategy {-# INLINE builderToByteString #-} -- | -- -- Since 0.0.2 builderToByteStringFlush :: (MonadBase base m, PrimMonad base, StreamingBuilder b) => Conduit (Flush b) m (Flush S.ByteString) builderToByteStringFlush = builderToByteStringWithFlush defaultStrategy {-# INLINE builderToByteStringFlush #-} -- | Incrementally execute builders on the given buffer and pass on the filled -- chunks as bytestrings. Note that, if the given buffer is too small for the -- execution of a build step, a larger one will be allocated. -- -- WARNING: This conduit yields bytestrings that are NOT -- referentially transparent. Their content will be overwritten as soon -- as control is returned from the inner sink! unsafeBuilderToByteString :: (MonadBase base m, PrimMonad base, StreamingBuilder b) => IO Buffer -- action yielding the inital buffer. -> Conduit b m S.ByteString unsafeBuilderToByteString = builderToByteStringWith . reuseBufferStrategy {-# INLINE unsafeBuilderToByteString #-} -- | A conduit that incrementally executes builders and passes on the -- filled chunks as bytestrings to an inner sink. -- -- INV: All bytestrings passed to the inner sink are non-empty. builderToByteStringWith :: (MonadBase base m, PrimMonad base, StreamingBuilder b) => BufferAllocStrategy -> Conduit b m S.ByteString builderToByteStringWith = helper (liftM (fmap Chunk) await) yield' where yield' Flush = return () yield' (Chunk bs) = yield bs {-# INLINE builderToByteStringWith #-} -- | -- -- Since 0.0.2 builderToByteStringWithFlush :: (MonadBase base m, PrimMonad base, StreamingBuilder b) => BufferAllocStrategy -> Conduit (Flush b) m (Flush S.ByteString) builderToByteStringWithFlush = helper await yield {-# INLINE builderToByteStringWithFlush #-} helper :: (MonadBase base m, PrimMonad base, Monad (t m), MonadTrans t, StreamingBuilder b) => t m (Maybe (Flush b)) -> (Flush S.ByteString -> t m ()) -> BufferAllocStrategy -> t m () helper await' yield' strat = do (recv, finish) <- lift $ unsafeLiftIO $ newBuilderRecv strat let loop = await' >>= maybe finish' cont finish' = do mbs <- lift $ unsafeLiftIO finish maybe (return ()) (yield' . Chunk) mbs cont fbuilder = do let builder = case fbuilder of Flush -> builderFlush Chunk b -> b popper <- lift $ unsafeLiftIO $ recv builder let cont' = do bs <- lift $ unsafeLiftIO popper unless (S.null bs) $ do yield' (Chunk bs) cont' cont' case fbuilder of Flush -> yield' Flush Chunk _ -> return () loop loop {-# INLINE helper #-}