pipes-zlib 0.4.3 → 0.4.4
raw patch · 5 files changed
+213/−54 lines, 5 filesdep +HUnitdep +QuickCheckdep +pipes-zlibdep −zlibdep −zlib-bindingsdep ~basedep ~bytestringdep ~pipesPVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, QuickCheck, pipes-zlib, quickcheck-instances, streaming-commons, tasty, tasty-hunit, tasty-quickcheck
Dependencies removed: zlib, zlib-bindings
Dependency ranges changed: base, bytestring, pipes, transformers
API changes (from Hackage documentation)
+ Pipes.GZip: data CompressionLevel
+ Pipes.GZip: decompress' :: MonadIO m => Producer ByteString m r -> Producer ByteString m (Either (Producer ByteString m r) r)
+ Pipes.Zlib: data CompressionLevel
+ Pipes.Zlib: decompress' :: MonadIO m => WindowBits -> Producer ByteString m r -> Producer ByteString m (Either (Producer ByteString m r) r)
+ Pipes.Zlib: instance GHC.Classes.Eq Pipes.Zlib.CompressionLevel
+ Pipes.Zlib: instance GHC.Classes.Ord Pipes.Zlib.CompressionLevel
+ Pipes.Zlib: instance GHC.Read.Read Pipes.Zlib.CompressionLevel
+ Pipes.Zlib: instance GHC.Show.Show Pipes.Zlib.CompressionLevel
Files
- changelog.md +11/−0
- pipes-zlib.cabal +27/−7
- src/Pipes/GZip.hs +25/−11
- src/Pipes/Zlib.hs +77/−36
- test/Main.hs +73/−0
changelog.md view
@@ -1,3 +1,14 @@+# Version 0.4.4++* Depend on `streaming-commons` instead of `zlib` and `zlib-bindings`,+ as the latter are deprecated.++* Add `Pipes.Zlib.decompress'` and `Pipes.GZip.decompress'`.++* Bump upper bound dependency on `transformers`.++* Add tests.+ # Version 0.4.3 * Fix usage of the `Producer'` type synonym (#14).
pipes-zlib.cabal view
@@ -1,5 +1,5 @@ name: pipes-zlib-version: 0.4.3+version: 0.4.4 license: BSD3 license-file: LICENSE Copyright: Copyright (c) Paolo Capriotti 2012,@@ -24,10 +24,30 @@ hs-source-dirs: src exposed-modules: Pipes.Zlib Pipes.GZip- build-depends: base (>= 4.5 && < 5.0)- , transformers (>= 0.2 && < 0.5)- , pipes (>= 4.0 && < 4.2)- , bytestring (>= 0.9.2.1)- , zlib (>= 0.5 && < 0.7)- , zlib-bindings (>= 0.1 && < 0.2)+ build-depends: base >= 4.5 && < 5.0+ , transformers >= 0.2 && < 0.6+ , pipes >= 4.0 && < 4.2+ , bytestring >= 0.9.2.1+ , streaming-commons >= 0.1.15 && < 0.2 ghc-options: -Wall -O2++test-suite test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: base+ , pipes-zlib+ , pipes+ , bytestring+ , HUnit+ , QuickCheck+ , quickcheck-instances+ , tasty+ , tasty-quickcheck+ , tasty-hunit+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/githubuser/twio
src/Pipes/GZip.hs view
@@ -6,18 +6,19 @@ module Pipes.GZip ( -- * Streams decompress+ , decompress' , compress -- * Compression level- -- $ccz-re-export- , ZC.defaultCompression- , ZC.noCompression- , ZC.bestSpeed- , ZC.bestCompression- , ZC.compressionLevel+ , Pipes.Zlib.CompressionLevel+ , Pipes.Zlib.defaultCompression+ , Pipes.Zlib.noCompression+ , Pipes.Zlib.bestSpeed+ , Pipes.Zlib.bestCompression+ , Pipes.Zlib.compressionLevel ) where -import qualified Codec.Compression.Zlib as ZC+import qualified Data.Streaming.Zlib as Zlib import qualified Data.ByteString as B import Pipes import qualified Pipes.Zlib@@ -35,11 +36,21 @@ :: MonadIO m => Proxy x' x () B.ByteString m r -- ^ Compressed stream -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream- -- -> Producer' B.ByteString m r -- ^ Decompressed stream-decompress = Pipes.Zlib.decompress (ZC.WindowBits 31)+decompress = Pipes.Zlib.decompress gzWindowBits {-# INLINABLE decompress #-} +-- | Decompress bytes flowing from a 'Producer', returning any leftover input+-- that follows the compressed stream.+decompress'+ :: MonadIO m+ => Producer B.ByteString m r -- ^ Compressed stream+ -> Producer B.ByteString m (Either (Producer B.ByteString m r) r)+ -- ^ Decompressed stream, returning either a 'Producer' of the leftover input+ -- or the return value from the input 'Producer'.+decompress' = Pipes.Zlib.decompress' gzWindowBits+{-# INLINABLE decompress' #-} + -- | Compress bytes flowing from a 'Producer'. -- -- @@@ -50,8 +61,11 @@ -- @ compress :: MonadIO m- => ZC.CompressionLevel+ => Pipes.Zlib.CompressionLevel -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream -> Proxy x' x () B.ByteString m r -- ^ Compressed stream-compress clevel = Pipes.Zlib.compress clevel (ZC.WindowBits 31)+compress clevel = Pipes.Zlib.compress clevel gzWindowBits {-# INLINABLE compress #-}++gzWindowBits :: Zlib.WindowBits+gzWindowBits = Zlib.WindowBits 31
src/Pipes/Zlib.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | This module exports utilities to compress and decompress @pipes@ streams -- using the zlib compression codec.@@ -9,24 +10,25 @@ module Pipes.Zlib ( -- * Streams decompress+ , decompress' , compress - -- * Compression level- -- $ccz-re-export- , ZC.defaultCompression- , ZC.noCompression- , ZC.bestSpeed- , ZC.bestCompression- , ZC.compressionLevel+ -- * Compression levels+ , CompressionLevel+ , defaultCompression+ , noCompression+ , bestSpeed+ , bestCompression+ , compressionLevel -- * Window size -- $ccz-re-export- , ZC.defaultWindowBits- , ZC.windowBits+ , Z.defaultWindowBits+ , windowBits ) where -import qualified Codec.Compression.Zlib as ZC-import qualified Codec.Zlib as Z+import Data.Streaming.Zlib as Z+import Control.Exception (throwIO) import Control.Monad (unless) import qualified Data.ByteString as B import Pipes@@ -39,13 +41,13 @@ -- -- @ -- 'decompress' :: 'MonadIO' m--- => 'ZC.WindowBits'+-- => 'Z.WindowBits' -- => 'Producer' 'B.ByteString' m r -- -> 'Producer' 'B.ByteString' m r -- @ decompress :: MonadIO m- => ZC.WindowBits+ => Z.WindowBits -> Proxy x' x () B.ByteString m r -- ^ Compressed stream -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream decompress wbits p0 = do@@ -58,27 +60,52 @@ return r {-# INLINABLE decompress #-} +-- | Decompress bytes flowing from a 'Producer', returning any leftover input+-- that follows the compressed stream.+decompress'+ :: MonadIO m+ => Z.WindowBits+ -> Producer B.ByteString m r -- ^ Compressed stream+ -> Producer B.ByteString m (Either (Producer B.ByteString m r) r)+ -- ^ Decompressed stream, ending with either leftovers or a result+decompress' wbits p0 = go p0 =<< liftIO (Z.initInflate wbits)+ where+ flush inf = do+ bs <- liftIO $ Z.flushInflate inf+ unless (B.null bs) (yield bs)+ go p inf = do+ res <- lift (next p)+ case res of+ Left r -> return $ Right r+ Right (bs, p') -> do+ fromPopper =<< liftIO (Z.feedInflate inf bs)+ flush inf+ leftover <- liftIO $ Z.getUnusedInflate inf+ if B.null leftover+ then go p' inf+ else return $ Left (yield leftover >> p')+{-# INLINABLE decompress' #-} -- | Compress bytes flowing from a 'Producer'. -- -- See the "Codec.Compression.Zlib" module for details about--- 'ZC.CompressionLevel' and 'ZC.WindowBits'.+-- 'Z.CompressionLevel' and 'Z.WindowBits'. -- -- @ -- 'compress' :: 'MonadIO' m--- => 'ZC.CompressionLevel'--- -> 'ZC.WindowBits'+-- => 'Z.CompressionLevel'+-- -> 'Z.WindowBits' -- -> 'Producer' 'B.ByteString' m r -- -> 'Producer' 'B.ByteString' m r -- @ compress :: MonadIO m- => ZC.CompressionLevel- -> ZC.WindowBits+ => CompressionLevel+ -> Z.WindowBits -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream -> Proxy x' x () B.ByteString m r -- ^ Compressed stream-compress clevel wbits p0 = do- def <- liftIO $ Z.initDeflate (fromCompressionLevel clevel) wbits+compress (CompressionLevel clevel) wbits p0 = do+ def <- liftIO $ Z.initDeflate clevel wbits r <- for p0 $ \bs -> do popper <- liftIO (Z.feedDeflate def bs) fromPopper popper@@ -94,26 +121,40 @@ -- convenience. --------------------------------------------------------------------------------+-- Compression Levels++-- | How hard should we try to compress?+newtype CompressionLevel = CompressionLevel Int+ deriving (Show, Read, Eq, Ord)++defaultCompression, noCompression, bestSpeed, bestCompression :: CompressionLevel+defaultCompression = CompressionLevel (-1)+noCompression = CompressionLevel 0+bestSpeed = CompressionLevel 1+bestCompression = CompressionLevel 9++-- | A specific compression level between 0 and 9.+compressionLevel :: Int -> CompressionLevel+compressionLevel n+ | n >= 0 && n <= 9 = CompressionLevel n+ | otherwise = error "CompressionLevel must be in the range 0..9"++windowBits :: Int -> WindowBits+windowBits = WindowBits++-------------------------------------------------------------------------------- -- Internal stuff -- | Produce values from the given 'Z.Popper' until exhausted.-fromPopper :: MonadIO m => Z.Popper -> Producer' B.ByteString m ()-fromPopper pop = loop where+fromPopper :: MonadIO m+ => Z.Popper+ -> Producer' B.ByteString m ()+fromPopper pop = loop+ where loop = do mbs <- liftIO pop case mbs of- Nothing -> return ()- Just bs -> yield bs >> loop+ PRDone -> return ()+ PRError e -> liftIO $ throwIO e+ PRNext bs -> yield bs >> loop {-# INLINABLE fromPopper #-}---- We need this function until the @zlib@ library hides the--- 'ZC.CompressionLevel' constructors in future version 0.7.-fromCompressionLevel :: ZC.CompressionLevel -> Int-fromCompressionLevel level = case level of- ZC.DefaultCompression -> -1- ZC.NoCompression -> 0- ZC.BestSpeed -> 1- ZC.BestCompression -> 9- ZC.CompressionLevel n- | n >= 0 && n <= 9 -> fromIntegral n- _ -> error "CompressLevel must be in the range 1..9"
+ test/Main.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+import Control.Arrow+import qualified Data.ByteString.Char8 as B8+import Data.List+import Data.Ord+import Test.Tasty+import Test.Tasty.QuickCheck as QC+import Test.QuickCheck.Instances ()+import Test.Tasty.HUnit++import qualified Pipes as P+import qualified Pipes.Prelude as P+import qualified Pipes.Zlib as PZ+import qualified Pipes.GZip as PGZ++main = defaultMain tests++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "Tests" [properties, unitTests]++properties :: TestTree+properties = testGroup "Properties" [qcProps]++qcProps = testGroup "(checked by QuickCheck)"+ [ QC.testProperty "id == decompress . compress" $ \bs -> QC.ioProperty $ do+ let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits (P.yield bs)+ pd = PZ.decompress PZ.defaultWindowBits pc+ bs' <- B8.concat <$> P.toListM pd+ return (bs QC.=== bs')++ , QC.testProperty "id == decompress' . compress" $ \bs bsl -> QC.ioProperty $ do+ let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits (P.yield bs)+ pd = PZ.decompress' PZ.defaultWindowBits (pc >> P.yield bsl)+ (bs', elr) <- first B8.concat <$> P.toListM' pd+ case elr of+ Left pl -> do+ bsl' <- B8.concat <$> P.toListM pl+ return $ (bs QC.=== bs') QC..&&. (bsl QC.=== bsl')+ Right () -> do+ return $ (bs QC.=== bs') QC..&&. (bsl QC.=== B8.empty)+ ]++unitTests = testGroup "Unit tests"+ [ testCase "Zlib compression default" $ do+ let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits+ (P.yield bsUncompressed)+ bs <- B8.concat <$> P.toListM pc+ bs @?= bsCompressedZlibDefault+ , testCase "Zlib decompression default" $ do+ let pd = PZ.decompress PZ.defaultWindowBits+ (P.yield bsCompressedZlibDefault)+ bs <- B8.concat <$> P.toListM pd+ bs @?= bsUncompressed+ , testCase "GZip compression default" $ do+ let pd = PGZ.compress PGZ.defaultCompression (P.yield bsUncompressed)+ bs <- B8.concat <$> P.toListM pd+ bs @?= bsCompressedGZipDefault+ , testCase "GZip decompression default" $ do+ let pd = PGZ.decompress (P.yield bsCompressedGZipDefault)+ bs <- B8.concat <$> P.toListM pd+ bs @?= bsUncompressed+ ]++bsUncompressed :: B8.ByteString+bsUncompressed = "foo"++bsCompressedZlibDefault :: B8.ByteString+bsCompressedZlibDefault = "x\156K\203\207\a\NUL\STX\130\SOHE"++bsCompressedGZipDefault :: B8.ByteString+bsCompressedGZipDefault =+ "\US\139\b\NUL\NUL\NUL\NUL\NUL\NUL\ETXK\203\207\a\NUL!es\140\ETX\NUL\NUL\NUL"