pipes-binary 0.1.0.0 → 0.2.0
raw patch · 7 files changed
+256/−184 lines, 7 filesdep +transformersdep ~pipesdep ~pipes-parse
Dependencies added: transformers
Dependency ranges changed: pipes, pipes-parse
Files
- NEWS +13/−0
- PEOPLE +2/−0
- pipes-binary.cabal +8/−7
- src/Control/Proxy/Binary.hs +0/−122
- src/Control/Proxy/Binary/Internal.hs +0/−55
- src/Pipes/Binary.hs +154/−0
- src/Pipes/Binary/Internal.hs +79/−0
NEWS view
@@ -1,3 +1,16 @@+# Version 0.2.0++* API revamped in order to support `pipes-4.0.0` and `pipes-parse-2.0.0`.++* Added functions that act directly upon `Put` and `Get` monads: `encodePut`,+ `decodeGet`, `decodeGetMany`.++* Decoding functions now report the number of bytes that were consumed in order+ to decode a value.++* Re-export `Get`, `Put`, `Binary` and `ByteOffset` from the `binary` package.++ # Version 0.1.0.0 * Initial version.
PEOPLE view
@@ -3,3 +3,5 @@ discussions about the library design. Renzo Carbonara+Gabriel Gonzalez+Kyle Van Berendonck
pipes-binary.cabal view
@@ -1,12 +1,12 @@ name: pipes-binary-version: 0.1.0.0+version: 0.2.0 license: BSD3 license-file: LICENSE copyright: Copyright (c) Renzo Carbonara 2013 author: Renzo Carbonara maintainer: renzocarbonaraλgmail.com stability: Experimental-tested-with: GHC == 7.4.1+tested-with: GHC == 7.6.3 homepage: https://github.com/k0001/pipes-binary bug-reports: https://github.com/k0001/pipes-binary/issues category: Pipes@@ -15,7 +15,7 @@ cabal-version: >=1.8 extra-source-files: README.md PEOPLE NEWS description:- Encode and decode binary streams using the @Pipes@ and @binary@ libraries.+ Encode and decode binary Pipes streams using the @binary@ library. . See the @NEWS@ file in the source distribution to learn about any important changes between version.@@ -29,8 +29,9 @@ build-depends: base (==4.*) , binary (>=0.7 && <0.8) , bytestring (>=0.9.2.1)- , pipes (>=3.3 && <3.4)- , pipes-parse (>=1.0 && <1.1)- exposed-modules: Control.Proxy.Binary- other-modules: Control.Proxy.Binary.Internal+ , pipes (>=4.0 && <4.1)+ , pipes-parse (>=2.0 && <2.1)+ , transformers (>=0.2 && <0.4)+ exposed-modules: Pipes.Binary+ other-modules: Pipes.Binary.Internal ghc-options: -Wall -O2
− src/Control/Proxy/Binary.hs
@@ -1,122 +0,0 @@--- | This module exports facilities that allows you to encode and decode--- streams of 'Bin.Binary' values using the @pipes@ and @pipes-parse@ libraries.--module Control.Proxy.Binary- ( -- * Decoding- -- $decoding- decode- , decodeD- -- * Encoding- -- $encoding- , encode- , encodeD- -- * Types- , I.DecodingError(..)- ) where-----------------------------------------------------------------------------------import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy.Internal as BLI-import Control.Monad (unless)-import qualified Control.Proxy as P-import qualified Control.Proxy.Binary.Internal as I-import qualified Control.Proxy.Parse as Pa-import qualified Control.Proxy.Trans.Either as P-import qualified Control.Proxy.Trans.State as P-import qualified Data.Binary as Bin-import Data.Foldable (mapM_)-import Data.Function (fix)-import Prelude hiding (mapM_)------------------------------------------------------------------------------------- $decoding------ There are two different 'Bin.Binary' decoding facilities exported by this--- module, and choosing between them is easy: If you need to interleave decoding--- with other stream effects you must use 'decode', otherwise you may use the--- simpler 'decodeD'.---- | Decodes one 'Bin.Binary' instance flowing downstream.------ * In case of decoding errors, a 'I.DecodingError' exception is thrown in the--- 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw' when needed.------ * /Do not/ use this proxy if 'Control.Proxy.ByteString.isEndOfBytes' returns--- 'True', otherwise you may get unexpected decoding errors.-decode- :: (P.Proxy p, Monad m, Bin.Binary r)- => P.EitherP I.DecodingError (P.StateP [BS.ByteString] p)- () (Maybe BS.ByteString) y' y m r-decode = do- (er, mlo) <- P.liftP (I.parseWith Pa.draw Bin.get)- P.liftP (mapM_ Pa.unDraw mlo)- either P.throw return er-{-# INLINABLE decode #-}----- | Decodes 'Bin.Binary' instances flowing downstream until end of input.------ * In case of decoding errors, a 'I.DecodingError' exception is thrown in the--- 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw', when needed.------ * Empty input chunks flowing downstream will be discarded.-decodeD- :: (P.Proxy p, Monad m, Bin.Binary b)- => ()- -> P.Pipe (P.EitherP I.DecodingError (P.StateP [BS.ByteString] p))- (Maybe BS.ByteString) b m ()-decodeD = \() -> loop where- loop = do- eof <- P.liftP isEndOfBytes- unless eof $ decode >>= P.respond >> loop-{-# INLINABLE decodeD #-}------------------------------------------------------------------------------------- $encoding------ There are two different 'Bin.Binary' encoding facilities exported by this--- module, and choosing between them is easy: If you need to interleave encoding--- with other stream effects you must use 'encode', otherwise you may use the--- simpler 'encodeD'.---- | Encodes the given 'Bin.Binary' instance and sends it downstream in--- 'BS.ByteString' chunks.-encode- :: (P.Proxy p, Monad m, Bin.Binary x)- => x -> p x' x () BS.ByteString m ()-encode = \x -> P.runIdentityP $ do- BLI.foldrChunks (\e a -> P.respond e >> a) (return ()) (Bin.encode x)-{-# INLINABLE encode #-}----- | Encodes 'Bin.Binary' instances flowing downstream, each in possibly more--- than one 'BS.ByteString' chunk.-encodeD- :: (P.Proxy p, Monad m, Bin.Binary a)- => () -> P.Pipe p a BS.ByteString m r-encodeD = P.pull P./>/ encode-{-# INLINABLE encodeD #-}-------------------------------------------------------------------------------------- XXX: this function is here until pipes-bytestring exports it---- | Like 'Pa.isEndOfInput', except it also consumes and discards leading--- empty 'BS.ByteString' chunks.-isEndOfBytes- :: (Monad m, P.Proxy p)- => P.StateP [BS.ByteString] p () (Maybe BS.ByteString) y' y m Bool-isEndOfBytes = fix $ \loop -> do- ma <- Pa.draw- case ma of- Just a- | BS.null a -> loop- | otherwise -> Pa.unDraw a >> return False- Nothing -> return True-{-# INLINABLE isEndOfBytes #-}-
− src/Control/Proxy/Binary/Internal.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}---- | This module provides low-level integration with the @binary@ package and is--- likely to be modified in backwards-incompatible ways in the future.------ Use the "Control.Proxy.Binary" module instead.--module Control.Proxy.Binary.Internal- ( DecodingError(..)- , parseWith- ) where-----------------------------------------------------------------------------------import qualified Data.ByteString as BS-import qualified Data.Binary as Bin-import qualified Data.Binary.Get as Bin-import Control.Exception (Exception)-import Data.Data (Data, Typeable)-----------------------------------------------------------------------------------data DecodingError = DecodingError- { peConsumed :: Bin.ByteOffset -- ^Number of bytes consumed before the error.- , peMessage :: String -- ^Error message.- } deriving (Show, Eq, Data, Typeable)--instance Exception DecodingError------------------------------------------------------------------------------------- | Run a parser drawing input from the given monadic action as needed.-parseWith- :: (Monad m, Bin.Binary r)- => m (Maybe BS.ByteString)- -- ^An action that will be executed to provide the parser with more input- -- as needed. If the action returns 'Nothing', then it's assumed no more- -- input is available.- -> Bin.Get r- -- ^Parser to run on the given input.- -> m (Either DecodingError r, Maybe BS.ByteString)- -- ^Either a parser error or a parsed result, together with any leftover.-parseWith refill g = step $ Bin.runGetIncremental g- where- step (Bin.Partial k) = step . k =<< refill- step (Bin.Done lo _ r) = return (Right r, mayInput lo)- step (Bin.Fail lo n m) = return (Left (DecodingError n m), mayInput lo)-{-# INLINABLE parseWith #-}---- | Wrap @a@ in 'Just' if not-null. Otherwise, 'Nothing'.-mayInput :: BS.ByteString -> Maybe BS.ByteString-mayInput x | BS.null x = Nothing- | otherwise = Just x-{-# INLINE mayInput #-}-
+ src/Pipes/Binary.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE RankNTypes #-}++-- | This module exports facilities that allow you to encode and decode Pipes+-- streams of binary values. It builds on top of the @binary@, @pipes@ and+-- @pipes-parse@ libraries, and assumes you know how to use those libraries.++module Pipes.Binary+ ( -- * @Binary@ instances+ encode+ , decode+ , decodeMany+ -- * @Get@ monad+ , decodeGet+ , decodeGetMany+ -- * @Put@ monad+ , encodePut+ -- * Types+ , I.DecodingError(..)+ -- * Exports+ -- $exports+ , isEndOfBytes+ , module Data.Binary.Get+ ) where++-------------------------------------------------------------------------------++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy.Internal as BLI+import Pipes+import Pipes.Core+import qualified Pipes.Binary.Internal as I+import qualified Pipes.Lift as P+import qualified Pipes.Parse as Pp+import qualified Data.Binary as Bin (get, put)+import qualified Data.Binary.Put as Put (runPut)+--------------------------------------------------------------------------------+import Data.Binary (Binary)+import Data.Binary.Get (ByteOffset, Get)+import Data.Binary.Put (Put)++-- $exports+--+-- The following types are re-exported on this module for your convenience:+--+-- [From "Data.Binary"] 'Binary'.+--+-- [From "Data.Binary.Get"] 'Get', 'ByteOffset'.+--+-- [From "Data.Binary.Put"] 'Put'.++--------------------------------------------------------------------------------++-- | Try to decode leading output from the underlying 'Producer' into a+-- 'Bin.Binary' instance, returning either a 'I.DecodingError' on failure, or a+-- pair with the decoded entity together with the number of bytes consumed in+-- order to produce it.+--+-- /Do not/ use this function if 'isEndOfBytes' returns 'True', otherwise you+-- may get unexpected decoding errors.+decode+ :: (Monad m, Binary b)+ => Pp.StateT (Producer B.ByteString m r) m+ (Either I.DecodingError (ByteOffset, b)) -- ^+decode = decodeGet Bin.get+{-# INLINABLE decode #-}++-- | Like 'decode', except it takes an explicit 'Bin.Get' monad.+decodeGet+ :: Monad m+ => Get b -- ^+ -> Pp.StateT (Producer B.ByteString m r) m+ (Either I.DecodingError (ByteOffset, b))+decodeGet get = do+ (er, mlo) <- I.parseWithDraw get+ case mlo of+ Just lo -> Pp.unDraw lo+ Nothing -> return ()+ return er+{-# INLINABLE decodeGet #-}++-- | Continuously decode output from the given 'Producer' into a 'Bin.Binary'+-- instance, sending downstream pairs of each successfully decoded entity+-- together with the number of bytes consumed in order to produce it.+--+-- This 'Producer' runs until it either runs out of input or a decoding+-- failure occurs, in which case it returns 'Left' with a 'I.DecodingError' and+-- a 'Producer' with any leftovers. You can use 'P.errorP' to turn the 'Either'+-- return value into an 'Control.Monad.Trans.Error.ErrorT' monad transformer.+decodeMany+ :: (Monad m, Binary b)+ => Producer B.ByteString m r -- ^Producer from which to draw input.+ -> Producer' (ByteOffset, b) m+ (Either (I.DecodingError, Producer B.ByteString m r) r)+decodeMany src = decodeGetMany Bin.get src+{-# INLINABLE decodeMany #-}++-- | Like 'decodeMany', except it takes an explicit 'Bin.Get' monad.+decodeGetMany+ :: Monad m+ => Get b+ -> Producer B.ByteString m r -- ^Producer from which to draw input.+ -> Producer' (ByteOffset, b) m+ (Either (I.DecodingError, Producer B.ByteString m r) r)+decodeGetMany get src = do+ (me, src') <- P.runStateP src go+ return $ case me of+ Left e -> Left (e, src')+ Right r -> Right r+ where+ go = do+ eof <- lift isEndOfBytes+ if eof+ then do+ ra <- lift Pp.draw+ case ra of+ Left r -> return (Right r)+ Right _ -> error "Pipes.Binary.decodeGetMany: impossible!"+ else do+ eb <- lift (decodeGet get)+ case eb of+ Left e -> return (Left e)+ Right b -> yield b >> go+{-# INLINABLE decodeGetMany #-}++--------------------------------------------------------------------------------++-- | Encodes the given 'Bin.Binary' instance and sends it downstream in+-- 'BS.ByteString' chunks.+encode :: (Monad m, Binary x) => x -> Producer' B.ByteString m ()+encode = \x -> encodePut (Bin.put x)+{-# INLINABLE encode #-}++-- | Like 'encode', except it takes an explicit 'Bin.Put' monad.+encodePut :: Monad m => Put -> Producer' B.ByteString m ()+encodePut = \put -> do+ BLI.foldrChunks (\e a -> respond e >> a) (return ()) (Put.runPut put)+{-# INLINABLE encodePut #-}++--------------------------------------------------------------------------------+-- XXX: this function is here until pipes-bytestring exports it++-- | Checks if the underlying 'Producer' has any bytes left.+-- Leading 'BS.empty' chunks are discarded.+isEndOfBytes :: Monad m => Pp.StateT (Producer B.ByteString m r) m Bool+isEndOfBytes = do+ ma <- Pp.draw+ case ma of+ Left _ -> return True+ Right a+ | B.null a -> isEndOfBytes+ | otherwise -> Pp.unDraw a >> return False+{-# INLINABLE isEndOfBytes #-}+{-# DEPRECATED isEndOfBytes+ "Will be removed as soon as the `pipes-bytestring` library exports it" #-}
+ src/Pipes/Binary/Internal.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}++-- | This module provides low-level integration with the @binary@ package and is+-- likely to be modified in backwards-incompatible ways in the future.+--+-- Use the "Pipes.Binary" module instead.++module Pipes.Binary.Internal+ ( DecodingError(..)+ , parseWithDraw+ ) where++-------------------------------------------------------------------------------++import Control.Exception (Exception)+import Control.Monad.Trans.Error (Error)+import qualified Data.ByteString as B+import qualified Data.Binary.Get as Get+import Data.Data (Data, Typeable)+import Pipes (Producer)+import qualified Pipes.Parse as Pp++-------------------------------------------------------------------------------++-- | A 'Get.Get' decoding error, as provided by 'Get.Fail'.+data DecodingError = DecodingError+ { peConsumed :: Get.ByteOffset -- ^Number of bytes consumed before the error.+ , peMessage :: String -- ^Error message.+ } deriving (Show, Read, Eq, Data, Typeable)++instance Exception DecodingError+instance Error DecodingError++-------------------------------------------------------------------------------++instance Monad m => Error (DecodingError, Producer B.ByteString m r)++-------------------------------------------------------------------------------++-- | Run a 'Get.Get' drawing input from the given monadic action as needed.+parseWith+ :: Monad m+ => m (Maybe B.ByteString)+ -- ^An action that will be executed to provide the parser with more input+ -- as needed. If the action returns 'Nothing', then it's assumed no more+ -- input is available.+ -> Get.Get r+ -- ^Parser to run on the given input.+ -> m (Either DecodingError (Get.ByteOffset, r), Maybe B.ByteString)+ -- ^Either a decoding error or a pair of a result and the number of bytes+ -- consumed, as well as an any leftovers.+parseWith refill = \g -> step (Get.runGetIncremental g)+ where+ step (Get.Partial k) = refill >>= \a -> step (k a)+ step (Get.Done lo n r) = return (Right (n, r), mayInput lo)+ step (Get.Fail lo n m) = return (Left (DecodingError n m), mayInput lo)+{-# INLINABLE parseWith #-}++-- | Run a parser drawing input from the underlying 'Producer'.+parseWithDraw+ :: Monad m+ => Get.Get b -- ^Parser to run on the given input.+ -> Pp.StateT (Producer B.ByteString m r) m+ (Either DecodingError (Get.ByteOffset, b), Maybe B.ByteString)+parseWithDraw = parseWith $ do+ ea <- Pp.draw+ return (case ea of+ Left _ -> Nothing+ Right a -> Just a)+{-# INLINABLE parseWithDraw #-}++--------------------------------------------------------------------------------++-- | Wrap @a@ in 'Just' if not-null. Otherwise, 'Nothing'.+mayInput :: B.ByteString -> Maybe B.ByteString+mayInput x | B.null x = Nothing+ | otherwise = Just x+{-# INLINE mayInput #-}