lib: Make generic FilePack of any encodable type

This commit is contained in:
Alexander Kobjolke 2023-10-31 13:28:08 +01:00
parent 505f8e66af
commit 6c5f0b513c
2 changed files with 56 additions and 31 deletions

View file

@ -73,3 +73,9 @@ instance Encode Word32 where
instance Encode FileMode where
encode (CMode mode) = encode mode
instance (Encode a, Encode b) => Encode (a, b) where
encode (a, b) = encodeWithSize a <> encodeWithSize b
instance {-# OVERLAPPABLE #-} Encode a => Encode [a] where
encode = foldMap encodeWithSize

View file

@ -1,42 +1,61 @@
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RecordWildCards #-}
module FilePack.FileData where
import Data.ByteString (ByteString)
import Data.Text
import Data.Text (Text)
import System.Posix.Types (FileMode)
import Data.ByteString.Base64 qualified as B64
import Data.ByteString.Char8 qualified as BC
import GHC.Natural (Natural)
import System.Posix.Types (CMode (..), FileMode)
import Text.Read (readEither)
import Data.Word (Word32)
import FilePack.Encode (Encode (encode, encodeWithSize))
import FilePack.Encode ()
data FileData a = FileData
{ fileName :: Text
, fileSize :: Word32
, filePermissions :: FileMode
, fileData :: a
}
deriving (Eq, Read, Show)
data FileData = FileData
{ fileName :: Text
, fileSize :: Natural
, filePermissions :: FileMode
, fileData :: ByteString
}
deriving (Eq, Read, Show)
instance Encode a => Encode (FileData a) where
encode FileData{..} =
mconcat
[ encodeWithSize fileName
, encodeWithSize fileSize
, encodeWithSize filePermissions
, encodeWithSize fileData
]
newtype FilePack = FilePack {getPackedFiles :: [FileData]}
deriving (Eq, Read, Show)
data Packable = forall a. Encode a => Packable {getPackable :: a}
{- | @packFiles@ takes a FilePack and encodes it into a serialized form.
instance Encode Packable where
encode (Packable a) = encode a
>>> import Data.Text qualified as T
>>> import Data.ByteString qualified as BS
>>> import System.Posix.Files (stdFileMode)
>>> let sampleFileData = FileData{fileName=T.pack "foo.txt", fileSize = 1024, filePermissions = stdFileMode, fileData = BS.empty}
>>> let sampleFilePack = FilePack [sampleFileData]
>>> (Right sampleFilePack) == (unpackFiles . packFiles $ sampleFilePack)
True
-}
packFiles :: FilePack -> ByteString
packFiles = B64.encode . BC.pack . show
newtype FilePack = FilePack [Packable]
instance Encode FilePack where
encode (FilePack ps) = encode ps
infixr 6 .:
(.:) :: Encode a => FileData a -> FilePack -> FilePack
(.:) = addFileDataToPack
-- | @addFileDataToPack@ takes an encodeable FileData and adds it to a FilePack.
--
-- >>> import Data.ByteString qualified as BS
-- >>> d = FileData{fileName = pack "a", fileSize = 4, filePermissions = 0644, fileData = "foo"}
-- >>> BS.length $ encode d
-- 28
addFileDataToPack :: Encode a => FileData a -> FilePack -> FilePack
addFileDataToPack a (FilePack as) = FilePack (Packable a : as)
-- | @packFiles@ takes a FilePack and encodes it into a serialized form.
--
-- packFiles :: FilePack -> ByteString
-- packFiles = B64.encode . encode
-- | @unpackFiles@ tries to recover a FilePack from its serialized form.
unpackFiles :: ByteString -> Either String FilePack
unpackFiles serializedData =
B64.decode serializedData >>= readEither . BC.unpack
-- unpackFiles :: ByteString -> Either String FilePack
-- unpackFiles serializedData =
-- B64.decode serializedData >>= readEither . BC.unpack