codec-0.1: Examples/Tar.hs
{-# 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 ""