packages feed

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 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 #-}