packages feed

streaming-commons 0.1.9.1 → 0.1.10.0

raw patch · 8 files changed

+548/−26 lines, 8 filesdep ~basedep ~blaze-builderdep ~textPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, blaze-builder, text

API changes (from Hackage documentation)

+ Data.Streaming.ByteString.Builder: allNewBuffersStrategy :: Int -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: allocBuffer :: Int -> IO Buffer
+ Data.Streaming.ByteString.Builder: bufferSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: data Buffer
+ Data.Streaming.ByteString.Builder: defaultStrategy :: BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: freeSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: newByteStringBuilderRecv :: BufferAllocStrategy -> IO (BuilderRecv, BuilderFinish)
+ Data.Streaming.ByteString.Builder: nextSlice :: Int -> Buffer -> Maybe Buffer
+ Data.Streaming.ByteString.Builder: reuseBuffer :: Buffer -> Buffer
+ Data.Streaming.ByteString.Builder: reuseBufferStrategy :: IO Buffer -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: sliceSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
+ Data.Streaming.ByteString.Builder: type BuilderFinish = IO (Maybe ByteString)
+ Data.Streaming.ByteString.Builder: type BuilderPopper = IO ByteString
+ Data.Streaming.ByteString.Builder: type BuilderRecv = Builder -> IO BuilderPopper
+ Data.Streaming.ByteString.Builder: unsafeFreezeBuffer :: Buffer -> ByteString
+ Data.Streaming.ByteString.Builder: unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe ByteString
+ Data.Streaming.ByteString.Builder.Buffer: Buffer :: {-# UNPACK #-} !(ForeignPtr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> Buffer
+ Data.Streaming.ByteString.Builder.Buffer: allNewBuffersStrategy :: Int -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: allocBuffer :: Int -> IO Buffer
+ Data.Streaming.ByteString.Builder.Buffer: bufferSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: data Buffer
+ Data.Streaming.ByteString.Builder.Buffer: defaultStrategy :: BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: freeSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: nextSlice :: Int -> Buffer -> Maybe Buffer
+ Data.Streaming.ByteString.Builder.Buffer: reuseBuffer :: Buffer -> Buffer
+ Data.Streaming.ByteString.Builder.Buffer: reuseBufferStrategy :: IO Buffer -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: sliceSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
+ Data.Streaming.ByteString.Builder.Buffer: unsafeFreezeBuffer :: Buffer -> ByteString
+ Data.Streaming.ByteString.Builder.Buffer: unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe ByteString
+ Data.Streaming.ByteString.Builder.Buffer: updateEndOfSlice :: Buffer -> Ptr Word8 -> Buffer
+ Data.Streaming.ByteString.Builder.Class: builderFlush :: StreamingBuilder b => b
+ Data.Streaming.ByteString.Builder.Class: class Monoid b => StreamingBuilder b
+ Data.Streaming.ByteString.Builder.Class: instance StreamingBuilder Builder
+ Data.Streaming.ByteString.Builder.Class: newBuilderRecv :: StreamingBuilder b => BufferAllocStrategy -> IO (b -> IO BuilderPopper, BuilderFinish)
- Data.Streaming.Blaze: data Buffer :: *
+ Data.Streaming.Blaze: data Buffer

Files

ChangeLog.md view
@@ -1,3 +1,7 @@+## 0.1.10++Support blaze-builder >= 0.4.  Add `newByteStringBuilderRecv` to Data.Streaming.ByteString.Builder; add modules Data.Streaming.ByteString.Builder.Buffer and  Data.Streaming.ByteString.Builder.Class.+ ## 0.1.9  Add Data.Streaming.ByteString.Builder
Data/Streaming/Blaze.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE RankNTypes #-}@@ -5,6 +6,9 @@ -- -- Adapted from blaze-builder-enumerator, written by myself and Simon Meier. --+-- Note: if you have blaze-builder >= 0.4, 'newBlazeRecv' just calls+-- 'Data.Streaming.ByteString.Builder.newByteStringBuilderRecv'+ -- Note that the functions here can work in any monad built on top of @IO@ or -- @ST@. module Data.Streaming.Blaze@@ -37,26 +41,25 @@   , defaultStrategy     ) where -import Data.IORef+import Blaze.ByteString.Builder+import qualified Data.ByteString as S -import qualified Data.ByteString                   as S+#if MIN_VERSION_blaze_builder(0,4,0) -import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Internal.Types-import Blaze.ByteString.Builder.Internal.Buffer+import Data.Streaming.ByteString.Builder --- | Provides a series of @ByteString@s until empty, at which point it provides--- an empty @ByteString@.------ Since 0.1.2-type BlazePopper = IO S.ByteString+newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish)+newBlazeRecv = newByteStringBuilderRecv+{-# INLINE newBlazeRecv #-} -type BlazeRecv = Builder -> IO BlazePopper+#else /* !MIN_VERSION_blaze_builder(0,4,0) */ -type BlazeFinish = IO (Maybe S.ByteString)+import Blaze.ByteString.Builder.Internal hiding (insertByteString)+import Blaze.ByteString.Builder.Internal.Types hiding (insertByteString)+import Blaze.ByteString.Builder.Internal.Buffer (execBuildStep)+import Data.IORef -defaultStrategy :: BufferAllocStrategy-defaultStrategy = allNewBuffersStrategy defaultBufferSize+import Data.Streaming.ByteString.Builder.Buffer  newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish) newBlazeRecv (ioBufInit, nextBuf) = do@@ -133,3 +136,15 @@         buf <- lift $ unsafeLiftIO $ ioBuf         maybe (return ()) (yield' . Chunk) (unsafeFreezeNonEmptyBuffer buf) -}++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++-- | Provides a series of @ByteString@s until empty, at which point it provides+-- an empty @ByteString@.+--+-- Since 0.1.2+type BlazePopper = IO S.ByteString++type BlazeRecv = Builder -> IO BlazePopper++type BlazeFinish = IO (Maybe S.ByteString)
Data/Streaming/ByteString/Builder.hs view
@@ -1,23 +1,139 @@--- | Provides @toByteStringIO*@ like "Blaze.ByteString.Builder"s, for "Data.ByteString.Builder".+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE RankNTypes #-}++-- | Convert a stream of bytestring @Builder@s into a stream of @ByteString@s. --+-- Adapted from blaze-builder-enumerator, written by Michael Snoyman and Simon Meier.+--+-- Note that the functions here can work in any monad built on top of @IO@ or+-- @ST@.+--+-- Also provides @toByteStringIO*@ like "Blaze.ByteString.Builder"s, for+-- "Data.ByteString.Builder".+-- -- Since 0.1.9+-- module Data.Streaming.ByteString.Builder-    ( toByteStringIO+    ( BuilderRecv+    , BuilderPopper+    , BuilderFinish+    , newByteStringBuilderRecv++    -- * toByteStringIO+    , toByteStringIO     , toByteStringIOWith-    , toByteStringIOWithBuffer )+    , toByteStringIOWithBuffer++    -- * Buffers+    , Buffer++    -- ** Status information+    , freeSize+    , sliceSize+    , bufferSize++    -- ** Creation and modification+    , allocBuffer+    , reuseBuffer+    , nextSlice++    -- ** Conversion to bytestings+    , unsafeFreezeBuffer+    , unsafeFreezeNonEmptyBuffer++    -- * Buffer allocation strategies+    , BufferAllocStrategy+    , allNewBuffersStrategy+    , reuseBufferStrategy+    , defaultStrategy+    )     where -import Control.Monad (when)+import Control.Monad (when,unless)+import qualified Data.ByteString as S import Data.ByteString.Builder (Builder) import Data.ByteString.Builder.Extra (runBuilder, BufferWriter, Next(Done, More, Chunk)) import Data.ByteString.Internal (mallocByteString, ByteString(PS)) import Data.ByteString.Lazy.Internal (defaultChunkSize)+import Data.IORef (newIORef, writeIORef, readIORef) import Data.Word (Word8) import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)+import Foreign.Ptr (plusPtr, minusPtr) +import Data.Streaming.ByteString.Builder.Buffer++-- | Provides a series of @ByteString@s until empty, at which point it provides+-- an empty @ByteString@.+--+-- Since 0.1.10.0+--+type BuilderPopper = IO S.ByteString++type BuilderRecv = Builder -> IO BuilderPopper++type BuilderFinish = IO (Maybe S.ByteString)++newByteStringBuilderRecv :: BufferAllocStrategy -> IO (BuilderRecv, BuilderFinish)+newByteStringBuilderRecv (ioBufInit, nextBuf) = do+    refBuf <- newIORef ioBufInit+    return (push refBuf, finish refBuf)+  where+    finish refBuf = do+        ioBuf <- readIORef refBuf+        buf <- ioBuf+        return $ unsafeFreezeNonEmptyBuffer buf++    push refBuf builder = do+        refWri <- newIORef $ Left $ runBuilder builder+        return $ popper refBuf refWri++    popper refBuf refWri = do+        ioBuf <- readIORef refBuf+        ebWri <- readIORef refWri+        case ebWri of+            Left bWri -> do+                !buf@(Buffer _ _ op ope) <- ioBuf+                (bytes, next) <- bWri op (ope `minusPtr` op)+                let op' = op `plusPtr` bytes+                case next of+                    Done -> do+                        writeIORef refBuf $ return $ updateEndOfSlice buf op'+                        return S.empty+                    More minSize bWri' -> do+                        let buf' = updateEndOfSlice buf op'+                            {-# INLINE cont #-}+                            cont mbs = do+                                -- sequencing the computation of the next buffer+                                -- construction here ensures that the reference to the+                                -- foreign pointer `fp` is lost as soon as possible.+                                ioBuf' <- nextBuf minSize buf'+                                writeIORef refBuf ioBuf'+                                writeIORef refWri $ Left bWri'+                                case mbs of+                                    Just bs | not $ S.null bs -> return bs+                                    _ -> popper refBuf refWri+                        cont $ unsafeFreezeNonEmptyBuffer buf'+                    Chunk bs bWri' -> do+                        let buf' = updateEndOfSlice buf op'+                        let yieldBS = do+                                nextBuf 1 buf' >>= writeIORef refBuf+                                writeIORef refWri $ Left bWri'+                                if S.null bs+                                    then popper refBuf refWri+                                    else return bs+                        case unsafeFreezeNonEmptyBuffer buf' of+                            Nothing -> yieldBS+                            Just bs' -> do+                                writeIORef refWri $ Right yieldBS+                                return bs'+            Right action -> action+ -- | Use a pre-existing buffer to 'toByteStringIOWith'. -- -- Since 0.1.9+-- toByteStringIOWithBuffer :: Int                          -> (ByteString -> IO ())                          -> Builder@@ -31,7 +147,7 @@         loop :: BufferWriter -> IO ()         loop wr = do             (len, next) <- withForeignPtr buf (flip wr bufSize)-            when (len > 0) (io (PS buf 0 len))+            when (len > 0) (io $! PS buf 0 len)             case next of                 Done -> return ()                 More newBufSize nextWr@@ -40,7 +156,7 @@                         go newBufSize newBuf nextWr                     | otherwise -> loop nextWr                 Chunk s nextWr -> do-                    io s+                    unless (S.null s) (io s)                     loop nextWr  -- | @toByteStringIOWith bufSize io b@ runs the builder @b@ with a buffer of@@ -67,6 +183,7 @@                    -> IO () toByteStringIOWith bufSize io b =     toByteStringIOWithBuffer bufSize io b =<< mallocByteString bufSize+{-# INLINE toByteStringIOWith #-}  -- | Run the builder with a 'defaultChunkSize'd buffer and execute the given -- 'IO' action whenever the buffer is full or gets flushed.
+ Data/Streaming/ByteString/Builder/Buffer.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE Rank2Types #-}++-- | Buffers for 'Builder's.  This is a partial copy of blaze-builder-0.3.3.4's+-- "Blaze.ByteString.Builder.Internal.Buffer" module, which was removed in+-- blaze-builder-0.4.+--+-- If you are using blaze-builder 0.3.*, this module just re-exports from+-- "Blaze.ByteString.Builder.Internal.Buffer".+--+-- Since 0.1.10.0+--+module Data.Streaming.ByteString.Builder.Buffer+    (+    -- * Buffers+      Buffer (..)++    -- ** Status information+    , freeSize+    , sliceSize+    , bufferSize++    -- ** Creation and modification+    , allocBuffer+    , reuseBuffer+    , nextSlice+    , updateEndOfSlice++    -- ** Conversion to bytestings+    , unsafeFreezeBuffer+    , unsafeFreezeNonEmptyBuffer++    -- * Buffer allocation strategies+    , BufferAllocStrategy+    , allNewBuffersStrategy+    , reuseBufferStrategy+    , defaultStrategy+    ) where++import Data.ByteString.Lazy.Internal (defaultChunkSize)++#if MIN_VERSION_blaze_builder(0,4,0)++import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S+import Foreign (Word8, ForeignPtr, Ptr, plusPtr, minusPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)++------------------------------------------------------------------------------+-- Buffers+------------------------------------------------------------------------------++-- | A buffer @Buffer fpbuf p0 op ope@ describes a buffer with the underlying+-- byte array @fpbuf..ope@, the currently written slice @p0..op@ and the free+-- space @op..ope@.+--+-- Since 0.1.10.0+--+data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+                     {-# UNPACK #-} !(Ptr Word8)        -- beginning of slice+                     {-# UNPACK #-} !(Ptr Word8)        -- next free byte+                     {-# UNPACK #-} !(Ptr Word8)        -- first byte after buffer++-- | The size of the free space of the buffer.+--+-- Since 0.1.10.0+--+freeSize :: Buffer -> Int+freeSize (Buffer _ _ op ope) = ope `minusPtr` op++-- | The size of the written slice in the buffer.+--+-- Since 0.1.10.0+--+sliceSize :: Buffer -> Int+sliceSize (Buffer _ p0 op _) = op `minusPtr` p0++-- | The size of the whole byte array underlying the buffer.+--+-- Since 0.1.10.0+--+bufferSize :: Buffer -> Int+bufferSize (Buffer fpbuf _ _ ope) =+    ope `minusPtr` unsafeForeignPtrToPtr fpbuf++-- | @allocBuffer size@ allocates a new buffer of size @size@.+--+-- Since 0.1.10.0+--+{-# INLINE allocBuffer #-}+allocBuffer :: Int -> IO Buffer+allocBuffer size = do+    fpbuf <- S.mallocByteString size+    let !pbuf = unsafeForeignPtrToPtr fpbuf+    return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)++-- | Resets the beginning of the next slice and the next free byte such that+-- the whole buffer can be filled again.+--+-- Since 0.1.10.0+--+{-# INLINE reuseBuffer #-}+reuseBuffer :: Buffer -> Buffer+reuseBuffer (Buffer fpbuf _ _ ope) = Buffer fpbuf p0 p0 ope+  where+    p0 = unsafeForeignPtrToPtr fpbuf++-- | Convert the buffer to a bytestring. This operation is unsafe in the sense+-- that created bytestring shares the underlying byte array with the buffer.+-- Hence, depending on the later use of this buffer (e.g., if it gets reset and+-- filled again) referential transparency may be lost.+--+-- Since 0.1.10.0+--+{-# INLINE unsafeFreezeBuffer #-}+unsafeFreezeBuffer :: Buffer -> S.ByteString+unsafeFreezeBuffer (Buffer fpbuf p0 op _) =+    S.PS fpbuf (p0 `minusPtr` unsafeForeignPtrToPtr fpbuf) (op `minusPtr` p0)++-- | Convert a buffer to a non-empty bytestring. See 'unsafeFreezeBuffer' for+-- the explanation of why this operation may be unsafe.+--+-- Since 0.1.10.0+--+{-# INLINE unsafeFreezeNonEmptyBuffer #-}+unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString+unsafeFreezeNonEmptyBuffer buf+  | sliceSize buf <= 0 = Nothing+  | otherwise          = Just $ unsafeFreezeBuffer buf++-- | Update the end of slice pointer.+--+-- Since 0.1.10.0+--+{-# INLINE updateEndOfSlice #-}+updateEndOfSlice :: Buffer    -- Old buffer+                 -> Ptr Word8 -- New end of slice+                 -> Buffer    -- Updated buffer+updateEndOfSlice (Buffer fpbuf p0 _ ope) op' = Buffer fpbuf p0 op' ope++-- | Move the beginning of the slice to the next free byte such that the+-- remaining free space of the buffer can be filled further. This operation+-- is safe and can be used to fill the remaining part of the buffer after a+-- direct insertion of a bytestring or a flush.+--+-- Since 0.1.10.0+--+{-# INLINE nextSlice #-}+nextSlice :: Int -> Buffer -> Maybe Buffer+nextSlice minSize (Buffer fpbuf _ op ope)+  | ope `minusPtr` op <= minSize = Nothing+  | otherwise                    = Just (Buffer fpbuf op op ope)++------------------------------------------------------------------------------+-- Buffer allocation strategies+------------------------------------------------------------------------------++-- | A buffer allocation strategy @(buf0, nextBuf)@ specifies the initial+-- buffer to use and how to compute a new buffer @nextBuf minSize buf@ with at+-- least size @minSize@ from a filled buffer @buf@. The double nesting of the+-- @IO@ monad helps to ensure that the reference to the filled buffer @buf@ is+-- lost as soon as possible, but the new buffer doesn't have to be allocated+-- too early.+--+-- Since 0.1.10.0+--+type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))++-- | The simplest buffer allocation strategy: whenever a buffer is requested,+-- allocate a new one that is big enough for the next build step to execute.+--+-- NOTE that this allocation strategy may spill quite some memory upon direct+-- insertion of a bytestring by the builder. Thats no problem for garbage+-- collection, but it may lead to unreasonably high memory consumption in+-- special circumstances.+--+-- Since 0.1.10.0+--+allNewBuffersStrategy :: Int                 -- Minimal buffer size.+                      -> BufferAllocStrategy+allNewBuffersStrategy bufSize =+    ( allocBuffer bufSize+    , \reqSize _ -> return (allocBuffer (max reqSize bufSize)) )++-- | An unsafe, but possibly more efficient buffer allocation strategy:+-- reuse the buffer, if it is big enough for the next build step to execute.+--+-- Since 0.1.10.0+--+reuseBufferStrategy :: IO Buffer+                    -> BufferAllocStrategy+reuseBufferStrategy buf0 =+    (buf0, tryReuseBuffer)+  where+    tryReuseBuffer reqSize buf+      | bufferSize buf >= reqSize = return $ return (reuseBuffer buf)+      | otherwise                 = return $ allocBuffer reqSize+++#else  /* !MIN_VERSION_blaze_builder(0,4,0) */++import Blaze.ByteString.Builder.Internal.Buffer++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++defaultStrategy :: BufferAllocStrategy+defaultStrategy = allNewBuffersStrategy defaultChunkSize
+ Data/Streaming/ByteString/Builder/Class.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+-- | Typeclass to stream blaze-builder and bytestring(-builder) @Builder@s.+--+-- Since 0.1.10.0+--+module Data.Streaming.ByteString.Builder.Class+    ( StreamingBuilder (..)+    , module Data.Streaming.ByteString.Builder+    ) where++import qualified Data.ByteString.Builder+import qualified Data.ByteString.Builder.Internal+import Data.Monoid (Monoid)++import Data.Streaming.ByteString.Builder hiding (newByteStringBuilderRecv)+import qualified Data.Streaming.ByteString.Builder++#if !MIN_VERSION_blaze_builder(0,4,0)++import qualified Blaze.ByteString.Builder++import Data.Streaming.Blaze++instance StreamingBuilder Blaze.ByteString.Builder.Builder where+    newBuilderRecv = newBlazeRecv+    builderFlush   = Blaze.ByteString.Builder.flush++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++-- | Typeclass to stream blaze-builder (< 0.4) and bytestring(-builder) @Builder@s.+-- This is primarily to aid the transition from blaze-builder to bytestring @Builder@s+-- (if using blaze-builder >= 0.4, there is only one instance, since the @Builder@+-- type is shared).+--+-- Since 0.1.10.0+--+class Monoid b => StreamingBuilder b where+    newBuilderRecv :: BufferAllocStrategy -> IO (b -> IO BuilderPopper, BuilderFinish)+    builderFlush   :: b++instance StreamingBuilder Data.ByteString.Builder.Builder where+    newBuilderRecv = Data.Streaming.ByteString.Builder.newByteStringBuilderRecv+    builderFlush   = Data.ByteString.Builder.Internal.flush
streaming-commons.cabal view
@@ -1,11 +1,11 @@ name:                streaming-commons-version:             0.1.9.1+version:             0.1.10.0 synopsis:            Common lower-level functions needed by various streaming data libraries description:         Provides low-dependency functionality commonly needed by various streaming data libraries, such as conduit and pipes. homepage:            https://github.com/fpco/streaming-commons license:             MIT license-file:        LICENSE-author:              Michael Snoyman+author:              Michael Snoyman, Emanuel Borsboom maintainer:          michael@snoyman.com -- copyright:            category:            Data@@ -27,6 +27,8 @@ library   exposed-modules:     Data.Streaming.Blaze                        Data.Streaming.ByteString.Builder+                       Data.Streaming.ByteString.Builder.Buffer+                       Data.Streaming.ByteString.Builder.Class                        Data.Streaming.FileRead                        Data.Streaming.Filesystem                        Data.Streaming.Network@@ -47,7 +49,7 @@    build-depends:       base >= 4.4 && < 5                      , array-                     , blaze-builder >= 0.3 && < 0.4+                     , blaze-builder >= 0.3 && < 0.5                      , bytestring                      , directory                      , network@@ -82,6 +84,7 @@     type:           exitcode-stdio-1.0     ghc-options:    -Wall -threaded     other-modules:  Data.Streaming.ByteString.BuilderSpec+                    Data.Streaming.BlazeSpec                     Data.Streaming.FileReadSpec                     Data.Streaming.FilesystemSpec                     Data.Streaming.NetworkSpec
+ test/Data/Streaming/BlazeSpec.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Streaming.BlazeSpec (spec) where++import Test.Hspec+import qualified Blaze.ByteString.Builder as B+import Data.Streaming.ByteString.BuilderSpec hiding (spec)++spec :: Spec+spec = do+    describe "Data.Streaming.Blaze" $ builderSpec BuilderFunctions+        { bfFromByteString       = B.fromByteString+        , bfInsertLazyByteString = B.insertLazyByteString+        , bfToLazyByteString     = B.toLazyByteString+        , bfInsertByteString     = B.insertByteString+        , bfCopyByteString       = B.copyByteString+        }
test/Data/Streaming/ByteString/BuilderSpec.hs view
@@ -1,20 +1,136 @@ {-# LANGUAGE OverloadedStrings #-}-module Data.Streaming.ByteString.BuilderSpec (spec) where+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Data.Streaming.ByteString.BuilderSpec+    ( spec+    , builderSpec+    , BuilderFunctions(..)+    ) where -import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString as S+import Data.ByteString.Char8 () import qualified Data.ByteString.Unsafe as S import qualified Data.ByteString.Builder as B-import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.ByteString.Builder.Internal as B+import qualified Data.ByteString.Lazy as L+import Data.ByteString.Lazy.Char8 () import Data.IORef+import Data.Maybe import Data.Monoid import Test.Hspec import Test.Hspec.QuickCheck (prop)  import Data.Streaming.ByteString.Builder+import Data.Streaming.ByteString.Builder.Class +data BuilderFunctions b = BuilderFunctions+    { bfFromByteString       :: S.ByteString -> b+    , bfInsertLazyByteString :: L.ByteString -> b+    , bfToLazyByteString     :: b -> L.ByteString+    , bfInsertByteString     :: S.ByteString -> b+    , bfCopyByteString       :: S.ByteString -> b+    }++tester :: StreamingBuilder b => BufferAllocStrategy -> [b] -> IO [S.ByteString]+tester strat builders0 = do+    (recv, finish) <- newBuilderRecv strat+    let loop front [] = do+            mbs <- finish+            return $ front $ maybe [] return mbs+        loop front0 (bu:bus) = do+            popper <- recv bu+            let go front = do+                    bs <- popper+                    if S.null bs+                        then loop front bus+                        else go (front . (bs:))+            go front0+    loop id builders0++testerFlush :: StreamingBuilder b+            => BufferAllocStrategy -> [Maybe b] -> IO [Maybe S.ByteString]+testerFlush strat builders0 = do+    (recv, finish) <- newBuilderRecv strat+    let loop front [] = do+            mbs <- finish+            return $ front $ maybe [] (return . Just) mbs+        loop front0 (mbu:bus) = do+            popper <- recv $ fromMaybe builderFlush mbu+            let go front = do+                    bs <- popper+                    if S.null bs+                        then+                            case mbu of+                                Nothing -> loop (front . (Nothing:)) bus+                                Just _ -> loop front bus+                        else go (front . (Just bs:))+            go front0+    loop id builders0++builderSpec :: forall b. StreamingBuilder b => BuilderFunctions b -> Spec+builderSpec BuilderFunctions{..} = do+    prop "idempotent to toLazyByteString" $ \bss' -> do+        let bss = map S.pack bss'+        let builders :: [b]+            builders = map bfFromByteString bss+        let lbs = bfToLazyByteString $ mconcat builders+        outBss <- tester defaultStrategy builders+        L.fromChunks outBss `shouldBe` lbs++    it "works for large input" $ do+        let builders :: [b]+            builders = replicate 10000 (bfFromByteString "hello world!" :: b)+        let lbs = bfToLazyByteString $ mconcat builders+        outBss <- tester defaultStrategy builders+        L.fromChunks outBss `shouldBe` lbs++    it "works for lazy bytestring insertion" $ do+        let builders :: [b]+            builders = replicate 10000 (bfInsertLazyByteString "hello world!")+        let lbs = bfToLazyByteString $ mconcat builders+        outBss <- tester defaultStrategy builders+        L.fromChunks outBss `shouldBe` lbs++    prop "works for strict bytestring insertion" $ \bs' -> do+        let bs = S.pack bs'+        let builders :: [b]+            builders = replicate 10000 (bfCopyByteString bs `mappend` bfInsertByteString bs)+        let lbs = bfToLazyByteString $ mconcat builders+        outBss <- tester defaultStrategy builders+        L.fromChunks outBss `shouldBe` lbs++    it "flush shouldn't bring in empty strings." $ do+        let dat = ["hello", "world"]+            builders :: [b]+            builders = map ((`mappend` builderFlush) . bfFromByteString) dat+        out <- tester defaultStrategy builders+        dat `shouldBe` out++    prop "flushing" $ \bss' -> do+        let bss = concatMap (\bs -> [Just $ S.pack bs, Nothing]) $ filter (not . null) bss'+        let builders :: [Maybe b]+            builders = map (fmap bfFromByteString) bss+        outBss <- testerFlush defaultStrategy builders+        outBss `shouldBe` bss+    it "large flush input" $ do+        let lbs = L.pack $ concat $ replicate 100000 [0..255]+            chunks :: [Maybe b]+            chunks = map (Just . bfFromByteString) (L.toChunks lbs)+        bss <- testerFlush defaultStrategy chunks+        L.fromChunks (catMaybes bss) `shouldBe` lbs+ spec :: Spec spec =     describe "Data.Streaming.ByteString.Builder" $ do++        builderSpec BuilderFunctions+            { bfFromByteString       = B.byteString+            , bfInsertLazyByteString = B.lazyByteStringInsert+            , bfToLazyByteString     = B.toLazyByteString+            , bfInsertByteString     = B.byteStringInsert+            , bfCopyByteString       = B.byteStringCopy+            }+         prop "toByteStringIO idempotent to toLazyByteString" $ \bss' -> do             let bss = mconcat (map (B.byteString . S.pack) bss')             ior <- newIORef []