streaming-bytestring 0.1.4.4 → 0.1.4.5
raw patch · 5 files changed
+253/−85 lines, 5 filesdep +smallcheckdep +streaming-bytestringdep +tastydep ~basedep ~bytestringdep ~streaming
Dependencies added: smallcheck, streaming-bytestring, tasty, tasty-smallcheck
Dependency ranges changed: base, bytestring, streaming, transformers
Files
- Data/ByteString/Streaming.hs +44/−35
- Data/ByteString/Streaming/Char8.hs +74/−41
- Data/ByteString/Streaming/Internal.hs +69/−8
- streaming-bytestring.cabal +19/−1
- tests/test.hs +47/−0
Data/ByteString/Streaming.hs view
@@ -84,20 +84,13 @@ , uncons -- uncons :: Monad m => ByteString m r -> m (Either r (Word8, ByteString m r)) , nextByte -- nextByte :: Monad m => ByteString m r -> m (Either r (Word8, ByteString m r)) , denull- - -- * Direct chunk handling - , unconsChunk- , nextChunk -- nextChunk :: Monad m => ByteString m r -> m (Either r (ByteString, ByteString m r)) - , consChunk- , chunk- , foldrChunks- , foldlChunks -- * Substrings -- ** Breaking strings , break -- break :: Monad m => (Word8 -> Bool) -> ByteString m r -> ByteString m (ByteString m r) , drop -- drop :: Monad m => GHC.Int.Int64 -> ByteString m r -> ByteString m r + , dropWhile , group -- group :: Monad m => ByteString m r -> Stream (ByteString m) m r , groupBy , span -- span :: Monad m => (Word8 -> Bool) -> ByteString m r -> ByteString m (ByteString m r) @@ -130,11 +123,13 @@ -- ** Unfolding ByteStrings , unfoldM -- unfoldr :: (a -> m (Maybe (Word8, a))) -> m a -> ByteString m () , unfoldr -- unfold :: (a -> Either r (Word8, a)) -> a -> ByteString m r-+ , reread+ -- * Folds, including support for `Control.Foldl` , foldr -- foldr :: Monad m => (Word8 -> a -> a) -> a -> ByteString m () -> m a , fold -- fold :: Monad m => (x -> Word8 -> x) -> x -> (x -> b) -> ByteString m () -> m b , fold_ -- fold' :: Monad m => (x -> Word8 -> x) -> x -> (x -> b) -> ByteString m r -> m (b, r) + , head , head_ , last@@ -173,6 +168,17 @@ -- * Etc. , zipWithStream -- zipWithStream :: Monad m => (forall x. a -> ByteString m x -> ByteString m x) -> [a] -> Stream (ByteString m) m r -> Stream (ByteString m) m r + -- * Simple chunkwise operations + , unconsChunk+ , nextChunk + , chunk+ , foldrChunks+ , foldlChunks+ , chunkFold+ , chunkFoldM+ , chunkMap+ , chunkMapM+ , chunkMapM_ ) where import Prelude hiding@@ -533,14 +539,14 @@ return r {-# INLINE snoc #-} --- | /O(1)/ Extract the first element of a ByteString, which must be non-empty.+-- | /O(1)/ Extract the first element of a 'ByteString', which must be non-empty. head_ :: Monad m => ByteString m r -> m Word8 head_ (Empty _) = error "head" head_ (Chunk c _) = return $ S.unsafeHead c head_ (Go m) = m >>= head_ {-# INLINE head_ #-} --- | /O(c)/ Extract the first element of a ByteString, which must be non-empty.+-- | /O(c)/ Extract the first element of a 'ByteString', which must be non-empty. head :: Monad m => ByteString m r -> m (Of (Maybe Word8) r) head (Empty r) = return (Nothing :> r) head (Chunk c rest) = case S.uncons c of @@ -551,7 +557,7 @@ head (Go m) = m >>= head {-# INLINE head #-} --- | /O(1)/ Extract the head and tail of a ByteString, or Nothing+-- | /O(1)/ Extract the head and tail of a 'ByteString', or 'Nothing' -- if it is empty uncons :: Monad m => ByteString m r -> m (Maybe (Word8, ByteString m r)) uncons (Empty _) = return Nothing@@ -563,7 +569,7 @@ uncons (Go m) = m >>= uncons {-# INLINABLE uncons #-} ----- | /O(1)/ Extract the head and tail of a ByteString, or its return value+-- | /O(1)/ Extract the head and tail of a 'ByteString', or its return value -- if it is empty. This is the \'natural\' uncons for an effectful byte stream. nextByte :: Monad m => ByteString m r -> m (Either r (Word8, ByteString m r)) nextByte (Empty r) = return (Left r)@@ -593,7 +599,7 @@ Go m -> m >>= nextChunk {-# INLINABLE nextChunk #-} --- | /O(n\/c)/ Extract the last element of a ByteString, which must be finite+-- | /O(n\/c)/ Extract the last element of a 'ByteString', which must be finite -- and non-empty. last_ :: Monad m => ByteString m r -> m Word8 last_ (Empty _) = error "Data.ByteString.Streaming.last: empty string"@@ -690,12 +696,12 @@ {-# INLINABLE intersperse #-} -{- | 'foldr', applied to a binary operator, a starting value+-- | 'foldr', applied to a binary operator, a starting value -- (typically the right-identity of the operator), and a ByteString, -- reduces the ByteString using the binary operator, from right to left.--> foldr cons = id--}+--+-- > foldr cons = id+-- foldr :: Monad m => (Word8 -> a -> a) -> a -> ByteString m () -> m a foldr k = foldrChunks (flip (S.foldr k)) {-# INLINE foldr #-}@@ -715,7 +721,7 @@ {-# INLINABLE fold #-} --- | 'fold\'' keeps the return value of the left-folded bytestring. Useful for+-- | 'fold_' keeps the return value of the left-folded bytestring. Useful for -- simultaneous folds over a segmented bytestream fold_ :: Monad m => (x -> Word8 -> x) -> x -> (x -> b) -> ByteString m r -> m (Of b r)@@ -907,10 +913,10 @@ cycle = forever {-# INLINE cycle #-} --- | /O(n)/ The 'unfoldr' function is analogous to the Stream \'unfoldr\'.+-- | /O(n)/ The 'unfoldr' function is analogous to the Stream @unfoldr@. -- 'unfoldr' builds a ByteString from a seed value. The function takes -- the element and returns 'Nothing' if it is done producing the--- ByteString or returns 'Just' @(a,b)@, in which case, @a@ is a+-- ByteString or returns @'Just' (a,b)@, in which case, @a@ is a -- prepending to the ByteString and @b@ is used as the next element in a -- recursive call. @@ -925,8 +931,8 @@ {-# INLINABLE unfoldM #-} -- | 'unfold' is like 'unfoldr' but stops when the co-algebra --- returns 'Left'; the result is the return value of the 'ByteString m r'--- 'unfoldr uncons = id'+-- returns 'Left'; the result is the return value of the @ByteString m r@+-- @unfoldr uncons = id@ unfoldr :: (a -> Either r (Word8, a)) -> a -> ByteString m r unfoldr f s0 = unfoldChunk 32 s0 where unfoldChunk n s =@@ -1027,14 +1033,17 @@ | otherwise -> Chunk c (takeWhile' cs) {-# INLINABLE takeWhile #-} --- -- | 'dropWhile' @p xs@ returns the suffix remaining after 'takeWhile' @p xs@.--- dropWhile :: (Word8 -> Bool) -> ByteString -> ByteString--- dropWhile f cs0 = dropWhile' cs0--- where dropWhile' Empty = Empty--- dropWhile' (Chunk c cs) =--- case findIndexOrEnd (not . f) c of--- n | n < S.length c -> Chunk (S.drop n c) cs--- | otherwise -> dropWhile' cs+-- | 'dropWhile' @p xs@ returns the suffix remaining after 'takeWhile' @p xs@.+dropWhile :: Monad m => (Word8 -> Bool) -> ByteString m r -> ByteString m r+dropWhile pred = drop' where + drop' bs = case bs of + Empty r -> Empty r+ Go m -> Go (liftM drop' m) + Chunk c cs -> case findIndexOrEnd (not.pred) c of+ 0 -> Chunk c cs+ n | n < S.length c -> Chunk (S.drop n c) cs+ | otherwise -> drop' cs+{-#INLINABLE dropWhile #-} -- | 'break' @p@ is equivalent to @'span' ('not' . p)@. break :: Monad m => (Word8 -> Bool) -> ByteString m r -> ByteString m (ByteString m r)@@ -1419,7 +1428,7 @@ c <- liftIO (S.hGetSome h k) -- only blocks if there is no data available if S.null c- then Go $ liftIO (hClose h) >> return (Empty ())+ then Empty () else Chunk c loop {-#INLINABLE hGetContentsN #-} -- very effective inline pragma @@ -1527,8 +1536,8 @@ return r {-# INLINE writeFile #-} -{-| Read an entire file into a chunked 'ByteString IO ()'.- The Handle will be held open until EOF is encountered.+{-| Read an entire file into a chunked @'ByteString' IO ()@.+ The handle will be held open until EOF is encountered. The block governed by 'Control.Monad.Trans.Resource.runResourceT' will end with the closing of any handles opened. @@ -1753,7 +1762,7 @@ {-#INLINABLE concatBuilders #-} -{-| A simple construction of a builder from a byte stream.+{-| A simple construction of a builder from a 'ByteString'. >>> let aaa = "10000 is a number\n" :: Q.ByteString IO () >>> hPutBuilder IO.stdout $ toBuilder aaa
Data/ByteString/Streaming/Char8.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE CPP, BangPatterns #-}-{-#LANGUAGE RankNTypes, OverloadedStrings #-}+{-#LANGUAGE RankNTypes, OverloadedStrings, ScopedTypeVariables #-} -- This library emulates Data.ByteString.Lazy.Char8 but includes a monadic element -- and thus at certain points uses a `Stream`/`FreeT` type in place of lists. @@ -30,7 +30,6 @@ , mwrap - -- * Transforming ByteStrings , map -- map :: Monad m => (Char -> Char) -> ByteString m r -> ByteString m r , intercalate -- intercalate :: Monad m => ByteString m () -> Stream (ByteString m) m r -> ByteString m r @@ -51,19 +50,12 @@ , uncons -- uncons :: Monad m => ByteString m r -> m (Either r (Char, ByteString m r)) , nextChar - -- * Direct chunk handling- , unconsChunk- , nextChunk -- nextChunk :: Monad m => ByteString m r -> m (Either r (ByteString, ByteString m r)) - , consChunk- , chunk- , foldrChunks- , foldlChunks- -- * Substrings -- ** Breaking strings , break -- break :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m (ByteString m r) , drop -- drop :: Monad m => GHC.Int.Int64 -> ByteString m r -> ByteString m r + , dropWhile , group -- group :: Monad m => ByteString m r -> Stream (ByteString m) m r , groupBy , span -- span :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m (ByteString m r) @@ -77,8 +69,8 @@ , lines , words , denull+ -- ** Special folds- , concat -- concat :: Monad m => Stream (ByteString m) m r -> ByteString m r -- * Builders@@ -98,7 +90,7 @@ -- ** Unfolding ByteStrings , unfoldr -- unfoldr :: (a -> Maybe (Char, a)) -> a -> ByteString m () , unfoldM -- unfold :: (a -> Either r (Char, a)) -> a -> ByteString m r-+ , reread -- * Folds, including support for `Control.Foldl` -- , foldr -- foldr :: Monad m => (Char -> a -> a) -> a -> ByteString m () -> m a , fold -- fold :: Monad m => (x -> Char -> x) -> x -> (x -> b) -> ByteString m () -> m b @@ -135,6 +127,19 @@ , hGetNonBlockingN -- hGetNonBlockingN :: Int -> Handle -> Int -> ByteString IO () , hPut -- hPut :: Handle -> ByteString IO r -> IO r -- , hPutNonBlocking -- hPutNonBlocking :: Handle -> ByteString IO r -> ByteString IO r ++ -- * Simple chunkwise operations + , unconsChunk+ , nextChunk + , chunk+ , foldrChunks+ , foldlChunks+ , chunkFold+ , chunkFoldM+ , chunkMap+ , chunkMapM+ , chunkMapM_+ -- * Etc. -- , zipWithStream -- zipWithStream :: Monad m => (forall x. a -> ByteString m x -> ByteString m x) -> [a] -> Stream (ByteString m) m r -> Stream (ByteString m) m r , distribute -- distribute :: ByteString (t m) a -> t (ByteString m) a @@ -174,7 +179,8 @@ appendFile, stdout, stdin, fromHandle, toHandle, hGetContents, hGetContentsN, hGet, hGetN, hPut, getContents, hGetNonBlocking,- hGetNonBlockingN, readFile, writeFile, interact)+ hGetNonBlockingN, readFile, writeFile, interact,+ chunkFold, chunkFoldM, chunkMap, chunkMapM) -- hPutNonBlocking, import Control.Monad (liftM)@@ -351,12 +357,14 @@ iterate :: (Char -> Char) -> Char -> ByteString m r iterate f c = R.iterate (c2w . f . w2c) (c2w c)+{-#INLINE iterate #-} -- | @'repeat' x@ is an infinite ByteString, with @x@ the value of every -- element. -- repeat :: Char -> ByteString m r repeat = R.repeat . c2w+{-#INLINE repeat #-} -- -- | /O(n)/ @'replicate' n x@ is a ByteString of length @n@ with @x@ -- -- the value of every element.@@ -388,10 +396,12 @@ go a = case f a of Nothing -> Nothing Just (c,a) -> Just (c2w c, a)-+{-#INLINE unfoldM #-}+ unfoldr :: (a -> Either r (Char, a)) -> a -> ByteString m r unfoldr step = R.unfoldr (either Left (\(c,a) -> Right (c2w c,a)) . step) +{-#INLINE unfoldr #-} -- ---------------------------------------------------------------------@@ -402,25 +412,27 @@ -- satisfy @p@. takeWhile :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m () takeWhile f = R.takeWhile (f . w2c)--- -- | 'dropWhile' @p xs@ returns the suffix remaining after 'takeWhile' @p xs@.--- dropWhile :: (Word8 -> Bool) -> ByteString -> ByteString--- dropWhile f cs0 = dropWhile' cs0--- where dropWhile' Empty = Empty--- dropWhile' (Chunk c cs) =--- case findIndexOrEnd (not . f) c of--- n | n < B.length c -> Chunk (B.drop n c) cs--- | otherwise -> dropWhile' cs+{-#INLINE takeWhile #-} +-- | 'dropWhile' @p xs@ returns the suffix remaining after 'takeWhile' @p xs@.++dropWhile :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m r+dropWhile f = R.dropWhile (f . w2c)+{-#INLINE dropWhile #-}+ {- | 'break' @p@ is equivalent to @'span' ('not' . p)@. -} break :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m (ByteString m r) break f = R.break (f . w2c)+{-#INLINE break #-}+ -- -- | 'span' @p xs@ breaks the ByteString into two segments. It is -- equivalent to @('takeWhile' p xs, 'dropWhile' p xs)@ span :: Monad m => (Char -> Bool) -> ByteString m r -> ByteString m (ByteString m r) span p = break (not . p)+{-#INLINE span #-} -- -- | /O(n)/ Splits a 'ByteString' into components delimited by -- -- separators, where the predicate returns True for a separator element.@@ -483,29 +495,50 @@ This is the genuinely streaming 'lines' which only breaks chunks, and thus never increases the use of memory. -> lines :: Monad m => ByteString m r -> Stream (ByteString m) m r+ Because 'ByteString's are usually read in binary mode, with no line+ ending conversion, this function recognizes both @\\n@ and @\\r\\n@+ endings (regardless of the current platform). -} -lines :: Monad m => ByteString m r -> Stream (ByteString m) m r--- lines = loop--- where--- loop !x = case x of--- Empty r -> Return r--- Go m -> Effect $ liftM loop m--- Chunk c0 cs0 -> comb [] (B.split 10 c0) cs0-lines (Empty r) = Return r-lines (Go m) = Effect $ liftM lines m-lines (Chunk c0 cs0) = comb [] (B.split 10 c0) cs0 where- comb !acc [] (Empty r) = Step (revChunks acc (Return r))- comb acc [] (Chunk c cs) = comb acc (B.split 10 c) cs- comb acc (s:[]) (Empty r) = Step (revChunks (s:acc) (Return r))- comb acc (s:[]) (Chunk c cs) = comb (s:acc) (B.split 10 c) cs- comb acc b (Go m) = Effect (liftM (comb acc b) m)- comb acc (s:ss) cs = Step (revChunks (s:acc) (comb [] ss cs))- revChunks cs r = L.foldl' (flip Chunk) (Empty r) cs+lines :: forall m r . Monad m => ByteString m r -> Stream (ByteString m) m r+lines text0 = loop1 text0+ where+ loop1 :: ByteString m r -> Stream (ByteString m) m r+ loop1 text =+ case text of+ Empty r -> Return r+ Go m -> Effect $ liftM loop1 m+ Chunk c cs+ | B.null c -> loop1 cs+ | otherwise -> Step (loop2 text)+ loop2 :: ByteString m r -> ByteString m (Stream (ByteString m) m r)+ loop2 text =+ case text of+ Empty r -> Empty (Return r)+ Go m -> Go $ liftM loop2 m+ Chunk c cs ->+ case B.elemIndex 10 c of+ Nothing -> Chunk c (loop2 cs)+ Just i ->+ let prefixLength =+ if i >= 1 && B.unsafeIndex c (i-1) == 13 -- \r\n (dos)+ then i-1+ else i+ rest =+ if B.length c > i+1+ then Chunk (B.drop (i+1) c) cs+ else cs+ in Chunk (B.unsafeTake prefixLength c) (Empty (loop1 rest)) {-#INLINABLE lines #-} --- | The 'unlines' function restores line breaks between layers +-- | The 'unlines' function restores line breaks between layers.+--+-- Note that this is not a perfect inverse of 'lines':+--+-- * @'lines' . 'unlines'@ can produce more strings than there were if some of+-- the \"lines\" had embedded newlines.+--+-- * @'unlines' . 'lines'@ will replace @\\r\\n@ with @\\n@. unlines :: Monad m => Stream (ByteString m) m r -> ByteString m r unlines = loop where loop str = case str of
Data/ByteString/Streaming/Internal.hs view
@@ -14,6 +14,11 @@ , foldrChunksM -- :: Monad m => (ByteString -> m a -> m a) -> m a -> ByteString m r -> m a , foldlChunksM -- :: Monad m => (ByteString -> m a -> m a) -> m a -> ByteString m r -> m a+ , chunkFold+ , chunkFoldM+ , chunkMap+ , chunkMapM+ , chunkMapM_ , unfoldMChunks , unfoldrChunks @@ -88,8 +93,12 @@ instance Monad m => Applicative (ByteString m) where pure = Empty- (<*>) = ap-+ {-#INLINE pure #-} + bf <*> bx = do {f <- bf; x <- bx; Empty (f x)}+ {-#INLINE (<*>) #-} + (*>) = (>>)+ {-#INLINE (*>) #-}+ instance Monad m => Monad (ByteString m) where return = Empty {-#INLINE return #-}@@ -121,9 +130,9 @@ instance MFunctor ByteString where hoist phi bs = case bs of- Empty r -> Empty r+ Empty r -> Empty r Chunk bs' rest -> Chunk bs' (hoist phi rest)- Go m -> Go (phi (liftM (hoist phi) m))+ Go m -> Go (phi (liftM (hoist phi) m)) {-#INLINABLE hoist #-} instance (r ~ ()) => IsString (ByteString m r) where@@ -131,10 +140,10 @@ {-#INLINE fromString #-} instance (m ~ Identity, Show r) => Show (ByteString m r) where- show bs0 = case bs0 of- Empty r -> "Empty (" ++ show r ++ ")"+ show bs0 = case bs0 of -- the implementation this instance deserves ...+ Empty r -> "Empty (" ++ show r ++ ")" Go (Identity bs') -> "Go (Identity (" ++ show bs' ++ "))"- Chunk bs'' bs -> "Chunk " ++ show bs'' ++ " (" ++ show bs ++ ")"+ Chunk bs'' bs -> "Chunk " ++ show bs'' ++ " (" ++ show bs ++ ")" instance (Monoid r, Monad m) => Monoid (ByteString m r) where mempty = Empty mempty@@ -150,7 +159,6 @@ throwM = lift . throwM {-#INLINE throwM #-} - instance (MonadCatch m) => MonadCatch (ByteString m) where catch str f = go str where@@ -347,6 +355,46 @@ go a (Go m) = m >>= go a {-# INLINABLE foldlChunks #-} +chunkMap :: Monad m => (S.ByteString -> S.ByteString) -> ByteString m r -> ByteString m r +chunkMap f bs = dematerialize bs return (\bs bss -> Chunk (f bs) bss) Go+{-#INLINE chunkMap #-}++chunkMapM :: Monad m => (S.ByteString -> m S.ByteString) -> ByteString m r -> ByteString m r +chunkMapM f bs = dematerialize bs return (\bs bss -> Go (liftM (flip Chunk bss) (f bs))) Go+{-#INLINE chunkMapM #-}++chunkMapM_ :: Monad m => (S.ByteString -> m x) -> ByteString m r -> m r +chunkMapM_ f bs = dematerialize bs return (\bs mr -> f bs >> mr) join+{-#INLINE chunkMapM_ #-}+++{- | @chunkFold@ is preferable to @foldlChunks@ since it is + an appropriate argument for @Control.Foldl.purely@ which + permits many folds and sinks to be run simulaneously on one bytestream.++ -}+chunkFold :: Monad m => (x -> S.ByteString -> x) -> x -> (x -> a) -> ByteString m r -> m (Of a r)+chunkFold step begin done = go begin+ where go a _ | a `seq` False = undefined+ go a (Empty r) = return (done a :> r)+ go a (Chunk c cs) = go (step a c) cs+ go a (Go m) = m >>= go a+{-# INLINABLE chunkFold #-}++{- | @chunkFoldM@ is preferable to @foldlChunksM@ since it is + an appropriate argument for @Control.Foldl.impurely@ which+ permits many folds and sinks to be run simulaneously on one bytestream.++ -}+chunkFoldM :: Monad m => (x -> S.ByteString -> m x) -> m x -> (x -> m a) -> ByteString m r -> m (Of a r)+chunkFoldM step begin done bs = begin >>= go bs+ where + go str !x = case str of + Empty r -> done x >>= \a -> return (a :> r)+ Chunk c cs -> step x c >>= go cs+ Go m -> m >>= \str' -> go str' x+{-# INLINABLE chunkFoldM #-}+ foldlChunksM :: Monad m => (a -> S.ByteString -> m a) -> m a -> ByteString m r -> m (Of a r) foldlChunksM f z bs = z >>= \a -> go a bs where @@ -356,6 +404,8 @@ Go m -> m >>= go a {-# INLINABLE foldlChunksM #-} ++ -- | Consume the chunks of an effectful ByteString with a natural right monadic fold. foldrChunksM :: Monad m => (S.ByteString -> m a -> m a) -> m a -> ByteString m r -> m a foldrChunksM step nil bs = dematerialize bs@@ -397,7 +447,18 @@ {-# INLINABLE unfoldrChunks #-} +{-| Stream chunks from something that contains @IO (Maybe ByteString)@+ until it returns @Nothing@. @reread@ is of particular use rendering @io-streams@+ input streams as byte streams in the present sense +> Q.reread Streams.read :: InputStream S.ByteString -> Q.ByteString IO ()+> Q.reread (liftIO . Streams.read) :: MonadIO m => InputStream S.ByteString -> Q.ByteString m ()++The other direction here is ++> Streams.unfoldM Q.unconsChunk :: Q.ByteString IO r -> IO (InputStream S.ByteString)++ -} reread :: Monad m => (s -> m (Maybe S.ByteString)) -> s -> ByteString m () reread step s = loop where loop = Go $ do
streaming-bytestring.cabal view
@@ -1,5 +1,5 @@ name: streaming-bytestring-version: 0.1.4.4+version: 0.1.4.5 synopsis: effectful byte steams, or: bytestring io done right. description: This is an implementation of effectful, memory-constrained @@ -199,3 +199,21 @@ default-language: Haskell2010 ghc-options: -O2 +test-suite test+ default-language:+ Haskell2010+ type:+ exitcode-stdio-1.0+ hs-source-dirs:+ tests+ main-is:+ test.hs+ build-depends:+ base >= 4 && < 5+ , transformers+ , tasty >= 0.11.0.4+ , tasty-smallcheck >= 0.8.1+ , smallcheck >= 1.1.1+ , streaming+ , streaming-bytestring+ , bytestring
+ tests/test.hs view
@@ -0,0 +1,47 @@+import Test.Tasty+import Test.Tasty.SmallCheck+import Test.SmallCheck.Series++import Control.Applicative+import Data.Functor.Identity+import qualified Data.ByteString.Char8 as BS8+import qualified Data.ByteString.Streaming.Char8 as SBS8+import Text.Printf+import qualified Streaming.Prelude as S++listOf :: Monad m => Series m a -> Series m [a]+listOf a = decDepth $+ pure [] \/ ((:) <$> a <~> listOf a)++strSeries :: Monad m => Series m String+strSeries = listOf (generate $ const ['a', 'b', '\n'])++chunksSeries :: Monad m => Series m [String]+chunksSeries = listOf strSeries++fromChunks :: [String] -> SBS8.ByteString Identity ()+fromChunks = SBS8.fromChunks . S.each . map BS8.pack++unix2dos :: String -> String+unix2dos = concatMap $ \c -> if c == '\n' then "\r\n" else [c]++s_lines :: SBS8.ByteString Identity () -> [BS8.ByteString]+s_lines+ = runIdentity+ . S.toList_+ . S.mapped SBS8.toStrict+ . SBS8.lines++main = defaultMain $ testGroup "Tests"+ [ testGroup "lines" $+ [ testProperty "Data.ByteString.Streaming.Char8.lines is equivalent to Prelude.lines" $ over chunksSeries $ \chunks ->+ let expected = lines $ concat chunks+ got = (map BS8.unpack . s_lines . fromChunks) chunks+ in+ if expected == got+ then Right ""+ else Left (printf "Expected %s; got %s" (show expected) (show got) :: String)+ , testProperty "lines recognizes DOS line endings" $ over strSeries $ \str ->+ s_lines (SBS8.string $ unix2dos str) == s_lines (SBS8.string str)+ ]+ ]