packages feed

codec 0.1.1 → 0.2

raw patch · 24 files changed

+524/−1004 lines, 24 filesdep +codecdep +generic-arbitrarydep +profunctorsdep −data-default-classdep ~aesondep ~basedep ~binaryPVP ok

version bump matches the API change (PVP)

Dependencies added: codec, generic-arbitrary, profunctors, tasty, tasty-quickcheck, vector

Dependencies removed: data-default-class

Dependency ranges changed: aeson, base, binary, binary-bits, bytestring, mtl, template-haskell, text, transformers, unordered-containers

API changes (from Hackage documentation)

- Data.Aeson.Codec: entry :: Text -> JSONCodec a -> ObjectCodec a
- Data.Aeson.Codec: instance (ToJSON a, FromJSON a) => Default (JSONCodec a)
- Data.Aeson.Codec: instance (ToJSON a, FromJSON a) => IsString (ObjectCodec a)
- Data.Aeson.Codec: obj :: String -> ObjectCodec a -> JSONCodec a
- Data.Aeson.Codec: pair :: ToJSON a => Text -> a -> ObjectBuilder ()
- Data.Aeson.Codec: type JSONCodec a = ConcreteCodec Value Parser a
- Data.Binary.Codec: toLazyByteString :: BinaryCodec a -> ConcreteCodec ByteString (Either String) a
- Data.Codec.Codec: (<->) :: Alternative fr => PartialCodec fr fw a -> PartialCodec fr fw a -> PartialCodec fr fw a
- Data.Codec.Codec: (>-<) :: Functor fr => Field r a x y -> Codec fr fw a -> Build r (Codec' fr fw r) x y
- Data.Codec.Codec: Codec :: fr r -> (w -> fw ()) -> Codec' fr fw w r
- Data.Codec.Codec: assume :: (a -> Bool) -> Codec fr fw a -> PartialCodec fr fw a
- Data.Codec.Codec: cbuild :: (Functor fr, Buildable r y) => Con r x -> Build r (Codec' fr fw r) x y -> PartialCodec fr fw r
- Data.Codec.Codec: concrete :: (b -> f a) -> (a -> b) -> ConcreteCodec b f a
- Data.Codec.Codec: covered :: PartialCodec fr fw a -> Codec fr fw a
- Data.Codec.Codec: data Codec' fr fw w r
- Data.Codec.Codec: instance (Applicative fw, Applicative fr) => Applicative (Codec' fr fw w)
- Data.Codec.Codec: instance Functor fr => Functor (Codec' fr fw w)
- Data.Codec.Codec: mapCodec :: Functor fr => (a -> b) -> (b -> a) -> Codec fr fw a -> Codec fr fw b
- Data.Codec.Codec: mapCodecF :: (fr a -> gr a) -> (fw () -> gw ()) -> Codec fr fw a -> Codec gr gw a
- Data.Codec.Codec: mapCodecM :: (Monad fr, Monad fw) => (a -> fr b) -> (b -> fw a) -> Codec fr fw a -> Codec fr fw b
- Data.Codec.Codec: opt :: (Alternative fr, Applicative fw) => Codec fr fw a -> Codec fr fw (Maybe a)
- Data.Codec.Codec: parse :: Codec' fr fw w r -> fr r
- Data.Codec.Codec: parseVal :: ConcreteCodec b f a -> b -> f a
- Data.Codec.Codec: produce :: Codec' fr fw w r -> w -> fw ()
- Data.Codec.Codec: produceMaybe :: PartialCodec fr fw a -> a -> Maybe (fw ())
- Data.Codec.Codec: produceVal :: ConcreteCodec b f a -> a -> b
- Data.Codec.Codec: type Codec fr fw a = Codec' fr fw a a
- Data.Codec.Codec: type ConcreteCodec b f a = Codec (ReaderT b f) (Const b) a
- Data.Codec.Codec: type PartialCodec fr fw a = Codec fr (Compose Maybe fw) a
- Data.Codec.Field: ($>>) :: (Functor f, Buildable r y) => x -> Build r f x y -> f r
- Data.Codec.Field: (>>>) :: Category k cat => cat a b -> cat b c -> cat a c
- Data.Codec.Field: Build :: (f (x -> y)) -> Build r f x y
- Data.Codec.Field: Con :: x -> (r -> Bool) -> Con r x
- Data.Codec.Field: Field :: (a -> x -> y) -> (r -> a) -> Field r a x y
- Data.Codec.Field: X :: X
- Data.Codec.Field: build :: (Functor f, Buildable r y) => x -> Build r f x y -> f r
- Data.Codec.Field: class Buildable r a
- Data.Codec.Field: data Con r x
- Data.Codec.Field: data Field r a x y
- Data.Codec.Field: data X
- Data.Codec.Field: done :: Applicative f => Build r f x x
- Data.Codec.Field: give :: Buildable r a => a -> r
- Data.Codec.Field: having :: Functor f => Field r a x y -> f a -> Build r f x y
- Data.Codec.Field: instance Applicative f => Category (Build r f)
- Data.Codec.Field: instance Buildable r b => Buildable r (X -> b)
- Data.Codec.Field: instance Buildable r r
- Data.Codec.Field: newtype Build r f x y
- Data.Codec.TH: genFields :: Name -> Q [Dec]
- Data.Codec.Testing: class ParseResult f
- Data.Codec.Testing: instance ParseResult (Either String)
- Data.Codec.Testing: instance ParseResult Maybe
- Data.Codec.Testing: instance ParseResult Result
- Data.Codec.Testing: roundTrip :: ParseResult f => ConcreteCodec c f a -> a -> Either String a
- Data.Codec.Testing: roundTripStorable :: Storable a => a -> IO a
- Data.Codec.Testing: toEither :: ParseResult f => f a -> Either String a
- Data.Codec.Tuple: c_Left :: Con (Either a b) (a -> Either a b)
- Data.Codec.Tuple: c_Right :: Con (Either a b) (b -> Either a b)
- Data.Codec.Tuple: class Field1 r a x y | r x -> y, r y -> x, r -> a, x y -> r
- Data.Codec.Tuple: class Field2 r a x y | r x -> y, r y -> x, r -> a, x y -> r
- Data.Codec.Tuple: f_1 :: Field1 r a x y => Field r a x y
- Data.Codec.Tuple: f_2 :: Field2 r a x y => Field r a x y
- Data.Codec.Tuple: f_left :: Field (Either a b) a (a -> Either a b) (X -> Either a b)
- Data.Codec.Tuple: f_right :: Field (Either a b) b (b -> Either a b) (X -> Either a b)
- Data.Codec.Tuple: instance Field1 (a, b) a (a -> a2 -> (a, b)) (X -> a2 -> (a, b))
- Data.Codec.Tuple: instance Field2 (a, b) b (a1 -> b -> (a, b)) (a1 -> X -> (a, b))
- Foreign.Codec: cBool :: Integral c => ForeignCodec' c Bool
- Foreign.Codec: cast :: (Integral c, Storable c, Integral a) => ForeignCodec' c a
- Foreign.Codec: codecFor :: c -> ForeignCodec' c a -> ForeignCodec' c a
- Foreign.Codec: field :: Int -> ForeignCodec' f a -> ForeignCodec' p a
- Foreign.Codec: peekWith :: ForeignCodec' p a -> Ptr p -> IO a
- Foreign.Codec: pokeWith :: ForeignCodec' p a -> Ptr p -> a -> IO ()
- Foreign.Codec: storable :: Storable a => ForeignCodec a
- Foreign.Codec: type ForeignCodec a = ForeignCodec' a a
- Foreign.Codec: type ForeignCodec' p a = Codec (ForeignContext p) (ForeignContext p) a
- Foreign.Codec: type ForeignContext a = ReaderT (Ptr a) IO
+ Control.Monad.Codec: (=.) :: (c' -> c) -> CodecFor r w c a -> CodecFor r w c' a
+ Control.Monad.Codec: Codec :: r a -> (c -> w a) -> CodecFor r w c a
+ Control.Monad.Codec: [codecIn] :: CodecFor r w c a -> r a
+ Control.Monad.Codec: [codecOut] :: CodecFor r w c a -> c -> w a
+ Control.Monad.Codec: data CodecFor r w c a
+ Control.Monad.Codec: fmapArg :: Functor f => (a -> f ()) -> a -> f a
+ Control.Monad.Codec: instance (GHC.Base.Applicative r, GHC.Base.Applicative w) => GHC.Base.Applicative (Control.Monad.Codec.CodecFor r w c)
+ Control.Monad.Codec: instance (GHC.Base.Functor r, GHC.Base.Functor w) => Data.Profunctor.Unsafe.Profunctor (Control.Monad.Codec.CodecFor r w)
+ Control.Monad.Codec: instance (GHC.Base.Functor w, GHC.Base.Functor r) => GHC.Base.Functor (Control.Monad.Codec.CodecFor r w c)
+ Control.Monad.Codec: instance (GHC.Base.Monad r, GHC.Base.Monad w) => GHC.Base.Monad (Control.Monad.Codec.CodecFor r w c)
+ Control.Monad.Codec: type Codec r w a = CodecFor r w a a
+ Data.Aeson.Codec: JSONCodec :: (Value -> Parser a) -> (a -> Value) -> (a -> Encoding) -> JSONCodec a
+ Data.Aeson.Codec: [parseJSONCodec] :: JSONCodec a -> Value -> Parser a
+ Data.Aeson.Codec: [toEncodingCodec] :: JSONCodec a -> a -> Encoding
+ Data.Aeson.Codec: [toJSONCodec] :: JSONCodec a -> a -> Value
+ Data.Aeson.Codec: arrayOf :: (FromJSON b, ToJSON b) => (a -> [b]) -> ([b] -> a) -> JSONCodec a
+ Data.Aeson.Codec: arrayOf' :: (a -> [b]) -> ([b] -> a) -> JSONCodec b -> JSONCodec a
+ Data.Aeson.Codec: asArray :: String -> ArrayCodec a -> JSONCodec a
+ Data.Aeson.Codec: asObject :: String -> ObjectCodec a -> JSONCodec a
+ Data.Aeson.Codec: data JSONCodec a
+ Data.Aeson.Codec: defJSON :: (FromJSON a, ToJSON a) => JSONCodec a
+ Data.Aeson.Codec: element :: (FromJSON a, ToJSON a) => ArrayCodec a
+ Data.Aeson.Codec: element' :: JSONCodec a -> ArrayCodec a
+ Data.Aeson.Codec: field :: (FromJSON a, ToJSON a) => Text -> ObjectCodec a
+ Data.Aeson.Codec: field' :: Text -> JSONCodec a -> ObjectCodec a
+ Data.Aeson.Codec: type ArrayBuilder = Writer (Series, [Value])
+ Data.Aeson.Codec: type ArrayCodec a = Codec ArrayParser ArrayBuilder a
+ Data.Aeson.Codec: type ArrayParser = StateT [Value] Parser
+ Data.Binary.Codec: int16be :: BinaryCodec Int16
+ Data.Binary.Codec: int16host :: BinaryCodec Int16
+ Data.Binary.Codec: int16le :: BinaryCodec Int16
+ Data.Binary.Codec: int32be :: BinaryCodec Int32
+ Data.Binary.Codec: int32host :: BinaryCodec Int32
+ Data.Binary.Codec: int32le :: BinaryCodec Int32
+ Data.Binary.Codec: int64be :: BinaryCodec Int64
+ Data.Binary.Codec: int64host :: BinaryCodec Int64
+ Data.Binary.Codec: int64le :: BinaryCodec Int64
+ Data.Binary.Codec: int8 :: BinaryCodec Int8
+ Data.Binary.Codec: inthost :: BinaryCodec Int
- Data.Aeson.Codec: type ObjectBuilder = Const (Endo [Pair])
+ Data.Aeson.Codec: type ObjectBuilder = Writer (Series, Endo [Pair])

Files

− Data/Aeson/Codec.hs
@@ -1,54 +0,0 @@-module Data.Aeson.Codec-  (-  -- * JSON codecs-     JSONCodec-  -- * JSON object codecs-  , ObjectParser, ObjectBuilder, ObjectCodec-  , entry, pair, obj-  ) where--import Control.Applicative-import Data.Aeson-import Data.Aeson.Types (Parser, Pair)-import Control.Monad.Reader-import Control.Monad.Writer-import Data.Default.Class-import qualified Data.Text as T-import Data.String--import Data.Codec---- | JSON codec. This is just a `ToJSON`/`FromJSON` implementation wrapped up in newtypes.--- Use `def` to get a `JSONCodec` for a `ToJSON`/`FromJSON` instance.-type JSONCodec a = ConcreteCodec Value Parser a--instance (ToJSON a, FromJSON a) => Default (JSONCodec a) where-  def = Codec (ReaderT parseJSON) (Const . toJSON)--type ObjectParser = ReaderT Object Parser-type ObjectBuilder = Const (Endo [ Pair ])---- | A codec that parses values out of a given `Object`, and produces--- key-value pairs into a new one.-type ObjectCodec a = Codec ObjectParser ObjectBuilder a---- | Produce a key-value pair.-pair :: ToJSON a => T.Text -> a -> ObjectBuilder ()-pair key val = Const $ Endo ((key .= val):)---- | Read\/write a given value from/to a given key in the current object, using a given sub-codec.--- ObjectCodec's `IsString` instance is equal to `entry` `def`.-entry :: T.Text -> JSONCodec a -> ObjectCodec a-entry key cd = Codec-  { parse = ReaderT $ \o -> (o .: key) >>= parseVal cd-  , produce = pair key . produceVal cd-  }---- | Turn an `ObjectCodec` into a `JSONCodec` with an expected name (see `withObject`).-obj :: String -> ObjectCodec a -> JSONCodec a-obj err (Codec r w) = concrete-  (withObject err $ runReaderT r)-  (\x -> object $ appEndo (getConst $ w x) [])--instance (ToJSON a, FromJSON a) => IsString (ObjectCodec a) where-  fromString s = entry (fromString s) def
− Data/Binary/Bits/Codec.hs
@@ -1,37 +0,0 @@-module Data.Binary.Bits.Codec-  ( BitCodec-  , bool-  , word8, word16be, word32be, word64be-  , toBytes-  )-where--import Control.Applicative-import qualified Data.Binary.Bits.Get as G-import Data.Binary.Bits.Put-import qualified Data.Binary.Codec as B--import Data.Codec-import Data.Word--type BitCodec a = Codec G.Block BitPut a--bool :: BitCodec Bool-bool = Codec G.bool putBool--word8 :: Int -> BitCodec Word8-word8 = Codec <$> G.word8 <*> putWord8--word16be :: Int -> BitCodec Word16-word16be = Codec <$> G.word16be <*> putWord16be--word32be :: Int -> BitCodec Word32-word32be = Codec <$> G.word32be <*> putWord32be--word64be :: Int -> BitCodec Word64-word64be = Codec <$> G.word64be <*> putWord64be---- | Convert a `BitCodec` into a `B.BinaryCodec`.-toBytes :: BitCodec a -> B.BinaryCodec a-toBytes (Codec r w)-  = Codec (G.runBitGet $ G.block r) (runBitPut . w)
− Data/Binary/Codec.hs
@@ -1,73 +0,0 @@-module Data.Binary.Codec-  (-  -- * Binary codecs-    BinaryCodec-  , byteString-  , toLazyByteString-  , word8-  , word16be, word16le, word16host-  , word32be, word32le, word32host-  , word64be, word64le, word64host-  , wordhost-  )- where--import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as LBS-import Data.Binary.Get-import Data.Binary.Put-import Data.Word--import Data.Codec.Codec--type BinaryCodec a = Codec Get PutM a---- | Get/put an n-byte field.-byteString :: Int -> BinaryCodec BS.ByteString-byteString n = Codec-  { parse = getByteString n-  , produce = \bs -> if BS.length bs == n-      then putByteString bs-      else fail "ByteString wrong size for field."-  }--word8 :: BinaryCodec Word8-word8 = Codec getWord8 putWord8--word16be :: BinaryCodec Word16-word16be = Codec getWord16be putWord16be--word16le :: BinaryCodec Word16-word16le = Codec getWord16le putWord16le--word16host :: BinaryCodec Word16-word16host = Codec getWord16host putWord16host--word32be :: BinaryCodec Word32-word32be = Codec getWord32be putWord32be--word32le :: BinaryCodec Word32-word32le = Codec getWord32le putWord32le--word32host :: BinaryCodec Word32-word32host = Codec getWord32host putWord32host--word64be :: BinaryCodec Word64-word64be = Codec getWord64be putWord64be--word64le :: BinaryCodec Word64-word64le = Codec getWord64le putWord64le--word64host :: BinaryCodec Word64-word64host = Codec getWord64host putWord64host--wordhost :: BinaryCodec Word-wordhost = Codec getWordhost putWordhost---- | Convert a `BinaryCodec` into a `ConcreteCodec` on lazy `LBS.ByteString`s.-toLazyByteString :: BinaryCodec a -> ConcreteCodec LBS.ByteString (Either String) a-toLazyByteString (Codec r w) = concrete-  (\bs -> case runGetOrFail r bs of-    Left ( _ , _, err ) -> Left err-    Right ( _, _, x ) -> Right x)-  (runPut . w)
− Data/Codec.hs
@@ -1,50 +0,0 @@-module Data.Codec (-  -- $constructing-    module Data.Codec.Field-  , module Data.Codec.Codec-  , module Data.Codec.TH-  , module Data.Codec.Tuple-  ) where--import Data.Codec.Field -import Data.Codec.Codec-import Data.Codec.TH-import Data.Codec.Tuple---- $constructing--- The main purpose of this package is to make the creation of `Codec`s as easy and painless as possible.--- If we have a data type such as:------ @--- data User = User---  { username :: Text---  , userEmail :: Text---  , userLanguages :: [ Text ]---  , userReferrer :: Maybe Text---  } deriving Show--- @------ we can use the `genFields` function to generate `Field`s for each record field:------ @--- genFields ''User--- @------ This will create `Field`s named @f_username@, @f_userEmail@, etc. These fields can be associated with an--- appropriate `Codec` with the `>-<` operator to specify the representation of the data structure. These--- associations can then be combined with the `>>>` operator in the order of serialization/deserialization.--- These associations can then be finalized into a `Codec` by providng the constructor to use.--- For example, using the JSON `entry` `Codec` that assigns a value to a JSON key, we could write a codec for--- @User@ as:------ @---  userCodec :: JSONCodec User---  userCodec = obj "user object' $---    User---      $>> f_username      >-< "user"---      >>> f_userEmail     >-< "email"---      >>> f_userLanguages >-< "languages"---      >>> f_userReferrer  >-< opt "referrer"--- @------ The type system ensures that every field is provided exactly once.
− Data/Codec/Codec.hs
@@ -1,115 +0,0 @@-module Data.Codec.Codec-  ( -- * Codecs-    Codec'(..), Codec-  , (>-<)-    -- * Concrete codecs-  , ConcreteCodec, concrete, parseVal, produceVal-    -- * Partial codecs-    -- | Partial codecs are useful for creating codecs-    -- for types with multiple constructors. See @examples/Multi.hs@.-  , PartialCodec, cbuild, assume, covered, (<->), produceMaybe-    -- * Codec combinators-  , opt, mapCodec, mapCodecF, mapCodecM-  )-where--import Control.Applicative-import Control.Monad ((>=>))-import Control.Monad.Reader (ReaderT(..))-import Data.Codec.Field-import Data.Functor.Compose-import Data.Maybe (fromMaybe)---- | De/serializer for the given types. Usually w ~ r, but they are separate--- to allow for an `Applicative` instance.-data Codec' fr fw w r = Codec-  { parse :: fr r-  , produce :: w -> fw () -  }-  deriving Functor---- | De/serializer for @a@.-type Codec fr fw a = Codec' fr fw a a---- Build up a serializer in parallel to a deserializer.-instance (Applicative fw, Applicative fr) => Applicative (Codec' fr fw w) where-  pure x = Codec (pure x) (const $ pure ())-  Codec f fw <*> Codec x xw-    = Codec (f <*> x) (\w -> fw w *> xw w)---- | Associate a `Field` with a `Codec` to create a `Codec` `Build`.-(>-<) :: Functor fr => Field r a x y -> Codec fr fw a -> Build r (Codec' fr fw r) x y-Field c g >-< Codec r w-  = Build (c <$> Codec r (w . g))---- Codec combinators---- | Given a `Codec` for @a@, make one for `Maybe` @a@ that applies its deserializer optionally--- and does nothing when serializing `Nothing`.-opt :: (Alternative fr, Applicative fw) => Codec fr fw a -> Codec fr fw (Maybe a)-opt (Codec r w) = Codec (optional r) (maybe (pure ()) w)---- | Turn a @`Codec` a@ into a @`Codec` b@ by providing an isomorphism.-mapCodec :: Functor fr => (a -> b) -> (b -> a) -> Codec fr fw a -> Codec fr fw b-mapCodec to from (Codec r w)-  = Codec (to <$> r) (w . from)---- | Map a field codec monadically. Useful for error handling but care must be taken to make sure that--- the results are still complementary.-mapCodecM :: (Monad fr, Monad fw) => (a -> fr b) -> (b -> fw a) -> Codec fr fw a -> Codec fr fw b-mapCodecM to from (Codec r w)-  = Codec (r >>= to) (from >=> w)---- | Map the contexts of a given `Codec`.-mapCodecF :: (fr a -> gr a) -> (fw () -> gw ()) -> Codec fr fw a -> Codec gr gw a-mapCodecF fr fw (Codec r w)-  = Codec (fr r) (fw . w)---- | A codec where `a` can be produced from a concrete value of `b` in context `f`,--- and a concrete type of value `b` can always be produced.-type ConcreteCodec b f a = Codec (ReaderT b f) (Const b) a---- | Create a concrete codec from a reader and a writer.-concrete :: (b -> f a) -> (a -> b) -> ConcreteCodec b f a-concrete r w = Codec (ReaderT r) (Const . w)---- | Parse a concrete value with a given `ConcreteCodec`.-parseVal :: ConcreteCodec b f a -> b -> f a-parseVal (Codec r _) = runReaderT r---- | Produce a concrete value with a given `ConcreteCodec`.-produceVal :: ConcreteCodec b f a -> a -> b-produceVal (Codec _ w) = getConst . w---- | A codec that can only serialize a subset of values.-type PartialCodec fr fw a = Codec fr (Compose Maybe fw) a---- | Finish a codec construction with a @`Con` r@ to produce a `PartialCodec`.--- This will check that the given record has the appropriate constructor--- before serializing.-cbuild :: (Functor fr, Buildable r y)-  => Con r x -> Build r (Codec' fr fw r) x y -> PartialCodec fr fw r-cbuild (Con c p) = assume p . build c---- | Guard a `Codec` with a predicate to create a `PartialCodec`.-assume :: (a -> Bool) -> Codec fr fw a -> PartialCodec fr fw a-assume p (Codec r w)-  = Codec r (\x -> Compose $ if p x then Just (w x) else Nothing)---- | Convert a `PartialCodec` into a `Codec`, throwing an error--- on values it cannot serialize.-covered :: PartialCodec fr fw a -> Codec fr fw a-covered cd-  = Codec (parse cd) (fromMaybe (error "Could not serialize value.") . produceMaybe cd)---- | Combine alternative `PartialCodec`s.-(<->) :: Alternative fr => PartialCodec fr fw a -> PartialCodec fr fw a -> PartialCodec fr fw a-cd <-> acd = Codec-  { parse = parse cd <|> parse acd-  , produce = \x -> Compose $ produceMaybe cd x <|> produceMaybe acd x-}---- | Attempt to get a serialization for a given value.-produceMaybe :: PartialCodec fr fw a -> a -> Maybe (fw ())-produceMaybe (Codec _ w) x-  = getCompose (w x)
− Data/Codec/Field.hs
@@ -1,68 +0,0 @@-module Data.Codec.Field-  ( -  -- * First-class record construction-    Field(..)-  , Build(..)-  , Con(..)-  , ($>>), (>>>), done-  , X(X), Buildable(..)-  , having, build-  ) where--import Control.Applicative-import Control.Category-import Prelude hiding ((.), id)---- | `Field`s partially apply constructors and replace arguments with this type.-data X = X---- | The class of constructor applications that have been completely filled in by composing--- `Build`s. If you see an error message involving this, it means that you forgot to specify--- a `Build` for a field.-class Buildable r a where-  give :: a -> r--instance Buildable r r where-  give = id--instance Buildable r b => Buildable r (X -> b) where-  give f = give $ f X---- | Describes how to apply a constructor argument and how to extract from a record.--- @y@ should be @x@ with one argument knocked out: e. g.------ @--- Field MyType Int (Int -> a2 -> MyType) (X -> a2 -> MyType)--- @-data Field r a x y = Field (a -> x -> y) (r -> a)---- Static (Backwards f) + phantom parameter--- | An ongoing record construction of an @r@ in context @f@.--- Applicative actions are sequenced in the direction of `>>>`.-newtype Build r f x y = Build (f (x -> y))---- | Combine a `Field` and a way to produce an @a@ to get a `Build`.-having :: Functor f => Field r a x y -> f a -> Build r f x y-having (Field c _) p = Build (c <$> p)---- | No-op `Build` (same as `id`).-done :: Applicative f => Build r f x x-done = id--instance Applicative f => Category (Build r f) where-  id = Build (pure id)-  Build f . Build g-    = Build ((>>>) <$> g <*> f)---- | Finish a construction given a constructor.-build :: (Functor f, Buildable r y) => x -> Build r f x y -> f r-build x (Build b)-  = (\f -> give $ f x) <$> b---- | Infix version of `build`.-($>>) :: (Functor f, Buildable r y) => x -> Build r f x y -> f r-($>>) = build-infixr 1 $>>---- | A constructor for a given record and a way to check whether it has it.-data Con r x = Con x (r -> Bool)
− Data/Codec/TH.hs
@@ -1,89 +0,0 @@-module Data.Codec.TH (genFields) where--import Control.Applicative-import Data.Foldable (foldl')-import Data.Traversable (for, traverse)-import Language.Haskell.TH as TH-import Language.Haskell.TH.Syntax as TH--import Data.Codec.Field as F--replaceAt :: a -> Int -> [ a ] -> [ a ]-replaceAt x i xs = pr ++ x : suf-  where ( pr, _ : suf ) = splitAt i xs--deleteAt :: Int -> [ a ] -> [ a ]-deleteAt i xs = pr ++ suf-  where ( pr, _ : suf ) = splitAt i xs--fun :: Type -> Type -> Type-fun x = AppT (AppT ArrowT x)--genField :: [ Name ] -> Type -> Int -> ( Int, VarStrictType ) -> Q [ Dec ]-genField recVars recType fc ( i, ( fn, _, ft ) ) = do-  polyNames <- for [1..fc] $ \j -> do-    let pn = "arg" ++ show j-    if any (\rv -> nameBase rv == pn) recVars-      then newName pn-      else return $ mkName pn-  let polyTypes = map VarT polyNames-      polyArgs = map (\j -> mkName $ "arg" ++ show j) [1..fc]-      fieldVars = map PlainTV $ recVars ++ deleteAt i polyNames-      fieldName = mkName ("f_" ++ nameBase fn)-      r = pure recType-      a = pure ft-      x = pure $ foldr fun recType $ replaceAt ft i polyTypes-      y = pure $ foldr fun recType $ replaceAt (ConT ''X) i polyTypes-      mkApplicator c v = pure $ LamE argPats app-        where-          app = foldl' AppE (VarE c) $ map VarE $ replaceAt v i polyArgs-          argPats = replaceAt WildP i $ map VarP polyArgs-      -- \c x -> \a1 -> .. \_ -> .. \an -> c a1 .. x .. an-      applicator = [|\v c -> $(mkApplicator 'c 'v)|]-      extractor = pure $ VarE fn-  fieldType <- ForallT fieldVars [] <$>-    [t|Field $r $a $x $y|]-  fieldBody <--    [|Field $applicator $extractor|]-  return [ SigD fieldName fieldType, ValD (VarP fieldName) (NormalB fieldBody) [] ]--genCon :: [ Name ] -> Type -> Int -> TH.Con -> Q [ Dec ]-genCon recVars recType cc-  = \case-    RecC cName fields -> genCon' cName fields-    NormalC cName [] -> genCon' cName []-    _ -> fail "Unsupported constructor."-  where-    genCon' cName fields = do-      let fieldTypes = [ ft | ( _, _, ft ) <- fields ]-          conName = mkName ("c_" ++ nameBase cName)-          cType = foldr fun recType fieldTypes-          conMatch-            | cc == 1 = [|const True|]-            | otherwise = [|\r -> $(mkConMatch 'r)|]-          mkConMatch r = pure $ CaseE (VarE r)-                         [ Match (RecP cName []) (NormalB (ConE 'True)) []-                         , Match WildP (NormalB (ConE 'False)) []-                         ]-          fc = length fields-      conType <- ForallT (map PlainTV recVars) [] <$> [t|F.Con $(pure recType) $(pure cType)|]-      conBody <- [|F.Con $(pure $ ConE cName) $conMatch|]-      fDecs <- traverse (genField recVars recType fc) $ zip [0..] fields-      return $-        [ SigD conName conType-        , ValD (VarP conName) (NormalB conBody) []-        ] ++ concat fDecs---- | Generate `Field`s for a given data type. Currently only single-constructor records are supported.--- Each record field @a@ will be turned into a `Field` @f_a@, and all constructors will be turned into `Con`s.-genFields :: Name -> Q [ Dec ]-genFields n = reify n >>= \case-  TyConI (DataD [] _ vs cs _) -> do-      recVars <- for vs $ \case-        PlainTV vn -> return vn-        KindedTV vn k | k == starK -> return vn-        _ -> fail "Only simple type variables supported."-      let recType = foldl' (\t v -> AppT t (VarT v)) (ConT n) recVars-          cc = length cs-      concat <$> traverse (genCon recVars recType cc) cs-  _ -> fail "Unsupported record type."
− Data/Codec/Testing.hs
@@ -1,34 +0,0 @@-module Data.Codec.Testing-  ( -- * Testing-    ParseResult(..)-  , roundTrip, roundTripStorable-  )-where--import Data.Aeson.Types (Result(..))-import Data.Codec.Codec-import Foreign--class ParseResult f where-  toEither :: f a -> Either String a--instance ParseResult (Either String) where-  toEither = id--instance ParseResult Maybe where-  toEither = maybe (Left "Nothing") Right--instance ParseResult Result where-  toEither = \case-    Error err -> Left err-    Success x -> Right x---- | Round-trip a value through a `ConcreteCodec` to an `Either String a`.-roundTrip :: ParseResult f => ConcreteCodec c f a -> a -> Either String a-roundTrip cd-  = toEither . parseVal cd . produceVal cd---- | Round-trip a value through its `Storable` instance.-roundTripStorable :: Storable a => a -> IO a-roundTripStorable x-  = with x peek
− Data/Codec/Tuple.hs
@@ -1,32 +0,0 @@-module Data.Codec.Tuple-  ( Field1(..), Field2(..)-  , c_Left, c_Right, f_left, f_right-  ) where--import Data.Either--import Data.Codec.Field (Field(..), Con(..), X)--class Field1 r a x y | r x -> y, r y -> x, r -> a, x y -> r where-  f_1 :: Field r a x y--instance Field1 ( a, b ) a (a -> a2 -> ( a, b )) (X -> a2 -> ( a, b )) where-  f_1 = Field (\x c _ a2 -> c x a2) fst--class Field2 r a x y | r x -> y, r y -> x, r -> a, x y -> r where-  f_2 :: Field r a x y--instance Field2 ( a, b ) b (a1 -> b -> ( a, b )) (a1 -> X -> ( a, b )) where-  f_2 = Field (\x c a1 _ -> c a1 x) snd--c_Left :: Con (Either a b) (a -> Either a b)-c_Left = Con Left (\case { Left _ -> True; _ -> False })--c_Right :: Con (Either a b) (b -> Either a b)-c_Right = Con Right (\case { Right _ -> True; _ -> False })--f_left :: Field (Either a b) a (a -> Either a b) (X -> Either a b)-f_left = Field (\x c _ -> c x) (\(Left l) -> l)--f_right :: Field (Either a b) b (b -> Either a b) (X -> Either a b)-f_right = Field (\x c _ -> c x) (\(Right l) -> l)
− Examples/Foreign.hsc
@@ -1,75 +0,0 @@-module Examples.Foreign where--#include "time.h"--#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)--import Foreign-import Foreign.C-import Data.Codec-import Foreign.Codec--data TM = TM-  { seconds :: Int-  , minutes :: Int-  , hours :: Int-  , monthDay :: Int-  , month :: Int-  , year :: Int-  , weekDay :: Int-  , yearDay :: Int-  , daylightSavingTime :: Bool-  } deriving Show--genFields ''TM---- convenience macro that enforces the correct codec type for a field-#define hsc_numField(s, f) \-  hsc_printf("field (%ld) . codecFor (undefined :: ", offsetof(s, f)); \-  hsc_type(typeof(((s*)0)->f)); \-  hsc_printf(")");--cTimeCodec :: ForeignCodec TM-cTimeCodec =-  TM-    $>> f_seconds            >-< (#numField struct tm, tm_sec)  cast-    >>> f_minutes            >-< (#numField struct tm, tm_min)  cast-    >>> f_hours              >-< (#numField struct tm, tm_hour) cast-    >>> f_monthDay           >-< (#numField struct tm, tm_mday) cast-    >>> f_month              >-< (#numField struct tm, tm_mon)  cast-    >>> f_year               >-< (#numField struct tm, tm_year) cast-    >>> f_weekDay            >-< (#numField struct tm, tm_wday) cast-    >>> f_yearDay            >-< (#numField struct tm, tm_yday) cast-    >>> f_daylightSavingTime >-< (#numField struct tm, tm_yday) cBool--instance Storable TM where-  sizeOf _ = #{size struct tm}-  alignment _ = #{alignment struct tm}-  peek = peekWith cTimeCodec-  poke = pokeWith cTimeCodec--foreign import ccall "time.h strftime" strftime-  :: CString -> CInt -> CString -> Ptr TM -> IO CSize--formatTM :: String -> TM -> IO String-formatTM fmt tm-  = allocaBytes maxSize $ \str -> do-      _ <- withCString fmt $ \cfmt ->-        with tm $ \tmp ->-          strftime str (fromIntegral maxSize) cfmt tmp-      peekCString str-  where-    maxSize = 512--testTime :: TM-testTime = TM-  { seconds = 42-  , minutes = 49-  , hours = 13-  , monthDay = 4-  , month = 4-  , year = 115-  , weekDay = 0-  , yearDay = 0-  , daylightSavingTime = False-  }
− Examples/IP.hs
@@ -1,40 +0,0 @@-module Examples.IP where--import Data.Codec-import Data.Binary.Bits.Codec-import Data.Word--data IPv4 = IPv4-  { version :: Word8-  , ihl :: Word8-  , dscp :: Word8-  , ecn :: Word8-  , totalLength :: Word16-  , identification :: Word16-  , flags :: Word8-  , fragmentOffset :: Word16-  , timeToLive :: Word8-  , protocol :: Word8-  , headerChecksum :: Word16-  , sourceIP :: Word32-  , destIP :: Word32-  }--genFields ''IPv4--ipv4Codec :: BitCodec IPv4-ipv4Codec = -  IPv4-    $>> f_version        >-< word8 4-    >>> f_ihl            >-< word8 4-    >>> f_dscp           >-< word8 6-    >>> f_ecn            >-< word8 2-    >>> f_totalLength    >-< word16be 16-    >>> f_identification >-< word16be 16-    >>> f_flags          >-< word8 3-    >>> f_fragmentOffset >-< word16be 13-    >>> f_timeToLive     >-< word8 8-    >>> f_protocol       >-< word8 8-    >>> f_headerChecksum >-< word16be 16-    >>> f_sourceIP       >-< word32be 32-    >>> f_destIP         >-< word32be 32
− Examples/JSON.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Examples.JSON where--import Data.Aeson-import Data.Aeson.Codec-import Data.Codec-import Data.Text (Text)--data User = User-  { username :: Text-  , userEmail :: Text-  , userLanguages :: [ Text ]-  , userReferrer :: Maybe User-  } deriving Show--genFields ''User--userCodec :: JSONCodec User-userCodec = obj "user object" $-  User-    $>> f_username      >-< "user" -- entry with FromJSON/ToJSON serialization-    >>> f_userEmail     >-< "email"-    >>> f_userLanguages >-< "languages"-    >>> f_userReferrer  >-< opt (entry "referrer" userCodec) -- entry with specific codec--instance FromJSON User where-  parseJSON = parseVal userCodec--instance ToJSON User where-  toJSON = produceVal userCodec
− Examples/Multi.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Examples.Multi where--import Data.Aeson-import Data.Aeson.Codec-import Data.Codec--data Multi a-  = Con1 { foo :: String, bar :: Int }-  | Con2 { baz :: a }-  | Con3-  deriving Show--genFields ''Multi--multiCodec :: (ToJSON a, FromJSON a) => JSONCodec (Multi a)-multiCodec = obj "multi object" $ covered $ (-  cbuild c_Con1-    $   f_foo >-< "foo"-    >>> f_bar >-< "bar"-  ) <-> (-  cbuild c_Con2-    $ f_baz >-< "baz"-  ) <-> (-  cbuild c_Con3 done-  )
− Examples/Tar.hs
@@ -1,78 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--module Examples.Tar where--import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as BC-import Data.Codec-import Data.Word-import Data.Binary.Codec-import Numeric---- stolen from tar-conduit-data Header = Header {-    headerName :: B.ByteString, -- ^ 100 bytes long-    headerMode :: Word64,-    headerOwnerUID :: Word64,-    headerOwnerGID :: Word64,-    headerFileSize :: Integer, -- ^ 12 bytes-    headerModifyTime :: Integer, -- ^ 12 bytes-    headerChecksum :: Word64,-    headerType :: Word8, -- ^ 1 byte-    headerLinkName :: B.ByteString, -- ^ 100 bytes-    headerMagic :: B.ByteString, -- ^ 6 bytes-    headerVersion :: Word16,-    headerOwnerUserName :: B.ByteString, -- ^ 32 bytes-    headerOwnerGroupName :: B.ByteString, -- ^ 32 bytes-    headerDeviceMajorNumber :: Word64,-    headerDeviceMinorNumber :: Word64,-    headerFilenamePrefix :: B.ByteString -- ^ 155 bytes-    }-    deriving (Eq, Show, Read)--genFields ''Header---- easy peasy-headerCodec :: BinaryCodec Header-headerCodec =-  Header-    $>> f_headerName              >-< bytes' 100 -- Codec will de/serialize in this order-    >>> f_headerMode              >-< octal 8-    >>> f_headerOwnerUID          >-< octal 8-    >>> f_headerOwnerGID          >-< octal 8-    >>> f_headerFileSize          >-< octal 12-    >>> f_headerModifyTime        >-< octal 12-    >>> f_headerChecksum          >-< octal 8-    >>> f_headerType              >-< word8-    >>> f_headerLinkName          >-< bytes' 100-    >>> f_headerMagic             >-< bytes' 6-    >>> f_headerVersion           >-< octal 2-    >>> f_headerOwnerUserName     >-< bytes' 32-    >>> f_headerOwnerGroupName    >-< bytes' 32-    >>> f_headerDeviceMajorNumber >-< octal 8-    >>> f_headerDeviceMinorNumber >-< octal 8-    >>> f_headerFilenamePrefix    >-< bytes' 155---- byte field with trailing nulls stripped-bytes' :: Int -> BinaryCodec B.ByteString-bytes' n = mapCodecM trim pad (byteString n)-  where-    trim bs = return (fst $ B.spanEnd (==0) bs)-    pad bs-      | B.length bs <= n = return $ bs `B.append` B.replicate (n - B.length bs) 0-      | otherwise = fail "Serialized ByteString too large for field."---- zero-padded null/space-terminated ASCII octal-octal :: (Show i, Integral i) => Int -> BinaryCodec i-octal n = mapCodecM parseOct makeOct (byteString n)-  where-    parseOct bs-      | B.null trimmed = return 0-      | otherwise = case readOct (BC.unpack trimmed) of-          [ ( x, _ ) ] -> return x-          _ -> fail $ "Could not parse octal value: " ++ show bs-        where trimmed = BC.takeWhile (`notElem` " \NUL") bs-    makeOct x-      | B.length octBS > n - 1 = fail "Octal value too large for field."-      | otherwise = return $ BC.replicate (n - 1 - B.length octBS) '0' `B.append` octBS `B.snoc` 0-        where octBS = BC.pack $ showOct x ""
− Foreign/Codec.hs
@@ -1,57 +0,0 @@-module Foreign.Codec-  ( -- * Foreign codecs-  ForeignContext, ForeignCodec, ForeignCodec'-  , peekWith, pokeWith, storable, field-  , cast, cBool-  , codecFor-  ) where--import Control.Monad.Reader-import Foreign--import Data.Codec.Codec--type ForeignContext a = ReaderT (Ptr a) IO--- | A foreign codec for @a@ given a pointer to @p@.-type ForeignCodec' p a = Codec (ForeignContext p) (ForeignContext p) a--- | A foreign codec for @a@ given a pointer to itself.--- Use `def` from `Default` to get a codec that uses a `Storable` instance,-type ForeignCodec a = ForeignCodec' a a---- | Peek a value using a `ForeignCodec'`.-peekWith :: ForeignCodec' p a -> Ptr p -> IO a-peekWith (Codec r _)-  = runReaderT r---- | Poke a value using a `ForeignCodec'`.-pokeWith :: ForeignCodec' p a -> Ptr p -> a -> IO ()-pokeWith (Codec _ w) ptr x-  = runReaderT (w x) ptr---- | A codec for a field of a foreign structure, given its byte offset and a sub-codec.--- You can get an offset easily using @{#offset struct_type, field}@ with @hsc2hs@.-field :: Int -> ForeignCodec' f a -> ForeignCodec' p a-field off cd = Codec-  { parse = inField $ parse cd-  , produce = inField . produce cd-  } where inField = withReaderT (`plusPtr` off)---- | A `ForeignCodec` for any `Storable` type.-storable :: Storable a => ForeignCodec a-storable = Codec (ReaderT peek) (\x -> ReaderT (`poke`x))--castContext :: ForeignCodec' c a -> ForeignCodec' c' a-castContext = mapCodecF castc castc-  where castc = withReaderT castPtr---- | Store any integral type.-cast :: (Integral c, Storable c, Integral a) => ForeignCodec' c a-cast = mapCodec fromIntegral fromIntegral storable---- | Store a `Bool` in any `Integral` `Ptr`.-cBool :: Integral c => ForeignCodec' c Bool-cBool = castContext storable---- | Restrict the pointer type of a given codec. Utility function for the @numField@ macro.-codecFor :: c -> ForeignCodec' c a -> ForeignCodec' c a-codecFor _ = id
− TestExamples.hs
@@ -1,14 +0,0 @@-module Main where---- No testing yet, just check that the examples all build.--import Examples.Foreign-import Examples.IP-import Examples.JSON-import Examples.Multi-import Examples.Tar--import System.Exit--main :: IO ()-main = exitSuccess
codec.cabal view
@@ -1,83 +1,15 @@ name:                codec-version:             0.1.1+version:             0.2 license:             BSD3 license-file:        LICENSE-synopsis:            First-class record construction and bidirectional serialization-description:-  Tired of writing complementary @parseJSON@\/@toJSON@, @peek@\/@poke@ or-  Binary @get@\/@put@ functions?-  .-  @codec@ provides easy bidirectional serialization of plain Haskell-  records in any Applicative context. All you need to do is provide a-  de\/serializer for every record field in any order you like, and you get-  a de\/serializer for the whole structure. The type system ensures that-  you provide every record exactly once. It also includes a library for-  general record construction in an Applicative context, of which creating-  codecs is just one application.-  .-  JSON!-  .-  > userCodec :: JSONCodec User-  > userCodec = obj "user object" $-  > User-  >   $>> f_username      >-< "user"-  >   >>> f_userEmail     >-< "email"-  >   >>> f_userLanguages >-< "languages"-  >   >>> f_userReferrer  >-< opt "referrer"-  >-  > instance FromJSON User where-  >   parseJSON = parseVal userCodec-  >-  > instance ToJSON User where-  >   toJSON = produceVal userCodec-  .-  Bit fields!-  .-  > ipv4Codec :: BinaryCodec IPv4-  > ipv4Codec = toBytes $-  >   IPv4-  >     $>> f_version         >-< word8 4-  >     >>> f_ihl             >-< word8 4-  >     >>> f_dscp            >-< word8 6-  >     >>> f_ecn             >-< word8 2-  >     >>> f_totalLength     >-< word16be 16-  >     >>> f_identification  >-< word16be 16-  >     >>> f_flags           >-< word8 3-  >     >>> f_fragmentOffset  >-< word16be 13-  >     >>> f_timeToLive      >-< word8 8-  >     >>> f_protocol        >-< word8 8-  >     >>> f_headerChecksum  >-< word16be 16-  >     >>> f_sourceIP        >-< word32be 32-  >     >>> f_destIP          >-< word32be 32-  >-  > instance Binary IPv4 where-  >   get = parse ipv4Codec-  >   put = produce ipv4Codec-  .-  Storable!-  .-  > timeSpecCodec :: ForeignCodec TimeSpec-  > timeSpecCodec =-  >   TimeSpec-  >     $>> f_seconds     >-< field (#offset struct timespec, tv_sec)  cInt-  >     >>> f_nanoseconds >-< field (#offset struct timespec, tv_nsec) cInt-  >-  > instance Storable TimeSpec where-  >   peek = peekWith timeSpecCodec-  >   poke = pokeWith timeSpecCodec-  >   ...-  .-  All of these examples use the same types and logic for constructing-  Codecs, and it\'s very easy to create Codecs for any-  parsing\/serialization library.-  .-  See "Data.Codec" for an introduction.+synopsis:            Simple bidirectional serialization+description:         See README.md author:              Patrick Chilton maintainer:          chpatrick@gmail.com--- copyright:           +-- copyright: category:            Data build-type:          Simple--- extra-source-files:  +-- extra-source-files: cabal-version:       >=1.10 homepage:            https://github.com/chpatrick/codec @@ -86,65 +18,39 @@   location:            https://github.com/chpatrick/codec.git  library-  exposed-modules:     Data.Codec,-                       Data.Codec.Field,-                       Data.Codec.Codec,-                       Data.Codec.TH,-                       Data.Codec.Tuple,-                       Data.Codec.Testing,-+  exposed-modules:     Control.Monad.Codec,                        Data.Aeson.Codec,                        Data.Binary.Codec,-                       Data.Binary.Bits.Codec,-                       Foreign.Codec-  -- other-modules:       -  default-extensions:  TemplateHaskell,-                       MultiParamTypeClasses,-                       FlexibleContexts,-                       FlexibleInstances,-                       LambdaCase,-                       FunctionalDependencies,-                       DeriveFunctor,-                       ScopedTypeVariables-  build-depends:       base >=4.6 && < 4.9,-                       bytestring >=0.10,-                       binary >=0.7,-                       binary-bits >=0.5,-                       template-haskell >=2.8,-                       mtl >= 2.2.1,-                       aeson >= 0.8.0.2,-                       text >= 1.2.0.4,-                       unordered-containers >= 0.2.5.1,-                       data-default-class >= 0.0.1,-                       transformers >= 0.4.2.0-  -- hs-source-dirs:      +                       Data.Binary.Bits.Codec+  build-depends:       base >=4.6 && < 6,+                       bytestring,+                       binary,+                       binary-bits,+                       template-haskell,+                       mtl,+                       aeson >= 1.0.0.0,+                       text,+                       unordered-containers,+                       transformers,+                       profunctors,+                       vector+  hs-source-dirs:      src   default-language:    Haskell2010-  ghc-options:         -Wall -fno-warn-orphans+  ghc-options:         -Wall -test-suite Examples-  default-language:    Haskell2010+test-suite codec-tests   type:                exitcode-stdio-1.0-  main-is:             TestExamples.hs-  other-modules:       Examples.Foreign,-                       Examples.IP,-                       Examples.JSON,-                       Examples.Multi,-                       Examples.Tar-  build-depends:       base >=4.6,-                       bytestring >=0.10,-                       binary >=0.7,-                       binary-bits >=0.5,-                       template-haskell >=2.8,-                       mtl >= 2.2.1,-                       aeson >= 0.8.0.2,-                       text >= 1.2.0.4,-                       unordered-containers >= 0.2.5.1,-                       data-default-class >= 0.0.1,-                       transformers >= 0.4.2.0-  default-extensions:  TemplateHaskell,-                       MultiParamTypeClasses,-                       FlexibleInstances,-                       DeriveFunctor,-                       FunctionalDependencies,-                       LambdaCase,-                       ScopedTypeVariables+  hs-source-dirs:      test+  main-is:             test.hs+  other-modules:       Data.Aeson.Codec.Test+                       Data.Binary.Codec.Test+  build-depends:       base,+                       bytestring,+                       aeson,+                       codec,+                       tasty,+                       tasty-quickcheck,+                       generic-arbitrary,+                       binary+  ghc-options:         -Wall+  default-language:    Haskell2010
+ src/Control/Monad/Codec.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Control.Monad.Codec+  ( CodecFor(..)+  , Codec+  , (=.)+  , fmapArg+  ) where++import           Data.Profunctor++-- | A serializer/deserializer pair reading @a@ in context @r@ and writing @c@ in context @w@.+data CodecFor r w c a = Codec+  { codecIn :: r a+  , codecOut :: c -> w a+  } deriving (Functor)++type Codec r w a = CodecFor r w a a++instance (Applicative r, Applicative w) => Applicative (CodecFor r w c) where+  pure x = Codec+    { codecIn = pure x+    , codecOut = \_ -> pure x+    }+  f <*> x = Codec+    { codecIn = codecIn f <*> codecIn x+    , codecOut = \c -> codecOut f c <*> codecOut x c+    }++instance (Monad r, Monad w) => Monad (CodecFor r w c) where+  return = pure+  m >>= f = Codec+    { codecIn = codecIn m >>= \x -> codecIn (f x)+    , codecOut = \c -> codecOut m c >>= \x -> codecOut (f x) c+    }++instance (Functor r, Functor w) => Profunctor (CodecFor r w) where+  dimap fIn fOut Codec {..} = Codec+    { codecIn = fmap fOut codecIn+    , codecOut = fmap fOut . codecOut . fIn+    }++-- | Compose a function into the serializer of a `Codec`.+-- Useful to modify a `Codec` so that it writes a particular record field.+(=.) :: (c' -> c) -> CodecFor r w c a -> CodecFor r w c' a+fIn =. codec = codec { codecOut = codecOut codec . fIn }++-- | Modify a serializer function so that it also returns the serialized value,+-- Useful for implementing codecs.+fmapArg :: Functor f => (a -> f ()) -> a -> f a+fmapArg f x = x <$ f x
+ src/Data/Aeson/Codec.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE FlexibleContexts #-}++module Data.Aeson.Codec+  ( JSONCodec(..)+  , defJSON++  -- * JSON object codecs+  , ObjectParser, ObjectBuilder, ObjectCodec+  , field, field'+  , asObject++  -- * JSON array codecs+  , ArrayParser, ArrayBuilder, ArrayCodec+  , element, element'+  , asArray+  , arrayOf, arrayOf'+  ) where++import           Control.Monad.Codec+import           Data.Aeson+import           Data.Aeson.Encoding+import qualified Data.Aeson.Encoding.Internal as AEI+import           Data.Aeson.Types (Parser, Pair)+import           Control.Monad.Reader+import           Control.Monad.State+import           Control.Monad.Writer.Strict+import qualified Data.Text as T+import qualified Data.Vector as V++-- | Describes the de/serialization of a type @a@. Equivalent to a `ToJSON` and a `FromJSON` instance.+data JSONCodec a = JSONCodec+  { parseJSONCodec :: Value -> Parser a+  , toJSONCodec :: a -> Value+  , toEncodingCodec :: a -> Encoding+  }++-- | Encode/decode a value with its `ToJSON` and `FromJSON` instances.+defJSON :: (FromJSON a, ToJSON a) => JSONCodec a+defJSON = JSONCodec+  { parseJSONCodec = parseJSON+  , toJSONCodec = toJSON+  , toEncodingCodec = toEncoding+  }++type ObjectParser = ReaderT Object Parser+type ObjectBuilder = Writer ( Series, Endo [ Pair ] )++-- | A codec that parses values out of a given `Object`, and produces+-- key-value pairs into a new one.+type ObjectCodec a = Codec ObjectParser ObjectBuilder a++-- | Store/retrieve a value in a given JSON field, with a given JSONCodec.+field' :: T.Text -> JSONCodec a -> ObjectCodec a+field' key valCodec = Codec+  { codecIn = ReaderT $ \obj -> (obj .: key) >>= parseJSONCodec valCodec+  , codecOut = \val ->+    writer+      ( val+      , ( pair key (toEncodingCodec valCodec val)+        , Endo ((key .= toJSONCodec valCodec val) :)+        )+      )+  }++-- | Store/retrieve a value in a given JSON field, with the default JSON serialization.+field :: (FromJSON a, ToJSON a) => T.Text -> ObjectCodec a+field key = field' key defJSON++-- | Turn an `ObjectCodec` into a `JSONCodec` with an expected name (see `withObject`).+asObject :: String -> ObjectCodec a -> JSONCodec a+asObject err objCodec = JSONCodec+  { parseJSONCodec = withObject err (runReaderT (codecIn objCodec))+  , toJSONCodec = object . (`appEndo` []) . snd . execOut+  , toEncodingCodec = pairs . fst . execOut+  } where execOut = execWriter . codecOut objCodec++type ArrayParser = StateT [ Value ] Parser+type ArrayBuilder = Writer ( Series, [ Value ] )++-- | A codec that serializes data to a sequence of JSON array elements.+type ArrayCodec a = Codec ArrayParser ArrayBuilder a++-- | Expect/append an array element, using a given `JSONCodec`.+element' :: JSONCodec a -> ArrayCodec a+element' valCodec = Codec+  { codecIn = StateT $ \case+    [] -> fail "Expected an element, got an empty list."+    x : xs -> do+      val <- parseJSONCodec valCodec x+      return ( val, xs )++  , codecOut = \val -> writer ( val, ( AEI.Value $ AEI.retagEncoding $ toEncodingCodec valCodec val, [ toJSONCodec valCodec val ] ) )+  }++-- | Expect/append an array element, using the default serialization.+element :: (FromJSON a, ToJSON a) => ArrayCodec a+element = element' defJSON++-- | A codec that parses values out of a given `Array`, and produces+-- key-value pairs into a new one.+asArray :: String -> ArrayCodec a -> JSONCodec a+asArray err arrCodec = JSONCodec+  { parseJSONCodec = withArray err $ \arr -> do+      ( val, leftover ) <- runStateT (codecIn arrCodec) (V.toList arr)+      unless (null leftover) $ fail "Elements left over in parsed array."+      return val+  , toJSONCodec = Array . V.fromList . snd . execOut+  , toEncodingCodec = \val -> case fst (execOut val) of+      AEI.Empty -> emptyArray_+      AEI.Value enc -> AEI.wrapArray enc+  } where execOut = execWriter . codecOut arrCodec++-- | Given a `JSONCodec` for @b@ and a way to turn @a@ into @[ b ]@ and back,+-- create a `JSONCodec` for @a@.+arrayOf' :: (a -> [ b ]) -> ([ b ] -> a) -> JSONCodec b -> JSONCodec a+arrayOf' aToList listToA elemCodec = JSONCodec+  { parseJSONCodec = \arr -> do+      vals <- parseJSON arr+      parsedVals <- traverse (parseJSONCodec elemCodec) (vals :: [ Value ])+      return (listToA parsedVals)+  , toJSONCodec = Array . V.fromList . map (toJSONCodec elemCodec) . aToList+  , toEncodingCodec = AEI.list (toEncodingCodec elemCodec) . aToList+  }++-- | Given a a way to turn @a@ into @[ b ]@ and back, create a `JSONCodec` for @a@.+arrayOf :: (FromJSON b, ToJSON b) => (a -> [ b ]) -> ([ b ] -> a) -> JSONCodec a+arrayOf aToList listToA = arrayOf' aToList listToA defJSON
+ src/Data/Binary/Bits/Codec.hs view
@@ -0,0 +1,42 @@+module Data.Binary.Bits.Codec+  ( BitCodec+  , bool+  , word8, word16be, word32be, word64be+  , toBytes+  )+where++import           Control.Monad+import           Control.Monad.Codec+import qualified Data.Binary.Bits.Get as G+import           Data.Binary.Bits.Put+import qualified Data.Binary.Codec as B++import           Data.Word++type BitCodec a = Codec G.Block BitPut a++bool :: BitCodec Bool+bool = Codec G.bool (fmapArg putBool)++bitCodec :: (Int -> G.Block a) -> (Int -> a -> BitPut ()) -> Int -> BitCodec a+bitCodec r w n = Codec (r n) (fmapArg (w n))++word8 :: Int -> BitCodec Word8+word8 = bitCodec G.word8 putWord8++word16be :: Int -> BitCodec Word16+word16be = bitCodec G.word16be putWord16be++word32be :: Int -> BitCodec Word32+word32be = bitCodec G.word32be putWord32be++word64be :: Int -> BitCodec Word64+word64be = bitCodec G.word64be putWord64be++-- | Convert a `BitCodec` into a `B.BinaryCodec`.+toBytes :: BitCodec a -> B.BinaryCodec a+toBytes c = Codec+  { codecIn = G.runBitGet $ G.block $ codecIn c+  , codecOut = fmapArg (runBitPut . void . codecOut c)+  }
+ src/Data/Binary/Codec.hs view
@@ -0,0 +1,101 @@+module Data.Binary.Codec+  (+  -- * Binary codecs+    BinaryCodec+  , byteString+  , word8+  , word16be, word16le, word16host+  , word32be, word32le, word32host+  , word64be, word64le, word64host+  , wordhost+  , int8+  , int16be, int16le, int16host+  , int32be, int32le, int32host+  , int64be, int64le, int64host+  , inthost+  )+ where++import           Control.Monad.Codec+import qualified Data.ByteString as BS+import           Data.Binary.Get+import           Data.Binary.Put+import           Data.Int+import           Data.Word++type BinaryCodec a = Codec Get PutM a++-- | Get/put an n-byte field.+byteString :: Int -> BinaryCodec BS.ByteString+byteString n = Codec+  { codecIn = getByteString n+  , codecOut = \bs -> if BS.length bs == n+      then bs <$ putByteString bs+      else fail $ "Expected a ByteString of size " ++ show n+  }++word8 :: BinaryCodec Word8+word8 = Codec getWord8 (fmapArg putWord8)++word16be :: BinaryCodec Word16+word16be = Codec getWord16be (fmapArg putWord16be)++word16le :: BinaryCodec Word16+word16le = Codec getWord16le (fmapArg putWord16le)++word16host :: BinaryCodec Word16+word16host = Codec getWord16host (fmapArg putWord16host)++word32be :: BinaryCodec Word32+word32be = Codec getWord32be (fmapArg putWord32be)++word32le :: BinaryCodec Word32+word32le = Codec getWord32le (fmapArg putWord32le)++word32host :: BinaryCodec Word32+word32host = Codec getWord32host (fmapArg putWord32host)++word64be :: BinaryCodec Word64+word64be = Codec getWord64be (fmapArg putWord64be)++word64le :: BinaryCodec Word64+word64le = Codec getWord64le (fmapArg putWord64le)++word64host :: BinaryCodec Word64+word64host = Codec getWord64host (fmapArg putWord64host)++wordhost :: BinaryCodec Word+wordhost = Codec getWordhost (fmapArg putWordhost)++int8 :: BinaryCodec Int8+int8 = Codec getInt8 (fmapArg putInt8)++int16be :: BinaryCodec Int16+int16be = Codec getInt16be (fmapArg putInt16be)++int16le :: BinaryCodec Int16+int16le = Codec getInt16le (fmapArg putInt16le)++int16host :: BinaryCodec Int16+int16host = Codec getInt16host (fmapArg putInt16host)++int32be :: BinaryCodec Int32+int32be = Codec getInt32be (fmapArg putInt32be)++int32le :: BinaryCodec Int32+int32le = Codec getInt32le (fmapArg putInt32le)++int32host :: BinaryCodec Int32+int32host = Codec getInt32host (fmapArg putInt32host)++int64be :: BinaryCodec Int64+int64be = Codec getInt64be (fmapArg putInt64be)++int64le :: BinaryCodec Int64+int64le = Codec getInt64le (fmapArg putInt64le)++int64host :: BinaryCodec Int64+int64host = Codec getInt64host (fmapArg putInt64host)++inthost :: BinaryCodec Int+inthost = Codec getInthost (fmapArg putInthost)
+ test/Data/Aeson/Codec/Test.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}++module Data.Aeson.Codec.Test+  ( aesonTests+  ) where++import           Data.Aeson+import           Data.Aeson.Encoding+import           Data.Aeson.Types+import           GHC.Generics (Generic)+import           Test.Tasty+import           Test.Tasty.QuickCheck+import           Test.QuickCheck.Arbitrary.Generic++import           Control.Monad.Codec+import           Data.Aeson.Codec++data RecordA = RecordA+  { recordAInt :: Int+  , recordANestedObj :: RecordB+  , recordANestedArr :: RecordB+  , recordANestedObjs :: [ RecordB ]+  } deriving (Eq, Ord, Show, Generic)++instance Arbitrary RecordA where+  arbitrary = genericArbitrary+  shrink = genericShrink++data RecordB = RecordB+  { recordBString :: String+  , recordBDouble :: Double+  } deriving (Eq, Ord, Show, Generic)++instance Arbitrary RecordB where+  arbitrary = genericArbitrary+  shrink = genericShrink++recordACodec :: JSONCodec RecordA+recordACodec = asObject "RecordA" $+  RecordA+    <$> recordAInt =. field "int"+    <*> recordANestedObj =. field' "nestedObj" recordBObjCodec+    <*> recordANestedArr =. field' "nestedArr" recordBArrCodec+    <*> recordANestedObjs =. field' "nestedObjs" (arrayOf' id id recordBObjCodec)++recordBObjCodec :: JSONCodec RecordB+recordBObjCodec = asObject "RecordB" $+  RecordB+    <$> recordBString =. field "string"+    <*> recordBDouble =. field "double"++recordBArrCodec :: JSONCodec RecordB+recordBArrCodec = asArray "RecordB" $+  RecordB+    <$> recordBString =. element+    <*> recordBDouble =. element++jsonRoundTrip :: (Eq a, Show a) => JSONCodec a -> a -> Property+jsonRoundTrip codec x = Right x === roundTripValue .&&. Right x === roundTripEncoding+  where+    roundTripValue = parseEither (parseJSONCodec codec) (toJSONCodec codec x)+    roundTripEncoding = do+      let bs = encodingToLazyByteString (toEncodingCodec codec x)+      val <- eitherDecode bs+      parseEither (parseJSONCodec codec) val++aesonTests :: TestTree+aesonTests = testGroup "Data.Aeson.Codec"+  [ testProperty "Complex" $ jsonRoundTrip recordACodec+  , testProperty "Object codec" $ jsonRoundTrip recordBObjCodec+  , testProperty "Array codec" $ jsonRoundTrip recordBArrCodec+  ]
+ test/Data/Binary/Codec/Test.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}++module Data.Binary.Codec.Test+  ( binaryTests+  ) where++import           Control.Monad+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import           Data.Binary.Get (runGetOrFail)+import           Data.Binary.Put (runPutM)+import           Data.Int+import           Data.Word+import           GHC.Generics (Generic)+import           Test.Tasty+import           Test.Tasty.QuickCheck+import           Test.QuickCheck.Arbitrary.Generic++import           Control.Monad.Codec+import           Data.Binary.Codec++data RecordA = RecordA+  { recordAInt64 :: Int64+  , recordAWord8 :: Word8+  , recordANestedB :: RecordB+  } deriving (Eq, Ord, Show, Generic)++instance Arbitrary RecordA where+  arbitrary = genericArbitrary+  shrink = genericShrink++data RecordB = RecordB+  { recordBWord16 :: Word16+  , recordBByteString64 :: BS.ByteString+  } deriving (Eq, Ord, Show, Generic)++instance Arbitrary RecordB where+  arbitrary =+    RecordB+      <$> arbitrary+      <*> (BS.pack <$> (replicateM 64 arbitrary))+  shrink (RecordB word bs) =+    RecordB <$> shrink word <*> pure bs++recordACodec :: BinaryCodec RecordA+recordACodec =+  RecordA+    <$> recordAInt64 =. int64le+    <*> recordAWord8 =. word8+    <*> recordANestedB =. recordBCodec++recordBCodec :: BinaryCodec RecordB+recordBCodec =+  RecordB+    <$> recordBWord16 =. word16host+    <*> recordBByteString64 =. byteString 64++binaryRoundTrip :: (Eq a, Show a) => BinaryCodec a -> a -> Property+binaryRoundTrip codec x = Right x === roundTripValue+  where+    roundTripValue = do+      let ( _, encoded ) = runPutM (codecOut codec x)+      ( leftover, _, val ) <- runGetOrFail (codecIn codec) encoded+      unless (LBS.null leftover) $ fail "Codec produced leftover bytes."+      return val++binaryTests :: TestTree+binaryTests = testGroup "Data.Aeson.Codec"+  [ testProperty "Simple" $ binaryRoundTrip recordBCodec+  , testProperty "Nested" $ binaryRoundTrip recordACodec+  ]
+ test/test.hs view
@@ -0,0 +1,15 @@+import           Test.Tasty+import           Test.Tasty.Ingredients.Basic (consoleTestReporter)++import           Data.Aeson.Codec.Test+import           Data.Binary.Codec.Test++main :: IO ()+main = defaultMainWithIngredients [ consoleTestReporter ] allTests++allTests :: TestTree+allTests =+  testGroup "Codec"+    [ aesonTests+    , binaryTests+    ]