packages feed

serokell-util (empty) → 0.1.1

raw patch · 41 files changed

+2248/−0 lines, 41 filesdep +QuickCheckdep +acid-statedep +aesonsetup-changed

Dependencies added: QuickCheck, acid-state, aeson, aeson-extra, base, base16-bytestring, base64-bytestring, binary, binary-orphans, bytestring, cereal, cereal-vector, clock, containers, data-msgpack, deepseq, directory, either, exceptions, extra, filepath, formatting, hashable, hspec, lens, mtl, optparse-applicative, parsec, quickcheck-instances, safecopy, scientific, semigroups, serokell-util, template-haskell, text, text-format, time-units, transformers, unordered-containers, vector, yaml

Files

+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2016 Serokell++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ serokell-util.cabal view
@@ -0,0 +1,131 @@+name:                serokell-util+version:             0.1.1+synopsis:            General-purpose functions by Serokell+homepage:            http://gitlab.serokell.io/serokell-team/serokell-util+license:             MIT+license-file:        LICENSE+category:            Utils+author:              Serokell+maintainer:          Serokell <hi@serokell.io>+copyright:           2016 Serokell+build-type:          Simple+description:         Serokell-util is a library consisting of functions, which+                     are not included in standard libraries, but are useful for+                     multiple projects. This library was created when it was+                     found that in new projects we need to use some utility+                     functions from existing projects and don't want to+                     copy-paste them.+-- extra-source-files:+cabal-version:       >=1.10++library+  exposed-modules:     Serokell.AcidState+                       Serokell.AcidState.ExtendedState+                       Serokell.AcidState.Instances+                       Serokell.AcidState.Statistics+                       Serokell.AcidState.Util+                       Serokell.Arbitrary+                       Serokell.Aeson.Options+                       Serokell.Data.Memory.Units+                       Serokell.Data.Variant+                       Serokell.Util+                       Serokell.Util.Base16+                       Serokell.Util.Base64+                       Serokell.Util.Bench+                       Serokell.Util.Binary+                       Serokell.Util.Common+                       Serokell.Util.Concurrent+                       Serokell.Util.I18N+                       Serokell.Util.Parse+                       Serokell.Util.OptParse+                       Serokell.Util.Exceptions+                       Serokell.Util.Lens+                       Serokell.Util.StaticAssert+                       Serokell.Util.Text+                       Serokell.Util.Verify+  other-modules:       Serokell.Data.Variant.Class+                       Serokell.Data.Variant.Helpers+                       Serokell.Data.Variant.Serialization+                       Serokell.Data.Variant.Variant+                       Serokell.Util.Parse.Base64+                       Serokell.Util.Parse.Common+                       Serokell.Util.Parse.Network+  build-depends:       QuickCheck >= 2.8.1+                     , acid-state+                     , aeson+                     , aeson-extra+                     , base >= 4.8 && < 5+                     , base16-bytestring+                     , base64-bytestring+                     , binary+                     , binary-orphans+                     , bytestring+                     , cereal+                     , cereal-vector+                     , containers+                     , clock+                     , data-msgpack >= 0.0.8+                     , deepseq+                     , directory+                     , either+                     , exceptions+                     , extra+                     , filepath+                     , formatting+                     , hashable >= 1.2.4.0+                     , lens+                     , mtl+                     , optparse-applicative+                     , parsec+                     , quickcheck-instances+                     , safecopy >= 0.9.0.1+                     , scientific+                     , semigroups+                     , template-haskell+                     , text+                     , text-format+                     , time-units+                     , transformers+                     , unordered-containers >= 0.2.7.0+                     , vector+                     , yaml+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options:         -Wall -fno-warn-orphans+  default-extensions:  OverloadedStrings+                     , RecordWildCards+                     , DeriveDataTypeable+                     , GeneralizedNewtypeDeriving++test-suite serokell-test+  main-is:             Test.hs+  other-modules:       Test.Serokell.Data.Memory.UnitsSpec+                       Test.Serokell.Data.Variant.VariantSpec+                       Test.Serokell.Util.CommonSpec+                       Test.Serokell.Util.ByteStringSpec+                       Test.Serokell.Util.TextSpec+                       Spec+  type:                exitcode-stdio-1.0+  build-depends:       aeson+                     , base >=4.8+                     , binary+                     , bytestring+                     , cereal+                     , hspec >= 2.1.10+                     , data-msgpack >= 0.0.8+                     , QuickCheck >= 2.8.1+                     , quickcheck-instances+                     , safecopy >= 0.9.0.1+                     , scientific+                     , serokell-util+                     , text+                     , text-format+                     , unordered-containers >= 0.2.7.0+                     , vector+  hs-source-dirs:      test+  default-language:    Haskell2010+  ghc-options:         -threaded -Wall -fno-warn-orphans+  default-extensions:  OverloadedStrings+                     , RecordWildCards+                     , DeriveDataTypeable+                     , GeneralizedNewtypeDeriving
+ src/Serokell/AcidState.hs view
@@ -0,0 +1,10 @@+-- | Re-export Serokell.AcidState.* modules.++module Serokell.AcidState+       (+         module Exports+       ) where++import           Serokell.AcidState.ExtendedState as Exports+import           Serokell.AcidState.Instances     ()+import           Serokell.AcidState.Util          as Exports
+ src/Serokell/AcidState/ExtendedState.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE TypeFamilies #-}++-- | The idea of ExtendedState is to store information about location+-- of the state (either FilePath or memory).++module Serokell.AcidState.ExtendedState+       ( ExtendedState (..)+       , closeExtendedState+       , extendedStateToAcid+       , openLocalExtendedState+       , openMemoryExtendedState+       , queryExtended+       , tidyExtendedState+       , updateExtended+       ) where++import           Control.Monad.Extra     (whenM)+import           Control.Monad.Trans     (MonadIO (liftIO))+import           Data.Acid               (AcidState, EventResult, EventState, IsAcidic,+                                          QueryEvent, UpdateEvent, closeAcidState,+                                          openLocalStateFrom)+import           Data.Acid.Advanced      (query', update')+import           Data.Acid.Memory        (openMemoryState)+import           Data.Typeable           (Typeable)++import           System.Directory        (doesDirectoryExist, removeDirectoryRecursive)++import           Serokell.AcidState.Util (tidyLocalState)++-- | ExtendedState is like usual AcidState, but also stores+-- information about FilePath (unless it's in memory).+data ExtendedState st+    = ESLocal (AcidState st)+              FilePath+    | ESMemory (AcidState st)++-- | Convert ExtendedState to AcidState.+extendedStateToAcid :: ExtendedState st -> AcidState st+extendedStateToAcid (ESLocal s _) = s+extendedStateToAcid (ESMemory s)  = s++-- | Like query', but works on ExtendedState.+queryExtended+    :: (EventState event ~ st, QueryEvent event, MonadIO m)+    => ExtendedState st -> event -> m (EventResult event)+queryExtended st = query' (extendedStateToAcid st)++-- | Like update', but works on ExtendedState.+updateExtended+    :: (EventState event ~ st, UpdateEvent event, MonadIO m)+    => ExtendedState st -> event -> m (EventResult event)+updateExtended st = update' (extendedStateToAcid st)++-- | Like openLocalStateFrom, but returns ExtendedState and operates+-- in MonadIO.+openLocalExtendedState+    :: (IsAcidic st, Typeable st, MonadIO m)+    => Bool -> FilePath -> st -> m (ExtendedState st)+openLocalExtendedState deleteIfExists fp st = do+    whenM ((deleteIfExists &&) <$> liftIO (doesDirectoryExist fp)) $+        liftIO $ removeDirectoryRecursive fp+    liftIO $ flip ESLocal fp <$> openLocalStateFrom fp st++-- | Like openMemoryState, but returns ExtendedState and operates in+-- MonadIO.+openMemoryExtendedState+    :: (IsAcidic st, Typeable st, MonadIO m)+    => st -> m (ExtendedState st)+openMemoryExtendedState st = liftIO $ ESMemory <$> openMemoryState st++-- | Like closeAcidState, but operates on ExtendedState and in+-- MonadIO.+closeExtendedState :: MonadIO m => ExtendedState st -> m ()+closeExtendedState = liftIO . closeAcidState . extendedStateToAcid++-- | Like tidyLocalState, but operates on ExtendedState.+tidyExtendedState :: MonadIO m => ExtendedState st -> m ()+tidyExtendedState (ESLocal st fp) = tidyLocalState st fp+tidyExtendedState (ESMemory _)    = return ()
+ src/Serokell/AcidState/Instances.hs view
@@ -0,0 +1,31 @@+-- | Helper instances for Acid State to remove boilerplate and introduce+-- redundant instances for other data types.++module Serokell.AcidState.Instances where++import           Control.Exception   (throw)+import           Control.Monad.Catch (MonadThrow (throwM))++import           Data.Acid           (Query, Update)+import           Data.Hashable       (Hashable)+import           Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HM hiding (HashMap)+import           Data.HashSet        (HashSet)+import qualified Data.HashSet        as HS hiding (HashSet)+import           Data.SafeCopy       (SafeCopy (getCopy, putCopy), contain,+                                      safeGet, safePut)++-- | Usually Queries shouldn't throw anything. This is a dirty hack.+instance MonadThrow (Query s) where+    throwM = throw++instance MonadThrow (Update s) where+    throwM = throw++instance (Eq a, Hashable a, SafeCopy a) => SafeCopy (HashSet a) where+    putCopy = contain . safePut . HS.toList+    getCopy = contain $ HS.fromList <$> safeGet++instance (Eq a, Hashable a, SafeCopy a, SafeCopy b) => SafeCopy (HashMap a b) where+    putCopy = contain . safePut . HM.toList+    getCopy = contain $ HM.fromList <$> safeGet
+ src/Serokell/AcidState/Statistics.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE Rank2Types                #-}++-- | Collect statistics about acid-state database.++module Serokell.AcidState.Statistics+       ( StoragePart (..)++       , estimateMemoryUsage+       ) where++import           Control.Lens               (Getter, (^.))+import qualified Data.ByteString            as BS (length)+import           Data.SafeCopy              (SafeCopy, safePut)+import           Data.Serialize.Put         (runPut)+import           Data.Text                  (Text)++import           Serokell.Data.Memory.Units (MemoryUnit, fromBytes)++type PartName = Text++data StoragePart storage = forall part. SafeCopy part =>+                                        StoragePart+    { spName   :: PartName+    , spGetter :: Getter storage part+    }++estimateMemoryUsage+    :: MemoryUnit unit+    => [StoragePart s] -> s -> [(PartName, unit)]+estimateMemoryUsage parts storage = map processPart parts+  where+    processPart StoragePart{..} =+        ( spName+        , fromBytes . toInteger . BS.length . runPut . safePut . (storage ^.) $+          spGetter)
+ src/Serokell/AcidState/Util.hs view
@@ -0,0 +1,56 @@+-- | Some useful functions to work with Data.Acid.++module Serokell.AcidState.Util+       (+         -- | Simple helpers+         exceptStateToUpdate+       , exceptStateToUpdateGeneric+       , readerToQuery+       , stateToUpdate++         -- | Utilities+       , createAndDiscardArchive+       , tidyLocalState+       ) where++import           Control.Exception    (Exception, throw)+import           Control.Monad.Except (ExceptT, runExceptT)+import           Control.Monad.Reader (Reader, asks, runReader)+import           Control.Monad.State  (State, runState, state)+import           Control.Monad.Trans  (MonadIO (liftIO))+import           Data.Acid            (AcidState, Query, Update, createArchive,+                                       createCheckpoint)+import           System.Directory     (removeDirectoryRecursive)+import           System.FilePath      ((</>))++readerToQuery :: Reader s a -> Query s a+readerToQuery = asks . runReader++stateToUpdate :: State s a -> Update s a+stateToUpdate = state . runState++exceptStateToUpdate+    :: (Exception e)+    => ExceptT e (State s) a -> Update s a+exceptStateToUpdate = exceptStateToUpdateGeneric id++exceptStateToUpdateGeneric+  :: (Exception exc)+  => (e -> exc) -> ExceptT e (State s) a -> Update s a+exceptStateToUpdateGeneric toException u =+    state $+    runState $+    do res <- runExceptT u+       either (throw . toException) return res++-- | Archive unnecessary data (see createArchive docs for details) and+-- discard it. Works for local state.+createAndDiscardArchive :: MonadIO m => AcidState st -> FilePath -> m ()+createAndDiscardArchive st path =+    liftIO $ createArchive st >> removeDirectoryRecursive (path </> "Archive")++-- | Apply all updates and remove all data from local state which is+-- unnecessary for state restoration.+tidyLocalState :: MonadIO m => AcidState st -> FilePath -> m ()+tidyLocalState st path =+    liftIO (createCheckpoint st) >> createAndDiscardArchive st path
+ src/Serokell/Aeson/Options.hs view
@@ -0,0 +1,59 @@+-- | Options used to derive FromJSON/ToJSON instance. These options+-- generally comply to our style regarding names. Of course sometimes+-- they don't fit one's needs, so treat them as just sensible+-- defaults.++module Serokell.Aeson.Options+       ( defaultOptions+       , leaveTagOptions+       , defaultOptionsPS+       ) where++import qualified Data.Aeson.TH as A+import           Data.Char     (isLower, isPunctuation, isUpper, toLower)+import           Data.List     (findIndex)++headToLower :: String -> String+headToLower []     = undefined+headToLower (x:xs) = toLower x : xs++stripFieldPrefix :: String -> String+stripFieldPrefix = dropWhile (not . isUpper)++dropPunctuation :: String -> String+dropPunctuation = filter (not . isPunctuation)++stripConstructorPrefix :: String -> String+stripConstructorPrefix t =+    maybe t (flip drop t . decrementSafe) $ findIndex isLower t+  where+    decrementSafe 0 = 0+    decrementSafe i = i - 1++-- | These options do the following transformations:+-- 1. Names of field+-- records are assumed to be camelCased, `camel` part is removed,+-- `Cased` part is converted to `cased`. So `camelCased` becomes+-- `cased`. Also all punctuation symbols are dropped before doing it.+-- 2. Constructors are assumed to start with some capitalized prefix+-- (which finished right before the last capital letter). This prefix+-- is dropped and then the first letter is lowercased.+defaultOptions :: A.Options+defaultOptions =+    A.defaultOptions+    { A.fieldLabelModifier = headToLower . stripFieldPrefix . dropPunctuation+    , A.constructorTagModifier = headToLower . stripConstructorPrefix+    , A.sumEncoding = A.ObjectWithSingleField+    }++-- | These options are the same as `defaultOptions`, but they don't+-- modify constructor tags.+leaveTagOptions :: A.Options+leaveTagOptions = defaultOptions { A.constructorTagModifier = id }++-- | Options used for communication with PureScript by default.+defaultOptionsPS :: A.Options+defaultOptionsPS =+    A.defaultOptions+    { A.constructorTagModifier = headToLower . stripConstructorPrefix+    }
+ src/Serokell/Arbitrary.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell    #-}++-- | Arbitrary instances for Serokell datatypes+module Serokell.Arbitrary+       ( VariantNoBytes (..)+       , VariantOnlyBytes (..)+       ) where++import           Data.ByteString               as BS hiding (zip)+import qualified Data.HashMap.Lazy             as H (fromList)+import           Data.Vector                   (fromList)+import           GHC.Generics                  (Generic)+import           Test.QuickCheck               (Arbitrary (..), Gen, choose,+                                                genericShrink, frequency, oneof,+                                                sized)+import           Test.QuickCheck.Instances     ()++import           Serokell.Data.Variant.Variant (Variant (..))+import qualified Serokell.Util.Base64          as S++instance Arbitrary S.JsonByteString where+    arbitrary = S.JsonByteString <$> (arbitrary :: Gen BS.ByteString)++newtype VariantNoBytes = NoBytes+    { getVariant :: Variant+    } deriving (Show, Eq, Generic)++newtype VariantOnlyBytes = OnlyBytes+    { getVarBytes :: Variant+    } deriving (Show, Eq, Generic)++instance Arbitrary VariantOnlyBytes where+    arbitrary = OnlyBytes . VarBytes <$> arbitrary+    shrink = genericShrink++instance Arbitrary VariantNoBytes where+    arbitrary = NoBytes <$> (sized $ \n -> genVariant (n*50 + 1))+    shrink = genericShrink++instance Arbitrary Variant where+    arbitrary = sized $ \n -> genVariant (n*50 + 1)+    shrink = genericShrink++-- | Generate something with at most N constructors. Say how many constructors+-- actually are in the generated 'Variant'. Because encoding 'VarBytes' into+-- JSON and then decoding it presents a problem - it becomes a 'VarString',+-- and it is difficult to tell whether it was decoded from a 'VarBytes' or+-- another 'VarString' - this generating function won't generate this+-- constructor unless given a true boolean flag.+genVariant :: Int -> Gen Variant+genVariant 1 = genFlatVariant+genVariant n = do+    frequency+        -- No reason for “3”, it just works well.+        [ (3, genFlatVariant)+        -- The more constructors we have to generate, the more likely we are+        -- to choose something nested instead of something flat.+        , (truncate (logBase 2 (fromIntegral n) :: Double),+             oneof [genListVariant n, genMapVariant n])+        ]++genFlatVariant :: Gen Variant+genFlatVariant = oneof $+    [ pure VarNone+    , VarBool <$> arbitrary+    , VarInt <$> arbitrary+    , VarUInt <$> arbitrary+    , VarFloat <$> arbitrary+    , VarString <$> arbitrary+    ]++-- | Generate a list of variants, using at most N constructors in total.+genBoundedVariants :: Int -> Gen [Variant]+genBoundedVariants 0 = return []+genBoundedVariants n = do+    v_cons <- choose (1, n)+    (:) <$> genVariant v_cons+        <*> genBoundedVariants (n-v_cons)++genListVariant :: Int -> Gen Variant+genListVariant n = VarList . fromList <$> genBoundedVariants (n-1)++genMapVariant :: Int -> Gen Variant+genMapVariant n = do+    keys <- genBoundedVariants ((n-1) `div` 2)+    vals <- genBoundedVariants ((n-1) `div` 2)+    -- Lengths of keys and vals may not match and so we would get less+    -- constructors due to truncation, but we don't care.+    return $ VarMap $ H.fromList $ zip keys vals
+ src/Serokell/Data/Memory/Units.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE DeriveGeneric       #-}+{-# LANGUAGE FlexibleInstances   #-}+{-# LANGUAGE Rank2Types          #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns        #-}++-- | Memory units.++module Serokell.Data.Memory.Units+       (+         -- | Type class+         MemoryUnit (..)++         -- | Concrete types+       , Byte+       , Kilobyte+       , Megabyte+       , Gigabyte+       , Terabyte++         -- | Pretty printing+       , unitBuilder+       , memory++         -- | Helpers+       , toBytes+       , fromBytes+       , convertUnit+       ) where++import           Data.Binary            (Binary)+import           Data.Proxy             (Proxy (Proxy))+import           Data.SafeCopy          (SafeCopy)+import           Data.Serialize         (Serialize)+import           Data.Text.Lazy.Builder (Builder)+import           Data.Typeable          (Typeable)+import           Formatting             (bprint, stext, (%))+import qualified Formatting             as Fmt+import           GHC.Generics           (Generic)++import           Serokell.Util.Text     (showFixedPretty')++import           Test.QuickCheck        (Arbitrary)++class Integral unit => MemoryUnit unit  where+    -- | This value is n iff (1 :: unit) is n bytes.+    bytesMultiplier :: Proxy unit -> Integer++-- | Convert given memory unit into integer representing bytes.+toBytes+    :: forall unit.+       MemoryUnit unit+    => unit -> Integer+toBytes mu = toInteger mu * bytesMultiplier proxy+  where+    proxy :: Proxy unit+    proxy = Proxy++-- | Convert given number of bytes into memory unit, flooring value if+-- necessary.+fromBytes+    :: forall unit.+       MemoryUnit unit+    => Integer -> unit+fromBytes bytes = fromInteger $ bytes `div` bytesMultiplier proxy+  where+    proxy :: Proxy unit+    proxy = Proxy++-- | Conversion between memory units.+convertUnit :: (MemoryUnit a, MemoryUnit b) => a -> b+convertUnit = fromBytes . toBytes++-- | Construct Text Builder.+unitBuilder :: MemoryUnit unit => unit -> Builder+unitBuilder n@(toBytes -> bytes)+  | bytes == 0 = "0"+  | bytes < 0 = mconcat ["-", unitBuilder n]+  | otherwise = bprint (stext % " " % stext) (showFixedPretty' 3 value) suffix+  where+    suffixes = ["B", "KiB", "MiB", "GiB", "TiB", "Pib", "EiB", "ZiB", "YiB"]+    bytesDouble :: Double+    bytesDouble = realToFrac bytes+    order = min (length suffixes - 1) (floor $ logBase 2 bytesDouble / 10)+    suffix = suffixes !! order+    value = bytesDouble / realToFrac ((2 :: Integer) ^ (order * 10))++-- | Formatter for `formatting` library.+memory :: MemoryUnit unit => Fmt.Format r (unit -> r)+memory = Fmt.later unitBuilder++pow10 :: Num n => Int -> n+pow10 = (10 ^)++newtype Byte =+    Byte Integer+    deriving (Show,Eq,Num,Typeable,Integral,Real,Enum,Ord,Generic,Serialize,Binary, Arbitrary)++instance SafeCopy Byte++instance MemoryUnit Byte where+    bytesMultiplier Proxy = pow10 0++newtype Kilobyte =+    Kilobyte Integer+    deriving (Show,Eq,Num,Typeable,Integral,Real,Enum,Ord,Generic,Serialize,Arbitrary)++instance SafeCopy Kilobyte++instance MemoryUnit Kilobyte where+    bytesMultiplier Proxy = pow10 3++-- P. S. Feel free to add more.++newtype Megabyte =+    Megabyte Integer+    deriving (Show,Eq,Num,Typeable,Integral,Real,Enum,Ord,Generic,Serialize,Arbitrary)++instance SafeCopy Megabyte++instance MemoryUnit Megabyte where+    bytesMultiplier Proxy = pow10 6++newtype Gigabyte =+    Gigabyte Integer+    deriving (Show,Eq,Num,Typeable,Integral,Real,Enum,Ord,Generic,Serialize,Arbitrary)++instance SafeCopy Gigabyte++instance MemoryUnit Gigabyte where+    bytesMultiplier Proxy = pow10 9++newtype Terabyte =+    Terabyte Integer+    deriving (Show,Eq,Num,Typeable,Integral,Real,Enum,Ord,Generic,Serialize,Arbitrary)++instance SafeCopy Terabyte++instance MemoryUnit Terabyte where+    bytesMultiplier Proxy = pow10 12
+ src/Serokell/Data/Variant.hs view
@@ -0,0 +1,14 @@+-- | Variant is a data type with dynamic structure. It's designed to+-- be very generic, but also efficient. You can choose whatever data+-- layout you want. This module re-exports modules relevant to this+-- type.++module Serokell.Data.Variant+       (+         module Export+       ) where++import           Serokell.Data.Variant.Class         as Export+import           Serokell.Data.Variant.Helpers       as Export+import           Serokell.Data.Variant.Serialization ()+import           Serokell.Data.Variant.Variant       as Export
+ src/Serokell/Data/Variant/Class.hs view
@@ -0,0 +1,31 @@+-- | Type class for values which can be converted to/from Variant.+-- Also contains some instances.++module Serokell.Data.Variant.Class+       ( ToVariant (toVariant)+       , FromVariant (fromVariant)+       ) where++import           Control.Monad.Catch           (MonadThrow)+import           Formatting                    (build, sformat, (%))++import           Serokell.Data.Variant.Variant (Variant (..))+import           Serokell.Util.Exceptions      (throwText)++class ToVariant v where+    toVariant :: v -> Variant++class FromVariant v where+    -- TODO: it won't be used now, so I didn't think much about this type.+    -- Maybe it's not the best idea to use MonadThrow here.+    fromVariant :: MonadThrow m => Variant -> m v++instance ToVariant Bool where+    toVariant = VarBool++instance ToVariant Double where+    toVariant = VarFloat++instance FromVariant Double where+    fromVariant (VarFloat v) = pure v+    fromVariant v = throwText $ sformat ("value is not Double: " % build) v
+ src/Serokell/Data/Variant/Helpers.hs view
@@ -0,0 +1,21 @@+-- | Helper functions for convenient work with Variant. Feel free to+-- add more if you need.++module Serokell.Data.Variant.Helpers+       ( none+       , varMap+       ) where++import           Data.Foldable                 (Foldable (toList))+import qualified Data.HashMap.Strict           as HM hiding (HashMap)++import           Serokell.Data.Variant.Variant (Variant (..))++-- | Shorter alias for VarNone.+none :: Variant+none = VarNone++-- | Create VarMap from Foldable containing pairs of Variants.+-- TODO: maybe use better approach like `a := b` to create KeyValuePair.+varMap :: Foldable t => t (Variant, Variant) -> Variant+varMap = VarMap . HM.fromList . toList
+ src/Serokell/Data/Variant/Serialization.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE TupleSections #-}++-- | This module contains serialization logic for Variant type.+-- Feel free to add serialization to/from other formats if you need it.++module Serokell.Data.Variant.Serialization+       (+       ) where++import qualified Data.Aeson                    as Aeson+import           Data.Bifunctor                (bimap)+import           Data.Binary                   (Binary)+import           Data.Binary.Orphans           ()+import           Data.Hashable                 (Hashable)+import           Data.HashMap.Strict           (HashMap)+import qualified Data.HashMap.Strict           as HM hiding (HashMap)+import qualified Data.MessagePack              as MP+import           Data.SafeCopy                 (SafeCopy)+import           Data.Scientific               (floatingOrInteger)+import qualified Data.Serialize                as Cereal+import           Data.Text                     (Text)+import qualified Data.Text.Encoding            as TE+import qualified Data.Vector                   as V+import           Data.Vector.Serialize         ()++import           Serokell.Data.Variant.Variant (VarMap, Variant (..))+import           Serokell.Util.Base64          (JsonByteString (JsonByteString))+import           Serokell.Util.Text            (show')++--  —————————JSON serialization————————— --+-- Since there is no bijection between Variant and JSON Value, there are some+-- non-trivial things:+-- 1. decode . encode ≠ id. For example, (decode . encode) for VarBytes+--    will return VarString.+-- 2. Bytes are encoded in base64 encoding.+-- 3. If map contains key which is not a string, this key is converted to+--    string using Buildable instance. Usually you should avoid JSON+--    serialization for such maps.+-- 4. For numbers there is smart deserialization which tries to guess right+--    type (signed int/unsigned int/float).+--    If number is floating, VarFloat will be returned. For integer values+--    result type depends on sign (negative ⇒ Int, otherwise UInt).++varMapToObject :: VarMap -> Aeson.Object+varMapToObject = HM.fromList . map (bimap show' Aeson.toJSON) . HM.toList++instance Aeson.ToJSON Variant where+    toJSON VarNone       = Aeson.Null+    toJSON (VarBool v)   = Aeson.toJSON v+    toJSON (VarInt v)    = Aeson.toJSON v+    toJSON (VarUInt v)   = Aeson.toJSON v+    toJSON (VarFloat v)  = Aeson.toJSON v+    toJSON (VarBytes v)  = Aeson.toJSON . JsonByteString $ v+    toJSON (VarString v) = Aeson.toJSON v+    toJSON (VarList v)   = Aeson.toJSON v+    toJSON (VarMap v)    = Aeson.Object . varMapToObject $ v+    toEncoding VarNone       = Aeson.toEncoding Aeson.Null+    toEncoding (VarBool v)   = Aeson.toEncoding v+    toEncoding (VarInt v)    = Aeson.toEncoding v+    toEncoding (VarUInt v)   = Aeson.toEncoding v+    toEncoding (VarFloat v)  = Aeson.toEncoding v+    toEncoding (VarBytes v)  = Aeson.toEncoding . JsonByteString $ v+    toEncoding (VarString v) = Aeson.toEncoding v+    toEncoding (VarList v)   = Aeson.toEncoding v+    toEncoding (VarMap v)    = Aeson.toEncoding . varMapToObject $ v++instance Aeson.FromJSON Variant where+    parseJSON Aeson.Null = pure VarNone+    parseJSON (Aeson.Bool v) = pure . VarBool $ v+    parseJSON (Aeson.Number v) =+        pure . either VarFloat convertInt . floatingOrInteger $ v+      where+        convertInt :: Integer -> Variant+        convertInt i+          | i < 0 = VarInt $ fromIntegral i+          | otherwise = VarUInt $ fromIntegral i+    parseJSON (Aeson.String v) = pure . VarString $ v+    parseJSON (Aeson.Array v) = fmap VarList . mapM Aeson.parseJSON $ v+    parseJSON (Aeson.Object v) =+        fmap (VarMap . HM.fromList) .+        mapM+            (\(key,val) ->+                  (VarString key, ) <$> Aeson.parseJSON val) .+        HM.toList $+        v++--  —————————Cereal and SafeCopy serialization————————— --+-- This serialization is very simple: first byte is tag followed by actual value.+-- `decode . encode` should be `id`.++-- TODO: move it somewhere??+instance Cereal.Serialize Text where+    put = Cereal.put . TE.encodeUtf8+    get = TE.decodeUtf8 <$> Cereal.get++instance (Eq a, Hashable a, Cereal.Serialize a, Cereal.Serialize b) =>+         Cereal.Serialize (HashMap a b) where+    put = Cereal.put . HM.toList+    get = HM.fromList <$> Cereal.get++instance Cereal.Serialize Variant++instance SafeCopy Variant++--  —————————MessagePack serialization————————— --+-- MessagePack data structure is very close to Variant. However, note that:+-- 1. We are using strange library where Object type doesn't cover all+--    possible objects. For example, there is only `Int` for integers.+--    So every integer number is converted to `Int` (which may be imprecise).+--    Decoding checks sign of input (like JSON).+-- 2. MessagePack distinguishes between Float and Double while we don't.+-- 3. ObjectExt can't be decoded.++instance MP.MessagePack Variant where+    toObject VarNone = MP.ObjectNil+    toObject (VarBool v) = MP.ObjectBool v+    toObject (VarInt v) = MP.ObjectInt $ fromIntegral v+    toObject (VarUInt v) = MP.ObjectInt $ fromIntegral v+    toObject (VarFloat v) = MP.ObjectDouble v+    toObject (VarBytes v) = MP.ObjectBin v+    toObject (VarString v) = MP.ObjectStr v+    toObject (VarList v) = MP.ObjectArray . fmap MP.toObject . V.toList $ v+    toObject (VarMap v) =+        MP.ObjectMap .+        fmap (bimap MP.toObject MP.toObject) . HM.toList $+        v+    fromObject MP.ObjectNil = pure VarNone+    fromObject (MP.ObjectBool v) = pure . VarBool $ v+    fromObject (MP.ObjectInt v) | v < 0 = pure . VarInt . fromIntegral $ v+                                | otherwise = pure . VarUInt . fromIntegral $ v+    fromObject (MP.ObjectFloat v) = pure . VarFloat . realToFrac $ v+    fromObject (MP.ObjectDouble v) = pure . VarFloat $ v+    fromObject (MP.ObjectStr v) = pure . VarString $ v+    fromObject (MP.ObjectBin v) = pure . VarBytes $ v+    fromObject (MP.ObjectArray v) = fmap (VarList . V.fromList)+                                  . mapM MP.fromObject+                                  $ v+    fromObject (MP.ObjectMap v) =+        fmap (VarMap . HM.fromList) .+        mapM+            (\(a,b) ->+                  (,) <$> MP.fromObject a <*> MP.fromObject b) $+        v+    fromObject (MP.ObjectExt _ _) = fail "Can't deserialize ObjectExt"++--  —————————Binary serialization————————— --+-- Here we use Generic support, it should be good enough.+instance Binary Variant
+ src/Serokell/Data/Variant/Variant.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies      #-}++-- | Variant type.++module Serokell.Data.Variant.Variant+       ( Variant (..)+       , VarList+       , VarMap+       ) where++import           Control.DeepSeq      (NFData)+import           Data.ByteString      (ByteString)+import           Data.Hashable        (Hashable (hashWithSalt))+import           Data.HashMap.Strict  (HashMap)+import qualified Data.HashMap.Strict  as HM hiding (HashMap)+import           Data.Int             (Int64)+import           Data.String          (IsString (fromString))+import           Data.Text            (Text)+import           Data.Text.Buildable  (Buildable (build))+import           Data.Vector          (Vector)+import qualified Data.Vector          as V hiding (Vector)+import           Data.Word            (Word64)+import           GHC.Exts             (IsList (..))+import           GHC.Generics         (Generic)++import qualified Serokell.Util.Base16 as B16+import           Serokell.Util.Text   (listBuilderJSONIndent, mapBuilder)++type VarList = Vector Variant+type VarMap = HashMap Variant Variant++-- | Variant is intended to store arbitrary data in arbitrary+-- format. You are free to choose data layout.+data Variant+    = VarNone               -- ^ None, i. e. no value.+    | VarBool !Bool         -- ^ Boolean value.+    | VarInt !Int64         -- ^ Signed integer number.+    | VarUInt !Word64       -- ^ Unsigned integer number.+    | VarFloat !Double      -- ^ IEEE 754 double precision floating point number.+    | VarBytes !ByteString  -- ^ Raw bytes.+    | VarString !Text       -- ^ Unicode string.+    | VarList !VarList      -- ^ List of Variants.+    | VarMap !VarMap        -- ^ Map (with unique keys) from Variant to Variant.+    deriving (Show,Eq,Generic)++instance Buildable Variant where+    build VarNone       = "None"+    build (VarBool v)   = build v+    build (VarInt v)    = build v+    build (VarUInt v)   = build v+    build (VarFloat v)  = build v+    build (VarBytes v)  = build . B16.encode $ v+    build (VarString v) = build v+    build (VarList v)   = listBuilderJSONIndent 2 v+    build (VarMap v)    = mapBuilder . HM.toList $ v++instance Hashable (Vector Variant) where+    hashWithSalt salt = V.foldr' (flip hashWithSalt) (hashWithSalt salt ())++instance Hashable Variant++instance IsString Variant where+    fromString = VarString . fromString++instance IsList Variant where+    type Item Variant = Variant+    toList (VarList v) = toList v+    toList _           = error "toList: not a list"+    fromList = VarList . fromList++instance NFData Variant
+ src/Serokell/Util.hs view
@@ -0,0 +1,17 @@+-- | Re-export everything from Serokell.Util.*++module Serokell.Util+       (+         module Exports+       ) where++import           Serokell.Util.Bench        as Exports+import           Serokell.Util.Binary       as Exports+import           Serokell.Util.Common       as Exports+import           Serokell.Util.Concurrent   as Exports+import           Serokell.Util.Exceptions   as Exports+import           Serokell.Util.Lens         as Exports+import           Serokell.Util.OptParse     as Exports+import           Serokell.Util.StaticAssert as Exports+import           Serokell.Util.Text         as Exports+import           Serokell.Util.Verify       as Exports
+ src/Serokell/Util/Base16.hs view
@@ -0,0 +1,39 @@+-- | Base16 encoding/decoding.++module Serokell.Util.Base16+       ( encode+       , decode+       , formatBase16+       , base16F+       ) where++import qualified Data.ByteString        as BS+import qualified Data.ByteString.Base16 as B16+import qualified Data.Text              as T+import qualified Data.Text.Encoding     as TE+import           Data.Text.Lazy.Builder (Builder, fromText)+import           Formatting             (Format, later, sformat, stext, (%))++-- | Apply base16 encoding to strict ByteString.+encode :: BS.ByteString -> T.Text+encode = TE.decodeUtf8 . B16.encode++-- | Decode base16-encoded ByteString.+decode :: T.Text -> Either T.Text BS.ByteString+decode = handleError . B16.decode . TE.encodeUtf8+  where+    handleError (res,rest)+      | BS.null rest = pure res+      | otherwise =+          Left $+          sformat+              ("suffix is not in base-16 format: " % stext)+              (TE.decodeUtf8 rest)++-- | Construct Builder from bytestring formatting it in Base16.+formatBase16 :: BS.ByteString -> Builder+formatBase16 = fromText . encode++-- | Format which uses Base16 to print bytestring.+base16F :: Format r (BS.ByteString -> r)+base16F = later formatBase16
+ src/Serokell/Util/Base64.hs view
@@ -0,0 +1,74 @@+-- | Base64 encoding/decoding.++module Serokell.Util.Base64+       ( encode+       , decode+       , encodeUrl+       , decodeUrl+       , formatBase64+       , base64F+       , JsonByteString (..)+       , JsonByteStringDeprecated (..)+       ) where++import           Control.Monad              ((>=>))+import           Data.Aeson                 (FromJSON (parseJSON), ToJSON (toJSON))+import qualified Data.ByteString            as BS+import qualified Data.ByteString.Base64     as B64+import qualified Data.ByteString.Base64.URL as B64url+import           Data.Either.Combinators    (mapLeft)+import qualified Data.Text                  as T+import           Data.Text.Encoding         (decodeUtf8, encodeUtf8)+import           Data.Text.Lazy.Builder     (Builder, fromText)+import           Formatting                 (Format, later)++-- | Apply base64 encoding to strict ByteString.+encode :: BS.ByteString -> T.Text+encode = decodeUtf8 . B64.encode++-- | Decode base64-encoded ByteString.+decode :: T.Text -> Either T.Text BS.ByteString+decode = mapLeft T.pack . B64.decode . encodeUtf8++-- | Apply base64url encoding to strict ByteString.+encodeUrl :: BS.ByteString -> T.Text+encodeUrl = decodeUtf8 . B64url.encode++-- | Decode base64url-encoded ByteString.+decodeUrl :: T.Text -> Either T.Text BS.ByteString+decodeUrl = mapLeft T.pack . B64url.decode . encodeUtf8++-- | Construct Builder from bytestring formatting it in Base64.+formatBase64 :: BS.ByteString -> Builder+formatBase64 = fromText . encode++-- | Format which uses Base64 to print bytestring.+base64F :: Format r (BS.ByteString -> r)+base64F = later formatBase64++-- | Wrapper on top of ByteString with JSON serialization (in base64+-- encoding).+newtype JsonByteString = JsonByteString+    { getJsonByteString :: BS.ByteString+    }++instance ToJSON JsonByteString where+    toJSON = toJSON . encode . getJsonByteString++instance FromJSON JsonByteString where+    parseJSON =+        parseJSON >=>+        either (fail . T.unpack) (pure . JsonByteString) . decode++------------ Deprecated--------------+newtype JsonByteStringDeprecated = JsonByteStringDeprecated+    { getJsonByteStringDeprecated :: BS.ByteString+    }++instance ToJSON JsonByteStringDeprecated where+    toJSON = toJSON . encodeUrl . getJsonByteStringDeprecated++instance FromJSON JsonByteStringDeprecated where+    parseJSON =+        parseJSON >=>+        either (fail . T.unpack) (pure . JsonByteStringDeprecated) . decodeUrl
+ src/Serokell/Util/Bench.hs view
@@ -0,0 +1,74 @@+-- | Benchmark related utils.++module Serokell.Util.Bench+       ( getWallTime+       , getCpuTime+       , ElapsedTime (..)+       , measureTime+       , measureTime_+       , perSecond+       ) where++import           Control.Monad.Trans (MonadIO (liftIO))+import           Data.Text.Buildable (Buildable (build))+import           Data.Time.Units     (Nanosecond, TimeUnit, convertUnit)+import           Formatting          (bprint, shown, (%))+import           System.Clock        (Clock (..), TimeSpec, diffTimeSpec,+                                      getTime, toNanoSecs)++-- | Get current wall-clock time as any time unit.+getWallTime :: (MonadIO m, TimeUnit a) => m a+getWallTime = timeSpecToUnit <$> getTime' Realtime++-- | Get current CPU time as any time unit.+getCpuTime :: (MonadIO m, TimeUnit a) => m a+getCpuTime = timeSpecToUnit <$> getTime' ProcessCPUTime++timeSpecToUnit+    :: TimeUnit a+    => TimeSpec -> a+timeSpecToUnit =+    convertUnit . (fromIntegral :: Integer -> Nanosecond) . toNanoSecs++-- | Data type describing time passed during execution of something.+data ElapsedTime = ElapsedTime+    { elapsedCpuTime  :: TimeSpec+    , elapsedWallTime :: TimeSpec+    } deriving (Show)++instance Buildable ElapsedTime where+    build ElapsedTime{..} =+        bprint+            ("(CPU time = " % shown % ", wall time = " % shown % ")")+            elapsedCpuTime+            elapsedWallTime++getTime' :: MonadIO m => Clock -> m TimeSpec+getTime' = liftIO . getTime++-- | Run given action and measure how much time it took.+measureTime :: MonadIO m => m a -> m (ElapsedTime, a)+measureTime action = do+    cpuTimeBefore <- getTime' ProcessCPUTime+    wallTimeBefore <- getTime' Realtime+    res <- action+    wallTimeAfter <- getTime' Realtime+    cpuTimeAfter <- getTime' ProcessCPUTime+    return+        ( ElapsedTime+          { elapsedCpuTime = cpuTimeAfter `diffTimeSpec` cpuTimeBefore+          , elapsedWallTime = wallTimeAfter `diffTimeSpec` wallTimeBefore+          }+        , res)++-- | Run given action and measure how much time it took, discarding+-- result of action.+measureTime_ :: MonadIO m => m a -> m ElapsedTime+measureTime_ = fmap fst . measureTime++-- | Given number of actions executed during some time, this function+-- calculates how much actions were executed per second (on average).+perSecond :: (Real a, Fractional b) => a -> TimeSpec -> b+perSecond n time =+    fromRational $+    toRational n / (fromIntegral (max 1 $ toNanoSecs time) * 1.0e9)
+ src/Serokell/Util/Binary.hs view
@@ -0,0 +1,17 @@+-- | Utilities for @Data.Binary@.++module Serokell.Util.Binary+       ( decodeFull+       ) where++import           Data.Binary (Binary, decodeOrFail)+import qualified Data.ByteString.Lazy as BSL+import           Data.ByteString.Lazy (ByteString)++-- | Like 'decode', but ensures that the whole input has been consumed.+decodeFull :: Binary a => ByteString -> Either String a+decodeFull bs = case decodeOrFail bs of+    Left (_, _, err) -> Left ("decodeFull: " ++ err)+    Right (unconsumed, _, a)+        | BSL.null unconsumed -> Right a+        | otherwise -> Left "decodeFull: unconsumed input"
+ src/Serokell/Util/Common.hs view
@@ -0,0 +1,72 @@+-- | Common utilities.++module Serokell.Util.Common+       ( enumerate+       , indexModulo+       , indexModuloMay+       , indexedSubList+       , subList+       ) where++import           Control.Monad.State (evalState, get, modify)+import           Data.List           (genericDrop, genericIndex, genericLength,+                                      genericTake)+import           Data.Maybe          (fromMaybe)++-- | Enumerate function is analogous to python's enumerate. It+-- takes sequences of values and returns sequence of pairs where the+-- first element is index and the second one is corresponding value.+-- It's roughly equivalent to `zip [0..]`.+-- > enumerate "Hello" = [(0,'H'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]+enumerate+    :: (Num i, Enum i, Traversable t)+    => t a -> t (i, a)+enumerate values = evalState action 0+  where+    action = mapM step values+    step v = do+        i <- get+        modify succ+        return (i, v)++-- | Returns element of a list with given index modulo length of+-- list. Raises error if list is empty.+-- Examples:+-- indexModulo [1, 2, 3] 10 = 2+-- indexModulo [1, 0] 2 = 1+-- indexModulo [] 199 = error+indexModulo :: Integral i => [a] -> i -> a+indexModulo xs =+    fromMaybe (error "Serokell.Util.Common.indexModulo: empty list") .+    indexModuloMay xs++-- | Behaves like `indexModulo` but uses Maybe to report error+-- (i. e. empty list).+indexModuloMay :: Integral i => [a] -> i -> Maybe a+indexModuloMay xs i = genericIndex xs <$> indexModuloIndex xs i++indexModuloIndex :: Integral i => [a] -> i -> Maybe i+indexModuloIndex [] _ = Nothing+indexModuloIndex xs i = Just $ i `mod` genericLength xs++-- | indexedSubList (lo, hi) returns sublist of given list with+-- indices in [max lo 0, hi). If the lower bound is negative,+-- 0 will in its place. If both indices are negative, the empty list+-- is returned.+-- Examples:+-- indexedSubList (2, 3) [0, 5, 10] = [(2, 10)]+-- indexedSubList (0, 2) [0, 5, 10] = [(0, 0), (1, 5)]+-- indexedSubList (0, 0) [0, 1, 11, 111] = []+-- indexedSubList (2000, 1000) [55, 47, 0, 1, 11, 111] = []+-- indexedSubList (-3, 3) [10, 11, 12, 13, 14] = [(0,10),(1,11),(2,12)]+-- indexedSubList (-6, -1) [1,2,3] = []+indexedSubList+    :: Integral i+    => (i, i) -> [a] -> [(i, a)]+indexedSubList (lo, hi)+    | hi <= lo = const []+    | otherwise = zip [max 0 lo .. hi - 1] . genericTake (hi - lo) . genericDrop lo++-- | Like indexedSubList, but not indexed :)+subList :: Integral i => (i, i) -> [a] -> [a]+subList range = map snd . indexedSubList range
+ src/Serokell/Util/Concurrent.hs view
@@ -0,0 +1,14 @@+-- | Convenient versions of some functions from `Control.Concurrent`++module Serokell.Util.Concurrent+       ( threadDelay+       ) where++import qualified Control.Concurrent  as Concurrent+import           Control.Monad.Trans (MonadIO (liftIO))+import           Data.Time.Units     (TimeUnit (toMicroseconds))++-- | Convenient version of Control.Concurrent.threadDelay which takes+-- any time-unit and operates in any MonadIO+threadDelay :: (MonadIO m, TimeUnit unit) => unit -> m ()+threadDelay = liftIO . Concurrent.threadDelay . fromIntegral . toMicroseconds
+ src/Serokell/Util/Exceptions.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE TypeFamilies  #-}+{-# LANGUAGE TypeOperators #-}++-- | Bitgram's general-purpose exceptions.+-- They may be useful when you need a simple instance of Exception (e. g. in MonadThrow context)++module Serokell.Util.Exceptions+       ( TextException (..)+       , throwText+       , EmptyException (..)+       , throwEmpty+       , eitherToFail+       ) where++import           Control.Exception      (Exception, SomeException,+                                         fromException)+import           Control.Monad.Catch    (MonadThrow, throwM)+import           Data.Text              (Text)+import           Data.Text.Buildable    (Buildable (build))+import qualified Data.Text.Format       as F+import           Data.Text.Lazy.Builder (Builder)+import           Data.Typeable          (Typeable)++instance Buildable SomeException where+    build e =+        maybe (build $ F.Shown e) (build :: TextException -> Builder) $+        fromException e++-- | Use this type if you are sure that text description is enough to represent error+newtype TextException = TextException+    { teMessage :: Text+    } deriving (Show,Typeable)++instance Exception TextException++instance Buildable TextException where+    build = F.build "TextException: {}" . F.Only . teMessage++throwText :: MonadThrow m+          => Text -> m a+throwText = throwM . TextException++-- | Use this type if you want to signal about error and there may be only one reason for it+data EmptyException = EmptyException+  deriving (Show, Typeable)++instance Exception EmptyException++throwEmpty :: MonadThrow m => m a+throwEmpty = throwM EmptyException++-- | Convert MonadThrow to arbitrary monad using `fail` to report+-- error Useful when you have `MonadThrow` and want to use it in+-- another monad context which uses `fail` for errors+eitherToFail :: (Monad m, e ~ SomeException) => Either e a -> m a+eitherToFail = either (fail . show) return
+ src/Serokell/Util/I18N.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE UndecidableInstances #-}++module Serokell.Util.I18N+       ( Translations+       , fromYaml+       , replaceTranslations+       , YamlMapKey+       , ToReplaceToken(..)+       ) where++import           Data.Aeson.Extra.Map   (FromJSONKey (..), getMap)+import           Data.Aeson.Types       (genericParseJSON, genericToJSON)+import qualified Data.Aeson.Types       as AT+import           Data.ByteString        as BS+import qualified Data.Map.Strict        as M+import           Data.Text              as T+import           Data.Yaml              (decodeEither)+import           Formatting             (build, sformat, (%))+import           GHC.Generics           (Generic, Rep)+import           Serokell.Aeson.Options (defaultOptions)++class (Eq a, Ord a, FromJSONKey a) => YamlMapKey a++instance (Generic a, AT.GFromJSON (Rep a)) =>+         FromJSONKey a where+    parseJSONKey l = genericParseJSON defaultOptions (AT.String l)++instance (Eq a, Ord a, Generic a, AT.GFromJSON (Rep a)) =>+         YamlMapKey a++type Translations lang token = M.Map lang (M.Map token T.Text)++fromYaml :: (YamlMapKey lang, YamlMapKey token)+         => BS.ByteString+         -> Translations lang token+fromYaml yamlStr =+    toLangMap $+    either+        (error .+         T.unpack . sformat ("Error during translation YAML parsing " % build))+        id $+    decodeEither yamlStr+  where+    toLangMap = fmap getMap . getMap+++class (Ord a, Eq a) => ToReplaceToken a where+  toReplaceToken :: a -> T.Text++instance (Ord a, Eq a, Generic a, AT.GToJSON (Rep a)) => ToReplaceToken a where+  toReplaceToken = (\(AT.String s) -> s) . genericToJSON defaultOptions++replaceTranslations+    :: (ToReplaceToken token, Eq lang, Ord lang)+    => Translations lang token -> lang -> T.Text -> Maybe T.Text+replaceTranslations translations lang text =+    M.foldrWithKey+        (\token ->+              T.replace $ "{{" `T.append` toReplaceToken token `T.append` "}}")+        text <$>+    lang `M.lookup` translations
+ src/Serokell/Util/Lens.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE Rank2Types #-}++-- | Extra operators on Lens+module Serokell.Util.Lens+       ( (%%=)+       , (%?=)+       ) where++import qualified Control.Lens               as L+import           Control.Monad.State        (State, get, runState)+import           Control.Monad.Trans.Except (ExceptT, mapExceptT)++-- I don't know how to call these operators++-- | Similar to %= operator, but takes State action instead of (a -> a)+infix 4 %%=+(%%=) :: L.Lens' s a -> State a b -> State s b+(%%=) l ma = do+    attr <- L.view l <$> get+    let (res,newAttr) = runState ma attr+    l L..= newAttr+    return res++-- | Like %%= but with possiblity of failure+infix 4 %?=+(%?=) :: L.Lens' s a -> ExceptT t (State a) b -> ExceptT t (State s) b+(%?=) l = mapExceptT (l %%=)
+ src/Serokell/Util/OptParse.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE MultiParamTypeClasses #-}++-- | Some useful helper for optparse-applicative library++module Serokell.Util.OptParse+       ( fromStr+       , strArgument+       , strOption+       , fromParsec+       ) where++import           Data.String         (IsString (fromString))+import           Options.Applicative (ArgumentFields, Mod, OptionFields, Parser, ReadM,+                                      argument, eitherReader, option, str)+import           Text.Parsec         (Parsec, parse)++-- | Reader which uses IsString instance for parsing+fromStr :: IsString s => ReadM s+fromStr = fromString <$> str++-- | Parse argument using IsString instance+strArgument :: IsString s => Mod ArgumentFields s -> Parser s+strArgument = argument fromStr++-- | Parse option using IsString instance+strOption :: IsString s => Mod OptionFields s -> Parser s+strOption = option fromStr++fromParsec :: Parsec String () a -> ReadM a+fromParsec parser = eitherReader $ either (Left . show) Right . parse parser "<CLI options>"
+ src/Serokell/Util/Parse.hs view
@@ -0,0 +1,9 @@+-- | Some useful helpers for parsec library++module Serokell.Util.Parse+       ( module Export+       ) where++import           Serokell.Util.Parse.Base64  as Export+import           Serokell.Util.Parse.Common  as Export+import           Serokell.Util.Parse.Network as Export
+ src/Serokell/Util/Parse/Base64.hs view
@@ -0,0 +1,27 @@+-- | Parsing Base64++module Serokell.Util.Parse.Base64+       ( base64+       , base64Url+       ) where+import qualified Data.ByteString                    as BS+import           Data.Text                          (pack, unpack)+import           Serokell.Util.Base64               (decode, decodeUrl)+import           Serokell.Util.Parse.Common         (Parser, asciiAlphaNum)+import           Text.Parsec                        (many, many1, (<|>))+import           Text.ParserCombinators.Parsec.Char (char)++base64 :: Parser BS.ByteString+base64 = do+  str <- (++) <$> many1 (asciiAlphaNum <|> char '+' <|> char '/') <*> many (char '=')+  case decode $ pack str of+    Left e   -> fail $ unpack e+    Right bs -> return bs++base64Url :: Parser BS.ByteString+base64Url = do+  str <- (++) <$> many1 (asciiAlphaNum <|> char '_' <|> char '-') <*> many (char '=')+  case decodeUrl $ pack str of+    Left e   -> fail $ unpack e+    Right bs -> return bs+
+ src/Serokell/Util/Parse/Common.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE MultiParamTypeClasses #-}++-- | Parsing common helpers++module Serokell.Util.Parse.Common+       ( Parser+       , countMinMax+       , limitedInt+       , byte+       , asciiAlphaNum+       ) where++import           Text.Parsec                        (Parsec, ParsecT, Stream, option,+                                                     satisfy)+import           Text.ParserCombinators.Parsec.Char (digit)++type Parser = Parsec String ()++isAsciiAlpha :: Char -> Bool+isAsciiAlpha c = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')++isAsciiNum :: Char -> Bool+isAsciiNum c = (c >= '0' && c <= '9')++isAsciiAlphaNum :: Char -> Bool+isAsciiAlphaNum c = isAsciiAlpha c || isAsciiNum c++asciiAlphaNum :: Parser Char+asciiAlphaNum = satisfy isAsciiAlphaNum++countMinMax :: (Stream s m t) => Int -> Int -> ParsecT s u m a -> ParsecT s u m [a]+countMinMax m x p+    | m > 0 = do+        f <- p+        end <- countMinMax (m - 1) (x - 1) p+        return $ f : end+    | x <= 0 = return []+    | otherwise = option [] $ do+        f <- p+        end <- countMinMax 0 (x - 1) p+        return $ f : end++limitedInt :: Int -> String -> Parser Int+limitedInt x e = do+    b <- read <$> countMinMax 1 (intDigits x) digit+    if b > x+        then fail e+        else return b+  where+    intDigits = length . show++byte :: Parser Word+byte = fromIntegral <$> limitedInt 255 "Value to large"+
+ src/Serokell/Util/Parse/Network.hs view
@@ -0,0 +1,117 @@+-- | Parsing network data++module Serokell.Util.Parse.Network+       ( Host (..)+       , port+       , ipv4address+       , ipv6address+       , ipv6addressWithScope+       , hostname+       , host+       , host'+       , connection+       , connection'+       ) where++import           Control.Monad                      (liftM, void)+import           Data.Word                          (Word16)+import           Serokell.Util.Parse.Common         (Parser, asciiAlphaNum, byte,+                                                     countMinMax, limitedInt)+import           Text.Parsec                        (choice, count, many1, oneOf, option,+                                                     try, (<?>), (<|>))+import           Text.ParserCombinators.Parsec.Char (alphaNum, char, hexDigit, string)++data Host = IPv4Address { hostAddress :: String }+          | IPv6Address { hostAddress :: String }+          | HostName    { hostAddress :: String }+    deriving(Show, Eq, Ord)++concatSequence :: (Monad m) => [m [a]] -> m [a]+concatSequence = liftM concat . sequence++port :: Parser Word16+port = fromIntegral <$> limitedInt 65535 "Port number to large"++ipv4address :: Parser String+ipv4address = concatSequence [+    byteStr, string ".",+    byteStr, string ".",+    byteStr, string ".", byteStr] <?> "bad IPv4 address"+  where+    byteStr = show <$> byte++ipv6address :: Parser String+ipv6address = do+    let ipv6variants = (try <$> skippedAtBegin)+                        ++ [try full]+                        ++ (try <$> skippedAtMiddle)+                        ++ (try <$> skippedAtEnd)+                        ++ [last2 False]+    choice ipv6variants <?> "bad IPv6 address"+  where+    hexShortNum = countMinMax 1 4 hexDigit+    h4s = (++) <$> hexShortNum <*> string ":"+    sh4 = (++) <$> string ":" <*> hexShortNum+    execNum 0 = return ""+    execNum n = concat <$> count n h4s+    partNum 0 = return ""+    partNum n = do+        f <- hexShortNum+        e <- countMinMax 0 (n - 1) (try sh4)+        return $ f ++ concat e++    maybeNum n = concat <$> countMinMax 0 n h4s+    last2f = try ipv4address <|> concatSequence [h4s, hexShortNum]+    last2 f = if f+        then last2f+        else choice [try last2f,+                     try $ concatSequence [string "::", hexShortNum],+                     concatSequence [hexShortNum, string "::"]]++    skippedAtBegin =+        map (\i -> concatSequence [string "::", execNum i, last2 True]) [5,4..0]++    skippedAtMiddle = [+        concatSequence [partNum 1, string "::", maybeNum 4, last2 True],+        concatSequence [partNum 2, string "::", maybeNum 3, last2 True],+        concatSequence [partNum 3, string "::", maybeNum 2, last2 True],+        concatSequence [partNum 4, string "::", maybeNum 1, last2 True],+        concatSequence [partNum 5, string "::", last2 True],+        concatSequence [partNum 6, string "::", hexShortNum]]++    skippedAtEnd = [concatSequence [partNum 7, string "::"]]++    full = concatSequence [concat <$> count 6 h4s, last2 True]++ipv6addressWithScope :: Parser String+ipv6addressWithScope = concatSequence [ipv6address, option "" scope]+  where+    scope = concatSequence [string "%", many1 asciiAlphaNum]++hostname :: Parser String+hostname = many1 $ alphaNum <|> oneOf ".-_"++host :: Parser String+host = hostAddress <$> host'++host' :: Parser Host+host' = (IPv6Address <$> try ipv6str)+         <|> (IPv4Address <$> try ipv4address)+         <|> (HostName <$> hostname)+  where+    ipv6str = do+        void $ char '['+        ipv6 <- ipv6addressWithScope+        void $ char ']'+        return ipv6++connection :: Parser (String, Maybe Word16)+connection = (\(h, p) -> (hostAddress h, p)) <$> connection'++connection' :: Parser (Host, Maybe Word16)+connection' = do+    addr <- host'+    p <- maybePort+    return (addr, p)+  where+    maybePort = option Nothing $ char ':' >> Just <$> port
+ src/Serokell/Util/StaticAssert.hs view
@@ -0,0 +1,14 @@+-- | Compile time assertions+-- Very simple implementation from: http://stackoverflow.com/a/6654903+-- TODO: understand and maybe improve+module Serokell.Util.StaticAssert+       ( staticAssert+       ) where++import           Control.Monad       (unless)+import           Language.Haskell.TH (Q)++staticAssert :: Bool -> String -> Q [a]+staticAssert cond mesg = do+    unless cond $ fail $ "Compile time assertion failed: " ++ mesg+    return [] -- No need to make a dummy declaration
+ src/Serokell/Util/Text.hs view
@@ -0,0 +1,244 @@+{-# LANGUAGE GADTs #-}++-- | Utility functions to work with `text` and `text-format`. Feel+-- free to add more if you need. Some functions have two versions, `'`+-- suffix means that function operates on strict Text.++module Serokell.Util.Text+       ( -- * @formatting@ utilities+         show+       , show'+       , FPFormat (..)+       , showFloat+       , showFloat'+       , showFixedPretty'+       , showDecimal+       , showDecimal'++       -- * Formatters+       , pairF+       , tripleF+       , listJson+       , listJsonIndent+       , listCsv+       , mapJson++       -- * Builders+       , pairBuilder+       , tripleBuilder+       , listBuilder+       , listBuilderJSON+       , listBuilderJSONIndent+       , listBuilderCSV+       , mapBuilder+       , mapBuilderJson++       -- * @text-format@ utilities+       , format+       , format'+       , formatSingle+       , formatSingle'+       , buildSingle++       -- * String readers+       , readFractional+       , readDouble+       , readDecimal+       , readUnsignedDecimal+       ) where++import qualified Data.Text                        as T+import           Data.Text.Buildable              (Buildable (build))+import qualified Data.Text.Format                 as F+import           Data.Text.Format.Params          (Params)+import qualified Data.Text.Lazy                   as LT+import qualified Data.Text.Lazy.Builder           as B+import qualified Data.Text.Lazy.Builder.Int       as B+import           Data.Text.Lazy.Builder.RealFloat (FPFormat (Exponent, Fixed, Generic))+import qualified Data.Text.Lazy.Builder.RealFloat as B+import qualified Data.Text.Read                   as T+import           Formatting                       (fixed, sformat, later, Format)+import           GHC.Exts                         (IsList(..))+import           Prelude                          hiding (show, showList)++show :: Buildable a+     => a -> LT.Text+show = B.toLazyText . build++show' :: Buildable a+      => a -> T.Text+show' = LT.toStrict . show++-- | Render a floating point number using normal notation, with the+-- given number of decimal places. This function also truncates+-- redundant terminating zeros.+showFixedPretty'+    :: Real a+    => Int -> a -> T.Text+showFixedPretty' prec =+    T.dropWhileEnd (== '.') . T.dropWhileEnd (== '0') . sformat (fixed prec)++showFloat+  :: (RealFloat a)+  => FPFormat -> Maybe Int -> a -> LT.Text+showFloat f precision v = B.toLazyText $ B.formatRealFloat f precision v++showFloat'+  :: (RealFloat a)+  => FPFormat -> Maybe Int -> a -> T.Text+showFloat' f prec = LT.toStrict . showFloat f prec++showDecimal :: (Integral a)+             => a -> LT.Text+showDecimal = B.toLazyText . B.decimal++showDecimal' :: (Integral a)+              => a -> T.Text+showDecimal' = LT.toStrict . showDecimal++pairF :: (Buildable a, Buildable b) => Format r ((a,b) -> r)+pairF = later pairBuilder++tripleF :: (Buildable a, Buildable b, Buildable c) => Format r ((a,b,c) -> r)+tripleF = later tripleBuilder++listJson :: (Foldable t, Buildable a) => Format r (t a -> r)+listJson = later listBuilderJSON++listJsonIndent :: (Foldable t, Buildable a) => Word -> Format r (t a -> r)+listJsonIndent = later . listBuilderJSONIndent++listCsv :: (Foldable t, Buildable a) => Format r (t a -> r)+listCsv = later listBuilderCSV++mapJson :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v)+        => Format r (t -> r)+mapJson = later mapBuilderJson++-- | Prints pair (a, b) like "(a, b)"+pairBuilder+    :: (Buildable a, Buildable b)+    => (a, b) -> B.Builder+pairBuilder = F.build "({}, {})"++-- | Prints triple (a, b, c) like "(a, b, c)"+tripleBuilder+    :: (Buildable a, Buildable b, Buildable c)+    => (a, b, c) -> B.Builder+tripleBuilder = F.build "({}, {}, {})"++-- | Generic list builder. Prints prefix, then values separated by delimiter and finally suffix+listBuilder+  :: (Buildable prefix, Buildable delimiter, Buildable suffix, Foldable t, Buildable a)+  => prefix -> delimiter -> suffix -> t a -> B.Builder+listBuilder prefix delimiter suffix as =+  mconcat [build prefix, mconcat builders, build suffix]+  where builders = foldr appendBuilder [] as+        appendBuilder a [] = [build a]+        appendBuilder a bs = build a : build delimiter : bs++-- | This function helps to deduce type arising from string literal+_listBuilder+  :: (Foldable t, Buildable a)+  => B.Builder -> B.Builder -> B.Builder -> t a -> B.Builder+_listBuilder = listBuilder++-- | Prints values in JSON-style (e. g. `[111, ololo, blablabla]`)+listBuilderJSON+  :: (Foldable t, Buildable a)+  => t a -> B.Builder+listBuilderJSON = _listBuilder "[" ", " "]"++-- | Like listBuilderJSON, but prints each value on a new line with indentation+listBuilderJSONIndent+  :: (Foldable t, Buildable a)+  => Word -> t a -> B.Builder+listBuilderJSONIndent _ as | null as = "[]"+listBuilderJSONIndent indent as+  | otherwise =+    listBuilder ("[\n" `LT.append` spaces)+                delimiter+                ("\n]" :: B.Builder)+                as+  where spaces =+          LT.replicate (fromIntegral indent)+                       " "+        delimiter = ",\n" `LT.append` spaces++-- | Prints comma separated values+listBuilderCSV+  :: (Foldable t, Buildable a)+  => t a -> B.Builder+listBuilderCSV = _listBuilder "" "," ""++-- | There is no appropriate type class for map, but all reasonable maps+-- provide something like `assocs` function.+-- Map may be printed prettier (e. g. using JSON style), it's future task.+-- Having at least one such function is still good anyway.+mapBuilder+    :: (Traversable t, Buildable k, Buildable v)+    => t (k, v) -> B.Builder+mapBuilder = listBuilderJSON . fmap pairBuilder++mapBuilderJson+    :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v)+    => t -> B.Builder+mapBuilderJson = _listBuilder "{" ", " "}" . map (F.build "{}: {}") . toList++{-# DEPRECATED format, format', formatSingle, formatSingle' "Not typesafe. Use formatting library instead" #-}++-- | Re-export Data.Text.Format.format for convenience+format :: Params ps+       => F.Format -> ps -> LT.Text+format = F.format++-- | Version of Data.Text.Format.format which returns strict Text+format' :: Params ps+        => F.Format -> ps -> T.Text+format' f = LT.toStrict . F.format f++formatSingle :: Buildable a+             => F.Format -> a -> LT.Text+formatSingle f = format f . F.Only++formatSingle' :: Buildable a+              => F.Format -> a -> T.Text+formatSingle' f = LT.toStrict . formatSingle f++buildSingle :: Buildable a+            => F.Format -> a -> B.Builder+buildSingle f = F.build f . F.Only++-- | Read fractional number. Returns error (i. e. Left) if there is something else+readFractional :: Fractional a => T.Text -> Either String a+readFractional = _wrapReader T.rational++-- | Like readFractional, but much more efficient. It may be slightly less accurate+readDouble :: T.Text -> Either String Double+readDouble = _wrapReader T.double++-- | Read signed decimal number. Returns error (i. e. Left) if there is something else+-- WARNING: if input is negative and `a` is unsigned, overflow will occur+readDecimal :: Integral a => T.Text -> Either String a+readDecimal = _wrapReader $ T.signed T.decimal++-- | Read unsigned decimal number. Returns error (i. e. Left) if there is something else+readUnsignedDecimal :: Integral a => T.Text -> Either String a+readUnsignedDecimal = _wrapReader T.decimal++_wrapReader :: T.Reader a -> T.Text -> Either String a+_wrapReader reader t =+  case reader t of+    Left err -> Left $ mconcat [ "failed to parse '"+                               , T.unpack t+                               , "': "+                               , err+                               ]+    Right (res, "") -> Right res+    Right (_, remainder) ->+      Left $+      mconcat [ "failed to parse '"+              , T.unpack t+              , "', because there is a remainder: "+              , T.unpack remainder+              ]
+ src/Serokell/Util/Verify.hs view
@@ -0,0 +1,52 @@+-- | General-purpose utility functions++module Serokell.Util.Verify+       ( VerificationRes (..)+       , isVerFailure+       , isVerSuccess++       , verifyGeneric+       ) where++import           Data.Semigroup (Semigroup)+import qualified Data.Semigroup as Semigroup+import qualified Data.Text      as T++data VerificationRes+    = VerSuccess+    | VerFailure ![T.Text]+    deriving (Show)++isVerSuccess :: VerificationRes -> Bool+isVerSuccess VerSuccess = True+isVerSuccess _          = False++isVerFailure :: VerificationRes -> Bool+isVerFailure (VerFailure _) = True+isVerFailure _              = False++instance Semigroup VerificationRes where+    VerSuccess <> a = a+    VerFailure xs <> a =+        VerFailure $+        xs +++        case a of+            VerSuccess    -> []+            VerFailure ys -> ys++instance Monoid VerificationRes where+    mempty = VerSuccess+    mappend = (Semigroup.<>)++-- | This function takes list of (predicate, message) pairs and checks+-- each predicate.  If predicate is False it's considered an error.+-- If there is at least one error this function returns VerFailure,+-- otherwise VerSuccess is returned.  It's useful to verify some data+-- before using it.+-- Example usage: `verifyGeneric [(checkA, "A is bad"), (checkB, "B is bad")]`+verifyGeneric :: [(Bool, T.Text)] -> VerificationRes+verifyGeneric errors+    | null messages = VerSuccess+    | otherwise = VerFailure messages+  where+    messages = map snd . filter (not . fst) $ errors
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}
+ test/Test.hs view
@@ -0,0 +1,5 @@+import           Spec       (spec)+import           Test.Hspec (hspec)++main :: IO ()+main = hspec spec
+ test/Test/Serokell/Data/Memory/UnitsSpec.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ScopedTypeVariables       #-}+{-# LANGUAGE StandaloneDeriving        #-}++module Test.Serokell.Data.Memory.UnitsSpec+       ( spec+       ) where++import           Test.Hspec                 (Spec, describe)+import           Test.Hspec.QuickCheck      (prop)+import           Test.QuickCheck            ((===))++import           Serokell.Arbitrary         ()+import qualified Serokell.Data.Memory.Units as S++spec :: Spec+spec = describe "Unit conversion" $ do+           describe "Identity Properties" $ do+               prop "Byte" $+                   \(a :: S.Byte) -> a === bytesMid a+               prop "Kilobyte" $+                   \(a :: S.Kilobyte) -> a === bytesMid a+               prop "Megabyte" $+                   \(a :: S.Megabyte) -> a === bytesMid a+               prop "Gigabyte" $+                   \(a :: S.Gigabyte) -> a === bytesMid a+               prop "Terabyte" $+                   \(a :: S.Terabyte) -> a === bytesMid a++bytesMid :: forall u. S.MemoryUnit u => u -> u+bytesMid = S.convertUnit
+ test/Test/Serokell/Data/Variant/VariantSpec.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns        #-}++module Test.Serokell.Data.Variant.VariantSpec+       ( spec+       ) where++import qualified Data.Aeson            as A (decode, encode)+import qualified Data.Binary           as B (decode, encode)+import qualified Data.HashMap.Lazy     as HM (elems, fromList, keys)+import           Data.MessagePack      (fromObject, toObject)+import qualified Data.SafeCopy         as SC (safeGet, safePut)+import           Data.Scientific       (floatingOrInteger, fromFloatDigits)+import qualified Data.Serialize        as C (decode, encode, runGet, runPut)+import           Data.Text             (unpack)+import qualified Data.Vector           as V (map)+import           Test.Hspec            (Spec, describe)+import           Test.Hspec.QuickCheck (prop)+import           Test.QuickCheck       ((===))++import           Serokell.Arbitrary    (VariantNoBytes (..),+                                        VariantOnlyBytes (..))+import qualified Serokell.Data.Variant as S+import qualified Serokell.Util.Base64  as S+import           Serokell.Util.Text    (show')++spec :: Spec+spec = describe "Variant" $ do+           describe "Identity Properties" $ do+               describe "JSON" $ do+                   prop "Variant (No VarBytes)" $+                       \(getVariant -> a) -> (jsonFixer a) === jsonMid a+                   prop "Variant (Only VarBytes)" $+                       \(getVarBytes -> a) -> a === (bytesFun $ jsonMid a)+               prop "MessagePack" $+                   \(a :: S.Variant) -> (msgPkFixer a) === msgPkMid a+               prop "Binary" $+                   \(a :: S.Variant) -> a === binMid a+               prop "SafeCopy" $+                   \(a :: S.Variant) -> a === safeCopyMid a+               prop "Serialize" $+                   \(a :: S.Variant) -> a === cerealMid a++jsonFixer :: S.Variant -> S.Variant+jsonFixer (S.VarMap m) = let ks = map toStr $ HM.keys m+                             vs = map jsonFixer $ HM.elems m+                             m' = HM.fromList $ zip ks vs+                         in S.VarMap m'+jsonFixer (S.VarList l) = S.VarList $ V.map jsonFixer l+jsonFixer v@(S.VarInt i) =+    if i < 0 then v+             else (S.VarUInt $ fromIntegral i)+jsonFixer (S.VarFloat f) =+    case floatingOrInteger $ fromFloatDigits f of+        Left float -> S.VarFloat float+        Right int -> if int < 0 then S.VarInt int+                                else S.VarUInt $ fromIntegral int+jsonFixer v = v++toStr :: S.Variant -> S.Variant+toStr var = stringVar var+  where+    stringVar :: S.Variant -> S.Variant+    stringVar = S.VarString . show'++jsonMid :: S.Variant -> S.Variant+jsonMid = maybe err id . A.decode . A.encode+   where+     err = error "[VariantSpec] Failed JSON decoding"++bytesFun :: S.Variant -> S.Variant+bytesFun (S.VarString s) = S.VarBytes right+  where+     right = either (error . unpack) id $ S.decode s+bytesFun _ = error "[bytesFun:] called with Variant that was not VarBytes"++msgPkFixer :: S.Variant -> S.Variant+msgPkFixer (S.VarMap m) = let ks = map msgPkFixer $ HM.keys m+                              vs = map msgPkFixer $ HM.elems m+                              m' = HM.fromList $ zip ks vs+                          in S.VarMap m'+msgPkFixer (S.VarList l) = S.VarList $ V.map msgPkFixer l+msgPkFixer v@(S.VarInt i) =+    if i < 0 then v+             else (S.VarUInt $ fromIntegral i)+msgPkFixer v@(S.VarUInt i) =+    if i >= 0 then v+             else (S.VarInt $ fromIntegral i)+msgPkFixer v = v++msgPkMid :: S.Variant -> S.Variant+msgPkMid = maybe err id . fromObject . toObject+  where+    err = error "[VariantSpec] Failed MessagePack decoding"++binMid :: S.Variant -> S.Variant+binMid = B.decode . B.encode++cerealMid :: S.Variant -> S.Variant+cerealMid = either error id . C.decode . C.encode++safeCopyMid :: S.Variant -> S.Variant+safeCopyMid = either error id . C.runGet SC.safeGet . C.runPut . SC.safePut
+ test/Test/Serokell/Util/ByteStringSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving  #-}++module Test.Serokell.Util.ByteStringSpec+       ( spec+       ) where++import           Data.Aeson                (decode, encode)+import qualified Data.ByteString           as BS+import           Data.Maybe                (fromJust)+import           Test.Hspec                (Spec, describe)+import           Test.Hspec.QuickCheck     (prop)+import           Test.QuickCheck           ((===))+import           Test.QuickCheck.Instances ()++import           Serokell.Arbitrary        ()+import qualified Serokell.Util.Base16      as C16+import qualified Serokell.Util.Base64      as C64++deriving instance Eq C64.JsonByteString+deriving instance Show C64.JsonByteString++spec :: Spec+spec =+    describe "Serialization" $ do+        describe "Indentity Properties" $ do+            prop "Base16" $+                \(a :: BS.ByteString) -> a === base16Mid a+            prop "Base64" $+                \(a :: BS.ByteString) -> a === base64Mid a+            prop "JSON Base64" $+                \(a :: C64.JsonByteString) -> a === base64JSONMid a++base16Mid,+    base64Mid :: BS.ByteString -> BS.ByteString+base16Mid = fromRight . C16.decode . C16.encode+base64Mid = fromRight . C64.decode . C64.encode++base64JSONMid :: C64.JsonByteString -> C64.JsonByteString+base64JSONMid = fromJust . decode . encode++fromRight :: Either a BS.ByteString -> BS.ByteString+fromRight (Left _)  = error "failed decoding to ByteString"+fromRight (Right b) = b
+ test/Test/Serokell/Util/CommonSpec.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ViewPatterns              #-}++module Test.Serokell.Util.CommonSpec+       ( spec+       ) where++import           Data.Foldable             (toList)+import           Data.List                 (genericIndex, genericLength,+                                            intersect)+import           Data.Vector               (Vector)+import           Test.Hspec                (Spec, describe)+import           Test.Hspec.QuickCheck     (prop)+import           Test.QuickCheck           (Arbitrary (..), Gen,+                                            NonEmptyList (..), oneof)+import           Test.QuickCheck.Instances ()++import qualified Serokell.Util.Common      as C++spec :: Spec+spec =+    describe "Common" $ do+        describe "enumerate" $+            prop description_enumerateCheck enumerateCheckIndexes+        describe "indexModulo" $+            prop description_indexModulo indexModuloCorrectIndex+        describe "indexedSubList" $+            prop description_indexedSubListNeg indexedSublistWhenNegative+  where+    description_enumerateCheck =+      "enumerates a structure in sequence, " +++      "starting from index 0"+    description_indexModulo =+      "returns the element of the the list with " +++      "given index modulo length of the list"+    description_indexedSubListNeg =+      "negative indices, given that the lower " +++      "bound is lesser than the upper bound, result in valid indexation"++-- TODO: consider using `SomeValue` to generate different types of+-- values.  I don't know how to add more than one quantifier to type+-- definition. Also consider using GADTs to achieve it.+type Value = Int++data SomeTraversable =+    forall t. (Traversable t, Show (t Value), Arbitrary (t Value)) =>+              SomeTraversable (t Value)++instance Show SomeTraversable where+    show (SomeTraversable t) = show t++instance Arbitrary SomeTraversable where+    arbitrary =+        oneof+            [ SomeTraversable <$> (arbitrary :: Gen [Value])+            , SomeTraversable <$> (arbitrary :: Gen (Vector Value))+            ]++enumerateCheckIndexes :: SomeTraversable -> Bool+enumerateCheckIndexes (SomeTraversable values) =+    let indexedVals = C.enumerate values+        indexList   = toList $ fmap fst indexedVals+        indexNum    = genericLength indexList+    in null values || indexList == [0 :: Word .. indexNum-1]++indexModuloCorrectIndex+    :: NonEmptyList Int -> Int -> Bool+indexModuloCorrectIndex (getNonEmpty -> list) ind =+    let len = genericLength list+        atModuloIndex = list `genericIndex` (ind `mod` len)+    in atModuloIndex == C.indexModulo list ind++indexedSublistWhenNegative+    :: (Int, Int) -> [Int] -> Bool+indexedSublistWhenNegative (lo, hi) list+    | hi <= lo = indexList lo hi == []+    | hi <= 0 = indexList lo hi == []+    | len -1 < lo = indexList lo hi == []+    | otherwise = testIndexes lo hi == indexList lo hi+  where+    len = length list+    indexList l h = map fst $ C.indexedSubList (l, h) list+    testIndexes l h = intersect [l .. h - 1] [0 .. len - 1]
+ test/Test/Serokell/Util/TextSpec.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ScopedTypeVariables       #-}+{-# LANGUAGE StandaloneDeriving        #-}++module Test.Serokell.Util.TextSpec+       ( spec+       ) where++import           Data.Int                  (Int64)+import           Data.Text.Buildable       (Buildable)+import           Data.Word                 (Word64)++import           Test.Hspec                (Spec, describe)+import           Test.Hspec.QuickCheck     (prop)+import           Test.QuickCheck           ((===))+import           Test.QuickCheck.Instances ()++import qualified Serokell.Util.Text        as S++spec :: Spec+spec =+    describe "Text Show/Read" $ do+        describe "Indentity Properties" $ do+            describe "readDecimal" $ do+                prop "Int" $+                    \(a :: Int) -> (Right a) === showReadIntegral a+                prop "Integer" $+                    \(a :: Integer) -> (Right a) === showReadIntegral a+                prop "Word" $+                    \(a :: Word) -> (Right a) === showReadIntegral a+                prop "Int64" $+                    \(a :: Int64) -> (Right a) === showReadIntegral a+                prop "Word64" $+                    \(a :: Word64) -> (Right a) === showReadIntegral a++showReadIntegral+    :: (Buildable a, Integral a) => a -> Either String a+showReadIntegral = S.readDecimal . S.show'