conduit-iconv 0.1.0.0 → 0.1.1.0
raw patch · 4 files changed
+313/−145 lines, 4 filesdep +criteriondep ~bytestringdep ~conduit
Dependencies added: criterion
Dependency ranges changed: bytestring, conduit
Files
- Data/Conduit/IConv.hs +161/−77
- benchmarks/Benchmarks.hs +83/−0
- conduit-iconv.cabal +41/−27
- tests/Tests.hs +28/−41
Data/Conduit/IConv.hs view
@@ -1,14 +1,14 @@ {-# LANGUAGE ForeignFunctionInterface #-} --- | A "Data.Conduit#t:Conduit" based on "Codec.Text.IConv" for--- converting a "Data.ByteString" from one character set encoding to another+-- | A `Conduit` based on the iconv library for+-- converting a `Data.ByteString` from one character set encoding to another module Data.Conduit.IConv (- convert+ CharacterEncoding+ , convert ) where import Data.Conduit-import qualified Data.Conduit.List as CL import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as BU @@ -17,7 +17,13 @@ import Foreign hiding (unsafePerformIO) import Foreign.C import System.IO.Unsafe (unsafePerformIO)+import Control.Exception (mask_) +-- | Type synonym for character encoding names+--+-- See `convert` for details.+type CharacterEncoding = String+ -- | Convert text from one named character encoding to another. -- -- Encoding names can be e.g. @\"UTF-8\"@ or @\"ISO-8559-1\"@. Appending@@ -27,15 +33,14 @@ -- -- Without this encoding errors will cause an exception. ----- On errors this will call "Control.Monad#v:fail"-convert :: Monad m => String -- ^ Name of input character encoding- -> String -- ^ Name of output character encoding+-- On errors this will call `fail`+convert :: Monad m => CharacterEncoding -- ^ Name of input character encoding+ -> CharacterEncoding -- ^ Name of output character encoding -> Conduit B.ByteString m B.ByteString-convert inputEncoding outputEncoding =- let initialConvert = iconvConvert inputEncoding outputEncoding- in run initialConvert-+convert inputEncoding outputEncoding = run initialConvert where+ initialConvert = iconvConvert inputEncoding outputEncoding+ run f = do maybeInput <- await case maybeInput of@@ -43,10 +48,7 @@ Just input -> do let res = f input case res of- ConvertSuccess c f' -> do- CL.sourceList c- run f'-+ ConvertSuccess c f' -> yield c >> run f' ConvertUnsupportedConversionError -> fail "Unsupported conversion" ConvertUnexpectedOpenError s -> fail ("Unexpected open error: " ++ s) ConvertInvalidInputError -> fail "Invalid input"@@ -54,66 +56,105 @@ -- Stream based API around iconv() data ConvertResult =- ConvertSuccess [B.ByteString] (B.ByteString -> ConvertResult)+ ConvertSuccess B.ByteString (B.ByteString -> ConvertResult) | ConvertUnsupportedConversionError | ConvertUnexpectedOpenError String | ConvertInvalidInputError | ConvertUnexpectedConversionError String -iconvConvert :: String -> String -> B.ByteString -> ConvertResult+iconvConvert :: CharacterEncoding -> CharacterEncoding -> B.ByteString -> ConvertResult iconvConvert inputEncoding outputEncoding input = let eCtx = iconvOpen inputEncoding outputEncoding in case eCtx of Left UnsupportedConversion -> ConvertUnsupportedConversionError Left (UnexpectedOpenError (Errno errno)) -> ConvertUnexpectedOpenError (show errno)- Right ctx -> convertInput ctx B.empty [] input+ Right ctx -> convertInput ctx B.empty input where- convertInput ctx remaining converted newInput+ -- Converts newInput with the given context+ --+ -- If converted is not empty, this will be prepended to+ -- the converted output. This will happen if we're called+ -- after the remainder of a previous conversion was consumed.+ -- converted is the result of the remainder conversion.+ --+ -- If this conversion results in a remainder we return any+ -- results we got so far but will use convertInputWithRemaining+ -- with the remainder for the next call.+ convertInput ctx converted newInput | B.null newInput =- ConvertSuccess converted (convertInput ctx remaining [])+ ConvertSuccess converted (convertInput ctx B.empty) - | B.null remaining =- let res = iconv ctx newInput+ | otherwise =+ let res = iconv ctx newInput converted in case res of- Converted c r -> ConvertSuccess (converted ++ [c]) (convertInput ctx r [])- MoreData c r -> convertInput ctx B.empty (converted ++ [c]) r- InvalidInput c _ -> ConvertSuccess (converted ++ [c]) (const ConvertInvalidInputError)+ Converted c r -> ConvertSuccess c (convertInputWithRemaining ctx r)+ InvalidInput c _ -> if B.null c then+ ConvertInvalidInputError+ else+ -- Converted a bit but detected invalid input afterwards.+ -- Let's return what was converted so far and fail with+ -- the next call+ ConvertSuccess c (const ConvertInvalidInputError) UnexpectedError (Errno errno) -> ConvertUnexpectedConversionError (show errno) + -- Convert any remainder from a previous conversion. We do+ -- this by first appending the first 32 bytes of the newInput+ -- to the remainder. With 32 bytes additionally we should be+ -- able to convert the remainder from any possible charset+ -- encoding that has 32 *bytes* per character at maximum.+ -- 4 would've been enough too probably.+ --+ -- If the conversion was successful, we take the remaining+ -- newInput and the converted remainder, and hand it over+ -- to the previous conversion function. Which then will+ -- convert the remaining newInput and prepend the converted+ -- remainder to it.+ convertInputWithRemaining ctx remaining newInput+ | B.null remaining =+ convertInput ctx B.empty newInput++ | B.null newInput =+ ConvertSuccess B.empty (convertInputWithRemaining ctx remaining)+ | otherwise = let tmpInput = remaining `B.append` B.take 32 newInput- res = iconv ctx tmpInput+ res = iconv ctx tmpInput B.empty in case res of- Converted c r -> let processed = B.length tmpInput - B.length r- consumedInput = processed - B.length remaining- in- if processed < B.length remaining then- ConvertSuccess converted (convertInput ctx (remaining `B.append` newInput) [])- else- convertInput ctx B.empty (converted ++ [c]) (B.drop consumedInput newInput)- MoreData c r -> let processed = B.length tmpInput - B.length r+ Converted c r -> if processed < B.length remaining then+ -- Didn't convert the complete remainder. Let's try again next+ -- time with the additional newInput+ ConvertSuccess B.empty (convertInputWithRemaining ctx (remaining `B.append` newInput))+ else+ -- Converted the complete remainder. Now let's try to+ -- convert the remaining new input+ convertInput ctx c (B.drop consumedInput newInput)++ where+ processed = B.length tmpInput - B.length r consumedInput = processed - B.length remaining- in- if processed < B.length remaining then- ConvertSuccess converted (convertInput ctx (remaining `B.append` newInput) [])- else- convertInput ctx B.empty (converted ++ [c]) (B.drop consumedInput newInput)- InvalidInput c _ -> ConvertSuccess (converted ++ [c]) (const ConvertInvalidInputError)+ InvalidInput c _ -> if B.null c then+ ConvertInvalidInputError+ else+ -- Converted a bit but detected invalid input afterwards.+ -- Let's return what was converted so far and fail with+ -- the next call+ ConvertSuccess c (const ConvertInvalidInputError) UnexpectedError (Errno errno) -> ConvertUnexpectedConversionError (show errno) -- Thin wrapper around iconv_open()-newtype IConvT = IConvT (ForeignPtr IConvT)+data IConvT = IConvT (ForeignPtr IConvT) data IConvOpenError = UnsupportedConversion | UnexpectedOpenError Errno -iconvOpen :: String -> String -> Either IConvOpenError IConvT-iconvOpen inputEncoding outputEncoding = unsafePerformIO $ do+iconvOpen :: CharacterEncoding -> CharacterEncoding -> Either IConvOpenError IConvT+iconvOpen inputEncoding outputEncoding = unsafePerformIO $+ mask_ $ do -- mask async exceptions, we might otherwise leak ptr <- withCString inputEncoding $ \inputEncodingPtr -> withCString outputEncoding $ \outputEncodingPtr -> c_iconv_open outputEncodingPtr inputEncodingPtr@@ -132,46 +173,89 @@ -- Thin wrapper around iconv() data IConvResult = Converted B.ByteString B.ByteString- | MoreData B.ByteString B.ByteString | InvalidInput B.ByteString B.ByteString | UnexpectedError Errno -iconv :: IConvT -> B.ByteString -> IConvResult-iconv (IConvT fPtr) input = unsafePerformIO $- withForeignPtr fPtr $ \ptr ->- BU.unsafeUseAsCStringLen input $ \(inPtr, inLeft) ->- with inPtr $ \inPtrPtr ->- with (fromIntegral inLeft) $ \inLeftPtr ->- let outLeft = max (inLeft + 16) 4096 in -- 16 byte padding just in case- mallocBytes outLeft >>= \outPtr ->- BU.unsafePackCStringLen (outPtr, outLeft) >>= \output -> -- created here already to be garbage collected in case of async exceptions- with outPtr $ \outPtrPtr ->- with (fromIntegral outLeft) $ \outLeftPtr -> do+iconv :: IConvT -> B.ByteString -> B.ByteString -> IConvResult+iconv (IConvT fPtr) input outputPrefix = unsafePerformIO $+ mask_ $ do -- mask async exceptions, we might otherwise leak+ let outputPrefixLen = B.length outputPrefix+ inputLen = B.length input+ -- We allocate enough memory for the worst case: 1+ -- byte per character encodings to 4 bytes per+ -- character encodings (e.g. ASCII to UTF32).+ -- Additionally 16 bytes of padding just in case and+ -- the length of the converted prefix we have to+ -- prepend to the result+ --+ -- We overallocate here but will resize later. The+ -- alternative would be to produce potentially many+ -- smaller chunks of output.+ outputLen = inputLen * 4 + 16 + outputPrefixLen+ convertLen = outputLen - outputPrefixLen - res <- c_iconv ptr inPtrPtr inLeftPtr outPtrPtr outLeftPtr+ -- We allocate via malloc(), which does not give us memory inside+ -- the GC heap. But this allows us to realloc() the memory later+ -- to the actual size.+ outputPtr <- mallocBytes outputLen - inLeft' <- fromIntegral <$> peek inLeftPtr- outLeft' <- fromIntegral <$> peek outLeftPtr- let output' = B.take (outLeft - outLeft') output- remaining = B.drop (inLeft - inLeft') input+ BU.unsafeUseAsCString outputPrefix $ \outputPrefixPtr ->+ copyBytes outputPtr outputPrefixPtr outputPrefixLen - if res /= (-1) then- return $ Converted output' remaining- else do- errno <- getErrno- case () of- _ | errno == e2BIG -> return $- if outLeft == outLeft' then -- we processed nothing and it's still too big?!- UnexpectedError errno- else- MoreData output' remaining- | errno == eINVAL -> return $ Converted output' remaining -- nothing converted here is no error as with future data we might be able to convert- | errno == eILSEQ -> return $- if outLeft == outLeft' then -- we processed nothing- UnexpectedError errno- else- InvalidInput output' remaining- | otherwise -> return $ UnexpectedError errno+ -- Newly converted output should start at this pointer+ let outputConvPtr = plusPtr outputPtr outputPrefixLen++ -- Do the actual conversion and calculate how many bytes we+ -- read and wrote to update our state+ (res, readCount, writeCount) <-+ withForeignPtr fPtr $ \ptr ->+ BU.unsafeUseAsCString input $ \inputPtr ->+ with inputPtr $ \inputPtrPtr ->+ with (fromIntegral inputLen) $ \inputLenPtr ->+ with outputConvPtr $ \outputConvPtrPtr ->+ with (fromIntegral convertLen) $ \convertLenPtr -> do+ res <- c_iconv ptr inputPtrPtr inputLenPtr outputConvPtrPtr convertLenPtr++ -- The length pointers will be updated by iconv to the still+ -- remaining length after conversion. That means we can calculate+ -- the read/written number of bytes with: old - new+ readCount <- (`subtract` inputLen) . fromIntegral <$> peek inputLenPtr+ writeCount <- (`subtract` convertLen) . fromIntegral <$> peek convertLenPtr+ return (res, readCount, writeCount)++ let remaining = B.drop readCount input+ convertedLen = outputPrefixLen + writeCount++ -- Reallocate the memory to a memory area of the actual size. This+ -- potentially allows the OS to reuse any memory we allocated too much and+ -- in general does not cause the memory to be copied.+ --+ -- This will return NULL if the output size is 0, but the resulting+ -- ByteString will then be the empty ByteString as expected.+ --+ -- Shadowing outputPtr to prevent accidential usage of old outputPtr+ outputPtr <- reallocBytes outputPtr convertedLen+ output <- BU.unsafePackMallocCStringLen (outputPtr, convertedLen)++ if res /= (-1) then+ return $ Converted output remaining+ else do+ errno <- getErrno+ case () of+ -- The output buffer was too small! This should not happen+ -- because we overallocate for any possible output charset+ -- encoding+ _ | errno == e2BIG -> return $ UnexpectedError errno++ -- Incomplete byte sequence was detected, which we might be+ -- able to convert with further input later+ | errno == eINVAL -> return $ Converted output remaining++ -- Invalid byte sequence was detected. We might've converted+ -- something already but from here on we can't do anything+ | errno == eILSEQ -> return $ InvalidInput output remaining++ | otherwise -> return $ UnexpectedError errno -- Taken from Codec.Text.IConv
+ benchmarks/Benchmarks.hs view
@@ -0,0 +1,83 @@+module Main where++import Control.Monad.Identity++import qualified Data.ByteString as B++import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++import Criterion.Main++import Data.Conduit+import qualified Data.Conduit.List as CL+import qualified Data.Conduit.IConv as I++loremIpsum, loremIpsumUnicode :: String+-- https://en.wikipedia.org/wiki/Lorem_ipsum+loremIpsum = concat . replicate 128 $ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."+-- http://www.geertvanderploeg.com/unicode-gen/+loremIpsumUnicode = concat . replicate 128 $ "L߀rèϻ ïρѕüm ԁòlòr ѕit ämét, còлsêctêtúr aԁìpïѕcìԉg ëlït, séd Ꮷò éïüsϻ߀d teϻρ߀r ìԉcìԁíᏧûлt ût láƅ߀re ét d߀lòrê ϻåǥnà álïɋùå. Ut ënïϻ aԁ ϻinìϻ veлìäm, qúïѕ n߀ѕtrüԁ èХêrcitɑtíòn ùlɭàϻcò ɭåb߀rïѕ ԉïsí ût àɭìɋûíp eх êà c߀mmòᏧ߀ c߀nѕèɋûát. Düïѕ ãütè irüré d߀lòr ïn reρrèհênᏧérìt iԉ ѵolüptãte ѵëɭit éssê ciɭlûm d߀ɭòrè ëû fugìɑt ԉúlɭã ρɑrìɑtür. Eӽcëρtéur síԉt occɑécät cùρíԁátât лoл proiԁêлt, ѕúԉt ín cûɭpä qui ߀ffïciä dèsërùԉt m߀ɭlït âԉïϻ íԁ eѕt läƅorùϻ."++main :: IO ()+main = defaultMain benchmarks++data SplitMode = None+ | Half+ | Quarter+ | ThreeBytes+ | TwoBytes+ | OneByte++convert :: (String -> B.ByteString) -> I.CharacterEncoding -> I.CharacterEncoding -> SplitMode -> String -> [B.ByteString]+convert encode from to split input = runIdentity $ CL.sourceList preparedInput $$ I.convert from to =$ CL.consume+ where+ encodedInput = encode input+ preparedInput = case split of+ None -> [encodedInput]+ Half -> halfByteString encodedInput+ Quarter -> concatMap halfByteString (halfByteString encodedInput)+ ThreeBytes -> chunksOf 3 encodedInput+ TwoBytes -> chunksOf 2 encodedInput+ OneByte -> chunksOf 1 encodedInput++ halfByteString b = let (s, s') = B.splitAt (B.length b `div` 2) b+ in [s, s']++ chunksOf c b | B.null b = []+ | otherwise = let (s, s') = B.splitAt c b+ in s : chunksOf c s'++benchmarks :: [Benchmark]+benchmarks = [+ bgroup "ascii" [+ bench "32LE-8-N" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" None) loremIpsum+ , bench "32LE-8-H" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" Half) loremIpsum+ , bench "32LE-8-Q" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" Quarter) loremIpsum+ , bench "32LE-8-3" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" ThreeBytes) loremIpsum+ , bench "32LE-8-2" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" TwoBytes) loremIpsum+ , bench "32LE-8-1" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" OneByte) loremIpsum++ , bench "8-32LE-N" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" None) loremIpsum+ , bench "8-32LE-H" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" Half) loremIpsum+ , bench "8-32LE-Q" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" Quarter) loremIpsum+ , bench "8-32LE-3" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" ThreeBytes) loremIpsum+ , bench "8-32LE-2" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" TwoBytes) loremIpsum+ , bench "8-32LE-1" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" OneByte) loremIpsum+ ]+ , bgroup "unicode" [+ bench "32LE-8-N" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" None) loremIpsumUnicode+ , bench "32LE-8-H" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" Half) loremIpsumUnicode+ , bench "32LE-8-Q" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" Quarter) loremIpsumUnicode+ , bench "32LE-8-3" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" ThreeBytes) loremIpsumUnicode+ , bench "32LE-8-2" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" TwoBytes) loremIpsumUnicode+ , bench "32LE-8-1" $ whnf (convert (TE.encodeUtf32LE . T.pack) "UTF-32LE" "UTF-8" OneByte) loremIpsumUnicode++ , bench "8-32LE-N" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" None) loremIpsumUnicode+ , bench "8-32LE-H" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" Half) loremIpsumUnicode+ , bench "8-32LE-Q" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" Quarter) loremIpsumUnicode+ , bench "8-32LE-3" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" ThreeBytes) loremIpsumUnicode+ , bench "8-32LE-2" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" TwoBytes) loremIpsumUnicode+ , bench "8-32LE-1" $ whnf (convert (TE.encodeUtf8 . T.pack) "UTF-8" "UTF-32LE" OneByte) loremIpsumUnicode+ ]+ ]
conduit-iconv.cabal view
@@ -1,17 +1,17 @@-name: conduit-iconv-version: 0.1.0.0-synopsis: Conduit for character encoding conversion.+name: conduit-iconv+version: 0.1.1.0+synopsis: Conduit for character encoding conversion. description:- @conduit-iconv@ provides a "Data.Conduit#t:Conduit" for character encoding+ @conduit-iconv@ provides a Conduit for character encoding conversion, based on the iconv library.-homepage: https://github.com/sdroege/conduit-iconv-license: BSD3-license-file: LICENSE-author: Sebastian Dröge-maintainer: slomo@coaxion.net-category: Conduit, Text-build-type: Simple-cabal-version: >= 1.10+homepage: https://github.com/sdroege/conduit-iconv+license: BSD3+license-file: LICENSE+author: Sebastian Dröge+maintainer: slomo@coaxion.net+category: Conduit, Text+build-type: Simple+cabal-version: >= 1.10 library exposed-modules: Data.Conduit.IConv@@ -29,21 +29,35 @@ extra-libraries: iconv test-suite Tests- type: exitcode-stdio-1.0- x-uses-tf: true- build-depends: base == 4.*- , QuickCheck >= 2.4- , mtl == 2.*- , test-framework >= 0.4.1- , test-framework-quickcheck2- , bytestring- , text- , conduit- , conduit-iconv- ghc-options: -Wall- hs-source-dirs: tests- default-language: Haskell2010- main-is: Tests.hs+ type: exitcode-stdio-1.0+ x-uses-tf: true+ build-depends: base == 4.*+ , QuickCheck >= 2.4+ , mtl == 2.*+ , test-framework >= 0.4.1+ , test-framework-quickcheck2+ , bytestring+ , text+ , conduit+ , conduit-iconv+ ghc-options: -Wall+ hs-source-dirs: tests+ default-language: Haskell2010+ main-is: Tests.hs++benchmark Benchmarks+ type: exitcode-stdio-1.0+ build-depends: base == 4.*+ , mtl == 2.*+ , conduit+ , bytestring+ , text+ , conduit-iconv+ , criterion+ ghc-options: -O2 -Wall+ hs-source-dirs: benchmarks+ default-language: Haskell2010+ main-is: Benchmarks.hs source-repository head type: git
tests/Tests.hs view
@@ -37,68 +37,55 @@ safeMod _ 0 = 0 safeMod m o = abs $ m `mod` abs o -prop_identityASCIIToUTF8 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityASCIIToUTF8 inString n a b c d = input == output- where input = B.map (\w -> if w >= 128 then w - 128 else w) . BC.pack $ inString- cInput = chunkByteString n a b c d input- converted = runIdentity $ CL.sourceList cInput $$ I.convert "ASCII" "UTF-8" =$ CL.consume- output = B.concat converted- prop_identityLatin1ToUTF8 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityLatin1ToUTF8 inString n a b c d = inString == output- where input = chunkByteString n a b c d . BC.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "Latin1" "UTF-8" =$ CL.consume- output = T.unpack . TE.decodeUtf8 . B.concat $ converted+prop_identityLatin1ToUTF8 = prop_identity BC.pack "Latin1" (T.unpack . TE.decodeUtf8) "UTF-8" prop_identityUTF8ToUTF16 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF8ToUTF16 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf8 . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-8" "UTF-16LE" =$ CL.consume- output = T.unpack . TE.decodeUtf16LE . B.concat $ converted+prop_identityUTF8ToUTF16 = prop_identity (TE.encodeUtf8 . T.pack) "UTF-8" (T.unpack . TE.decodeUtf16LE) "UTF-16LE" prop_identityUTF16ToUTF8 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF16ToUTF8 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf16LE . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-16LE" "UTF-8" =$ CL.consume- output = T.unpack . TE.decodeUtf8 . B.concat $ converted+prop_identityUTF16ToUTF8 = prop_identity (TE.encodeUtf16LE . T.pack) "UTF-16LE" (T.unpack . TE.decodeUtf8) "UTF-8" prop_identityUTF8ToUTF32 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF8ToUTF32 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf8 . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-8" "UTF-32LE" =$ CL.consume- output = T.unpack . TE.decodeUtf32LE . B.concat $ converted+prop_identityUTF8ToUTF32 = prop_identity (TE.encodeUtf8 . T.pack) "UTF-8" (T.unpack . TE.decodeUtf32LE) "UTF-32LE" prop_identityUTF16ToUTF32 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF16ToUTF32 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf16LE . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-16LE" "UTF-32LE" =$ CL.consume- output = T.unpack . TE.decodeUtf32LE . B.concat $ converted+prop_identityUTF16ToUTF32 = prop_identity (TE.encodeUtf16LE . T.pack) "UTF-16LE" (T.unpack . TE.decodeUtf32LE) "UTF-32LE" prop_identityUTF32ToUTF16 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF32ToUTF16 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf32LE . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-32LE" "UTF-16LE" =$ CL.consume- output = T.unpack . TE.decodeUtf16LE . B.concat $ converted+prop_identityUTF32ToUTF16 = prop_identity (TE.encodeUtf32LE . T.pack) "UTF-32LE" (T.unpack . TE.decodeUtf16LE) "UTF-16LE" prop_identityUTF32ToUTF8 :: String -> Int -> Int -> Int -> Int -> Int -> Bool-prop_identityUTF32ToUTF8 inString n a b c d = inString == output- where input = chunkByteString n a b c d . TE.encodeUtf32LE . T.pack $ inString- converted = runIdentity $ CL.sourceList input $$ I.convert "UTF-32LE" "UTF-8" =$ CL.consume- output = T.unpack . TE.decodeUtf8 . B.concat $ converted+prop_identityUTF32ToUTF8 = prop_identity (TE.encodeUtf32LE . T.pack) "UTF-32LE" (T.unpack . TE.decodeUtf8) "UTF-8" +prop_identity :: (String -> BC.ByteString)+ -> I.CharacterEncoding+ -> (BC.ByteString -> String)+ -> I.CharacterEncoding+ -> String+ -> Int+ -> Int+ -> Int+ -> Int+ -> Int+ -> Bool+prop_identity encode encodeTo decode decodeTo inString n a b c d = inString == output+ where input = chunkByteString n a b c d . encode $ inString+ converted = runIdentity $ CL.sourceList input $$ I.convert encodeTo decodeTo =$ CL.consume+ output = decode . B.concat $ converted+ main :: IO () main = defaultMain tests tests :: [Test] tests = [ testGroup "QuickCheck Data.Conduit.IConv" [- testProperty "identityASCIIToUTF8" prop_identityASCIIToUTF8- , testProperty "identityLatin1ToUTF8" prop_identityLatin1ToUTF8- , testProperty "identityUTF8ToUTF16" prop_identityUTF8ToUTF16- , testProperty "identityUTF16ToUTF8" prop_identityUTF16ToUTF8- , testProperty "identityUTF8ToUTF32" prop_identityUTF8ToUTF32+ testProperty "identityLatin1ToUTF8" prop_identityLatin1ToUTF8+ , testProperty "identityUTF8ToUTF16" prop_identityUTF8ToUTF16+ , testProperty "identityUTF16ToUTF8" prop_identityUTF16ToUTF8+ , testProperty "identityUTF8ToUTF32" prop_identityUTF8ToUTF32 , testProperty "identityUTF16ToUTF32" prop_identityUTF16ToUTF32 , testProperty "identityUTF32ToUTF16" prop_identityUTF32ToUTF16- , testProperty "identityUTF32ToUTF8" prop_identityUTF32ToUTF8+ , testProperty "identityUTF32ToUTF8" prop_identityUTF32ToUTF8 ] ]