bytepatch-0.2.0: src/BytePatch/Pretty/PascalText.hs
{-| Newtype for manipulating length-prefixed strings.
This type is for UTF-8 'Text' that you intend to write out to a length-prefixed
bytestring. The size of the length field is static. You essentially have to
decide the maximum bytesize of the string on creation.
-}
{-# LANGUAGE DataKinds, ScopedTypeVariables#-}
module BytePatch.Pretty.PascalText where
import BytePatch.Pretty.PatchRep
import qualified Data.Text.Encoding as Text
import Data.Text ( Text )
import qualified Data.ByteString as BS
import GHC.TypeLits
import Data.Proxy
import Data.Bits
newtype PascalText (n :: Nat) = PascalText { unPascalText :: Text }
instance KnownNat n => PatchRep (PascalText n) where
toPatchRep t =
case encodePascalText t of
Nothing -> Left "UTF-8 encoded text too long for length prefix field"
Just bs -> Right bs
encodePascalText :: forall n. KnownNat n => PascalText n -> Maybe BS.ByteString
encodePascalText t = do
lenBs <- encodeToSizedBE (fromIntegral (natVal (Proxy @n))) (BS.length bs)
return $ lenBs <> bs
where
bs = Text.encodeUtf8 . unPascalText $ t
encodeToSizedBE :: (Integral a, Bits a) => Int -> a -> Maybe BS.ByteString
encodeToSizedBE byteSize x =
let bs = i2be x
nulls = byteSize - BS.length bs
in if nulls < 0
then Nothing
else Just $ BS.replicate nulls 0x00 <> bs
-- | Re-encode an 'Integer' to a little-endian integer stored as a 'ByteString'
-- using the fewest bytes needed to represent it.
--
-- adapated from crypto-api 0.13.3, Crypto.Util.i2bs_unsized
i2be :: (Integral a, Bits a) => a -> BS.ByteString
i2be 0 = BS.singleton 0
i2be i = BS.reverse $ BS.unfoldr (\i' -> if i' <= 0 then Nothing else Just (fromIntegral i', (i' `shiftR` 8))) i
-- 0 -> 255 = 1
-- 256 -> 65535 = 2
ptLengthBytes :: forall n. KnownNat n => PascalText n -> Integer
ptLengthBytes _ = natVal (Proxy @n)