zlib-enum 0.1 → 0.2
raw patch · 3 files changed
+419/−59 lines, 3 filesdep +QuickCheckdep +processdep +test-frameworkdep −mtldep ~basedep ~bytestringdep ~enumeratornew-component:exe:zlib-enum-test
Dependencies added: QuickCheck, process, test-framework, test-framework-quickcheck2, transformers
Dependencies removed: mtl
Dependency ranges changed: base, bytestring, enumerator, zlib-bindings
Files
- Codec/Zlib/Enum.hs +57/−56
- test.hs +331/−0
- zlib-enum.cabal +31/−3
Codec/Zlib/Enum.hs view
@@ -1,80 +1,81 @@ module Codec.Zlib.Enum ( -- * Enumeratees- compress, decompress,- -- * Re-exported from zlib-bindings - WindowBits, defaultWindowBits+ compress, decompress, gzip, ungzip,+ -- * Re-exported from zlib-bindings+ WindowBits (..), defaultWindowBits ) where import Codec.Zlib import Data.Enumerator as E-import Control.Monad.Trans (MonadIO, liftIO)+import qualified Data.Enumerator.List as EL+import Control.Monad.Trans.Class (lift)+import Control.Monad.IO.Class (MonadIO, liftIO) import Data.ByteString (ByteString)-import Control.Monad (join) -joinIO :: MonadIO m => IO (m (Step a m b)) -> Iteratee a m b-joinIO = Iteratee . join . liftIO+-- | Gzip compression with default parameters.+gzip :: MonadIO m => Enumeratee ByteString ByteString m a+gzip = compress 1 defaultWindowBits -enumLoop :: Monad m =>- ((Stream b -> Iteratee b m c) -> Iteratee b m c)- -> (a -> (Stream b -> Iteratee b m c) -> Iteratee b m c)- -> Enumeratee a b m c-enumLoop done more = checkDone loop where- loop k = do maybe_x <- E.head- case maybe_x of- Nothing -> return $$ done k- Just x -> checkDone loop $$ more x k+-- | Gzip decompression with default parameters.+ungzip :: MonadIO m => Enumeratee ByteString ByteString m a+ungzip = decompress defaultWindowBits -- | -- Decompress (inflate) a stream of 'ByteString's. For example:--- +-- -- > run $ enumFile "test.z" $$ decompress defaultWindowBits $$ printChunks True -decompress :: MonadIO m+decompress+ :: MonadIO m => WindowBits -- ^ Zlib parameter (see the zlib-bindings package as well as the zlib C library) -> Enumeratee ByteString ByteString m a-decompress config step0 = do- inflate <- liftIO $ initInflate config- let done k = do lastChunk <- liftIO $ finishInflate inflate- k (Chunks [lastChunk])- more x k = joinIO $ withInflateInput inflate x (return . callback k)- enumLoop done more step0+decompress config inner = do+ inf <- liftIO $ initInflate config+ decompress' inf inner +decompress' :: MonadIO m => Inflate -> Enumeratee ByteString ByteString m b+decompress' inf (Continue k) = do+ x <- EL.head+ case x of+ Nothing -> do+ chunk <- liftIO $ finishInflate inf+ lift $ runIteratee $ k $ Chunks [chunk]+ Just bs -> do+ chunks <- liftIO $ withInflateInput inf bs $ callback+ step <- lift $ runIteratee $ k $ Chunks chunks+ decompress' inf step+decompress' _ step = return step+ -- | -- Compress (deflate) a stream of 'ByteString's. The 'WindowBits' also control -- the format (zlib vs. gzip). -compress :: MonadIO m- => Int -- ^ Compression level- -> WindowBits -- ^ Zlib parameter (see the zlib-bindings package as well as the zlib C library)+compress+ :: MonadIO m+ => Int -- ^ Compression level+ -> WindowBits -- ^ Zlib parameter (see the zlib-bindings package as well as the zlib C library) -> Enumeratee ByteString ByteString m a-compress level config step0 = do- deflate <- liftIO $ initDeflate level config- let done k = joinIO $ finishDeflate deflate (return . callback k)- more x k = joinIO $ withDeflateInput deflate x (return . callback k)- enumLoop done more step0---- A callback function for withInflateInput / withDeflateInput-callback :: MonadIO m =>- (Stream a -> Iteratee a m b) -> IO (Maybe a) -> m (Step a m b)+compress level config inner = do+ def <- liftIO $ initDeflate level config+ compress' def inner -callback k pop = maybe done more =<< liftIO pop- where- done = return (Continue k)- more y = do step <- runIteratee (k (Chunks [y]))- case step of- Continue k' -> callback k' pop- other -> return other+compress' :: MonadIO m => Deflate -> Enumeratee ByteString ByteString m b+compress' def (Continue k) = do+ x <- EL.head+ case x of+ Nothing -> do+ chunks <- liftIO $ finishDeflate def $ callback+ lift $ runIteratee $ k $ Chunks chunks+ Just bs -> do+ chunks <- liftIO $ withDeflateInput def bs $ callback+ step <- lift $ runIteratee $ k $ Chunks chunks+ compress' def step+compress' _ step = return step --- testInflate = do--- h <- openBinaryFile "test-out" WriteMode--- run $ enumFile "test.z"--- $$ decompress defaultWindowBits--- $$ iterHandle h--- hClose h--- --- testDeflate = do--- h <- openBinaryFile "test.z" WriteMode--- run $ enumFile "test"--- $$ compress 7 defaultWindowBits--- $$ iterHandle h--- hClose h+callback :: Monad m => m (Maybe a) -> m [a]+callback pop = go id where+ go front = do+ x <- pop+ case x of+ Nothing -> return $ front []+ Just y -> go (front . (:) y)
+ test.hs view
@@ -0,0 +1,331 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-------------------------------------------------------------------------------+-- |+-- Module : zlib-enum-test+-- Copyright : (c) 2011 Malte Sommerkorn, Michael Snoyman, Paulo Tanimoto+-- License : BSD3+--+-- Maintainer : Malte Sommerkorn <malte.sommerkorn@googlemail.com>+--+-------------------------------------------------------------------------------++module Main where++-------------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------------++import Prelude++import Data.String ()++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L++import Data.Enumerator+ ( Iteratee (..), Enumeratee+ , ($$), joinI, run_ )+import qualified Data.Enumerator as E+import qualified Data.Enumerator.List as EL+import qualified Data.Enumerator.Binary as EB++import Control.Monad (foldM)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Exception (bracket)++import Codec.Zlib+import qualified Codec.Zlib.Enum as Z++import Test.QuickCheck+import Test.QuickCheck.Monadic+import qualified Test.QuickCheck.Monadic as Q++import System.IO (IOMode (..), openBinaryFile, hClose)++import Test.Framework (Test, defaultMain, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)++-------------------------------------------------------------------------------+-- Instances+-------------------------------------------------------------------------------++-- | QuickCheck wants a Show instance for WindowBits+instance Show WindowBits where+ show (WindowBits n) = "WindowBits " ++ show n+ show _ = "DefaultWindowBits" -- not exported from zlib++-- | Random values for WindowBits: 15 or 31+instance Arbitrary WindowBits where+ arbitrary = elements [WindowBits 15, WindowBits 31]++-- | Generate relatively large ByteStrings+instance Arbitrary B.ByteString where+ arbitrary = (B.pack . concat . replicate 1000) `fmap` arbitrary++-- | Generate relatively large Lazy ByteStrings+instance Arbitrary L.ByteString where+ arbitrary = (L.fromChunks . concat . replicate 100) `fmap` arbitrary++-------------------------------------------------------------------------------+-- Utility Functions+-------------------------------------------------------------------------------++-- | Iteratee that consumes the entire Stream as a strict ByteString.+consume :: Monad m => Iteratee ByteString m ByteString+consume = do+ xs <- EL.consume+ return $ B.concat xs++-- | Enumeratee that consumes only a few bytes.+unconsumed :: Monad m => Enumeratee ByteString ByteString m a+unconsumed = E.sequence $ do+ xs <- EB.take 100+ return $ B.concat $ L.toChunks xs++-- | Concatenate an Iteratee and a list of Enumeratees into an Iteratee.+concatWith :: Monad m => Iteratee a m b -> [Enumeratee a a m b] -> Iteratee a m b+concatWith = foldr f+ where+ f iter enums = joinI $ iter $$ enums++-- | Compress a list of ByteStrings+compress+ :: MonadIO m+ => WindowBits+ -> [ByteString]+ -> m ByteString+compress win xs =+ E.run_ $ E.enumList 1 xs+ $$ joinI $ Z.compress 7 win+ $$ consume++-- | Decompress a list of ByteStrings+decompress+ :: MonadIO m+ => WindowBits+ -> [ByteString]+ -> m ByteString+decompress win xs =+ E.run_ $ E.enumList 1 xs+ $$ joinI $ Z.decompress win+ $$ consume++-- | Compress and decompress without doing anything else.+compressDecompress+ :: MonadIO m+ => WindowBits+ -> [ByteString]+ -> m ByteString+compressDecompress win xs =+ E.run_ $ E.enumList 1 xs+ $$ joinI $ Z.compress 7 win+ $$ joinI $ Z.decompress win+ $$ consume++-- | The same, but for 'gzip', 'ungzip'+gzipUngzip :: MonadIO m => [ByteString] -> m ByteString+gzipUngzip xs =+ E.run_ $ E.enumList 1 xs+ $$ joinI $ Z.gzip+ $$ joinI $ Z.ungzip+ $$ consume++-- | Compress and decompress a ByteString with given WindowBits,+-- piping the stream with an Enumeratee.+compressDecompressWith+ :: MonadIO m+ => Enumeratee ByteString ByteString m ByteString+ -> WindowBits+ -> [ByteString]+ -> m ByteString+compressDecompressWith enum win xs =+ E.run_ $ E.enumList 1 xs+ $$ joinI $ Z.compress 7 win+ $$ joinI $ enum+ $$ joinI $ Z.decompress win+ $$ consume++-- | Compress a ByteString 'n' times and then decompress it 'n' times+compressDecompressMany+ :: MonadIO m+ => WindowBits+ -> Int+ -> [ByteString]+ -> m ByteString+compressDecompressMany win n xs =+ E.run_ $ E.enumList 1 xs+ $$ concatWith consume es+ where+ es = replicate m (Z.compress 7 win) ++ replicate m (Z.decompress win)+ m = 1 + (abs n `rem` 10) -- restrict n to [1, 10]++-- | Compress a [ByteString] to a file with an Enumeratee+compressFileWith+ :: Enumeratee ByteString ByteString IO ()+ -> WindowBits -> FilePath -> [ByteString] -> IO ()+compressFileWith enum win file xs = bracket+ (openBinaryFile file WriteMode)+ (hClose)+ $ \ h -> do+ run_ $ E.enumList 1 xs+ $$ joinI $ Z.compress 7 win+ $$ joinI $ enum+ $$ EB.iterHandle h++-- | Decompress from a file with an Enumeratee+decompressFileWith+ :: Enumeratee ByteString ByteString IO ByteString+ -> WindowBits -> FilePath -> IO ByteString+decompressFileWith enum win file =+ run_ $ EB.enumFile file+ $$ joinI $ Z.decompress win+ $$ joinI $ enum+ $$ consume++-- | Alternative implementation of compress for comparison+compressChunks :: WindowBits -> [ByteString] -> IO [ByteString]+compressChunks win xs = do+ def <- initDeflate 7 win+ gziped <- foldM (go' def) id xs+ gziped' <- finishDeflate def $ go gziped+ return $ gziped' []+ where+ go' def front bs = withDeflateInput def bs $ go front+ go front x = do+ y <- x+ case y of+ Nothing -> return front+ Just z -> go (front . (:) z) x++-- | Alternative implementation of decompress for comparison+decompressChunks :: WindowBits -> [ByteString] -> IO [ByteString]+decompressChunks win xs = do+ inf <- initInflate win+ ungziped <- foldM (go' inf) id xs+ final <- finishInflate inf+ return $ ungziped [final]+ where+ go' inf front bs = withInflateInput inf bs $ go front+ go front x = do+ y <- x+ case y of+ Nothing -> return front+ Just z -> go (front . (:) z) x++-------------------------------------------------------------------------------+-- Properties+-------------------------------------------------------------------------------++-- | Compare compresssion via lists and enumerator+prop_compress_compare :: WindowBits -> [ByteString] -> Property+prop_compress_compare win xs = monadicIO $ do+ chks <- Q.run $ B.concat `fmap` compressChunks win xs+ enum <- Q.run $ compress win xs+ assert $ enum == chks++-- | Compare decompression via lists and enumerator+prop_decompress_compare :: WindowBits -> [ByteString] -> Property+prop_decompress_compare win xs = monadicIO $ do+ comp <- Q.run $ compressChunks win xs+ chks <- Q.run $ B.concat `fmap` decompressChunks win comp+ enum <- Q.run $ decompress win comp+ assert $ enum == chks++-- | Check: bs == decompress (compress bs)+-- (That is, with separate Enumeratees)+prop_compress_decompress :: WindowBits -> [ByteString] -> Property+prop_compress_decompress win xs = monadicIO $ do+ cs <- Q.run $ compress win xs+ ys <- Q.run $ decompress win [cs]+ assert (B.concat xs == ys)++-- | Check: bs == compressDecompress bs+-- (That is, with a single Enumeratee)+prop_compress_decompress' :: WindowBits -> [ByteString] -> Property+prop_compress_decompress' win xs = monadicIO $ do+ ys <- Q.run $ compressDecompress win xs+ assert (B.concat xs == ys)++-- | Check: bs == gzipUngzip bs+-- (just to see if the default parameters aren't broken)+prop_gzip_ungzip :: [ByteString] -> Property+prop_gzip_ungzip xs = monadicIO $ do+ ys <- Q.run $ gzipUngzip xs+ assert (B.concat xs == ys)++-- | Check if using an Iteratee that consumes only a few bytes works+prop_unconsumed :: WindowBits -> [ByteString] -> Property+prop_unconsumed win xs = monadicIO $ do+ ys <- Q.run $ compressDecompressWith unconsumed win xs+ assert (B.concat xs == ys)++-- | Check if mapping the identity function doesn't affect anything+prop_map_id :: WindowBits -> [ByteString] -> Property+prop_map_id win xs = monadicIO $ do+ ys <- Q.run $ compressDecompressWith (E.map id) win xs+ assert (B.concat xs == ys)++-- | Check if mapping 'reverse . reverse' doesn't affect anything+prop_map_revrev :: WindowBits -> [ByteString] -> Property+prop_map_revrev win xs = monadicIO $ do+ ys <- Q.run $ compressDecompressWith (E.map $ B.reverse . B.reverse) win xs+ assert (B.concat xs == ys)++-- | Check if compressing and decompressing multiple times works+prop_many :: WindowBits -> Int -> [ByteString] -> Property+prop_many win n xs = monadicIO $ do+ ys <- Q.run $ compressDecompressMany win n xs+ assert (B.concat xs == ys)++-- | Check compressing and decompressing a file+prop_files_map_id :: FilePath -> WindowBits -> [ByteString] -> Property+prop_files_map_id file win xs = monadicIO $ do+ Q.run $ compressFileWith enum win file xs+ ys <- Q.run $ decompressFileWith enum win file+ assert (B.concat xs == ys)+ where+ enum = E.map id++-- | Check compressing and decompressing a file with an Iteratee that+-- consumes only a few bytes+prop_files_unconsumed :: FilePath -> WindowBits -> [ByteString] -> Property+prop_files_unconsumed file win xs = monadicIO $ do+ Q.run $ compressFileWith unconsumed win file xs+ ys <- Q.run $ decompressFileWith unconsumed win file+ assert (B.concat xs == ys)++-------------------------------------------------------------------------------+-- Tests+-------------------------------------------------------------------------------++tests :: [Test]+tests = let testFile = "zlib-enum-test-file" in+ [ testGroup "compare"+ [ testProperty "compress_compressChunks" prop_compress_compare+ , testProperty "decompress_decompressChunks" prop_decompress_compare+ ]+ , testGroup "enumList"+ [ testProperty "compress_decompress" prop_compress_decompress+ , testProperty "compress_decompress'" prop_compress_decompress'+ , testProperty "gzip_ungzip" prop_gzip_ungzip+ , testProperty "unconsumed" prop_unconsumed+ , testProperty "map_id" prop_map_id+ , testProperty "map_revrev" prop_map_revrev+ , testProperty "many" prop_many+ ]+ , testGroup "enumFile"+ [ testProperty "files_map_id" (prop_files_map_id testFile)+ , testProperty "files_unconsumed" (prop_files_unconsumed testFile)+ ]+ ]++-------------------------------------------------------------------------------+-- IO+-------------------------------------------------------------------------------++main :: IO ()+main = defaultMain tests
zlib-enum.cabal view
@@ -1,8 +1,8 @@ name: zlib-enum-version: 0.1+version: 0.2 license: MIT license-file: LICENSE-author: Malte Sommerkorn <malte.sommerkorn@googlemail.com>+author: Paulo Tanimoto <ptanimoto@gmail.com> maintainer: Malte Sommerkorn <malte.sommerkorn@googlemail.com> synopsis: Enumerator interface for zlib compression description: zlib-enum is a stop-gap package to provide enumeratees for@@ -13,15 +13,43 @@ build-type: Simple homepage: http://github.com/maltem/zlib-enum +-------------------------------------------------------------------------------+flag test+ description: Build the test executable.+ default: False++------------------------------------------------------------------------------- library+ if flag(test)+ Buildable: False build-depends: base >= 4 && < 5 , bytestring- , mtl >= 2+ , transformers == 0.2.* , enumerator >= 0.4 , zlib-bindings exposed-modules: Codec.Zlib.Enum ghc-options: -Wall +-------------------------------------------------------------------------------+executable zlib-enum-test+ main-is: test.hs+ if flag(test)+ Buildable: True+ build-depends:+ base >= 4.0 && < 5.0+ , bytestring >= 0.9 && < 0.10+ , transformers == 0.2.*+ , enumerator >= 0.4 && < 0.5+ , zlib-bindings >= 0.0 && < 0.1+ , process >= 1.0 && < 1.1+ , test-framework >= 0.3 && < 0.4+ , test-framework-quickcheck2 >= 0.2.9 && < 0.3.0+ , QuickCheck >= 2.4 && < 2.5+ else+ Buildable: False+ ghc-options: -Wall++------------------------------------------------------------------------------- source-repository head type: git location: git://github.com/maltem/zlib-enum.git