1 {-# LANGUAGE RankNTypes, TypeFamilies, BangPatterns, CPP #-}
2 #if __GLASGOW_HASKELL__ >= 702
3 {-# LANGUAGE Trustworthy #-}
5 {-| This module provides @pipes@ utilities for \"text streams\", which are
6 streams of 'Text' chunks. The individual chunks are uniformly @strict@, but
7 a 'Producer' can be converted to and from lazy 'Text's, though this is generally
8 unwise. Where pipes IO replaces lazy IO, 'Producer Text m r' replaces lazy 'Text'.
9 An 'IO.Handle' can be associated with a 'Producer' or 'Consumer' according as it is read or written to.
11 To stream to or from 'IO.Handle's, one can use 'fromHandle' or 'toHandle'. For
12 example, the following program copies a document from one file to another:
15 > import qualified Data.Text.Pipes as Text
19 > withFile "inFile.txt" ReadMode $ \hIn ->
20 > withFile "outFile.txt" WriteMode $ \hOut ->
21 > runEffect $ Text.fromHandle hIn >-> Text.toHandle hOut
23 To stream from files, the following is perhaps more Prelude-like (note that it uses Pipes.Safe):
26 > import qualified Data.Text.Pipes as Text
29 > main = runSafeT $ runEffect $ Text.readFile "inFile.txt" >-> Text.writeFile "outFile.txt"
31 You can stream to and from 'stdin' and 'stdout' using the predefined 'stdin'
32 and 'stdout' proxies, as with the following \"echo\" program:
34 > main = runEffect $ Text.stdin >-> Text.stdout
36 You can also translate pure lazy 'TL.Text's to and from proxies:
38 > main = runEffect $ Text.fromLazy (TL.pack "Hello, world!\n") >-> Text.stdout
40 In addition, this module provides many functions equivalent to lazy
41 'Text' functions so that you can transform or fold text streams. For
42 example, to stream only the first three lines of 'stdin' to 'stdout' you
46 > import qualified Pipes.Text as Text
47 > import qualified Pipes.Parse as Parse
49 > main = runEffect $ takeLines 3 Text.stdin >-> Text.stdout
51 > takeLines n = Text.unlines . Parse.takeFree n . Text.lines
53 The above program will never bring more than one chunk of text (~ 32 KB) into
54 memory, no matter how long the lines are.
56 Note that functions in this library are designed to operate on streams that
57 are insensitive to text boundaries. This means that they may freely split
58 text into smaller texts, /discard empty texts/. However, apart from the
59 special case of 'concatMap', they will /never concatenate texts/ in order
60 to provide strict upper bounds on memory usage -- with the single exception of 'concatMap'.
108 -- * Primitive Character Parsers
136 -- * Other Decoding/Encoding Functions
162 , module Data.ByteString
164 , module Data.Profunctor
168 , module Pipes.Text.Internal
171 import Control.Exception (throwIO, try)
172 import Control.Applicative ((<*))
173 import Control.Monad (liftM, unless, join)
174 import Control.Monad.Trans.State.Strict (StateT(..), modify)
175 import Data.Monoid ((<>))
176 import qualified Data.Text as T
177 import qualified Data.Text.IO as T
178 import qualified Data.Text.Encoding as TE
179 import qualified Data.Text.Encoding.Error as TE
180 import Data.Text (Text)
181 import qualified Data.Text.Lazy as TL
182 import qualified Data.Text.Lazy.IO as TL
183 import Data.Text.Lazy.Internal (foldrChunks, defaultChunkSize)
184 import Data.ByteString.Unsafe (unsafeTake, unsafeDrop)
185 import Data.ByteString (ByteString)
186 import qualified Data.ByteString as B
187 import qualified Data.ByteString.Char8 as B8
188 import Data.Char (ord, isSpace)
189 import Data.Functor.Constant (Constant(Constant, getConstant))
190 import Data.Functor.Identity (Identity)
191 import Data.Profunctor (Profunctor)
192 import qualified Data.Profunctor
193 import qualified Data.List as List
194 import Foreign.C.Error (Errno(Errno), ePIPE)
195 import qualified GHC.IO.Exception as G
197 import qualified Pipes.ByteString as PB
198 import qualified Pipes.Text.Internal as PI
199 import Pipes.Text.Internal
200 import Pipes.Core (respond, Server')
201 import Pipes.Group (concats, intercalates, FreeT(..), FreeF(..))
202 import qualified Pipes.Group as PG
203 import qualified Pipes.Parse as PP
204 import Pipes.Parse (Parser)
205 import qualified Pipes.Safe.Prelude as Safe
206 import qualified Pipes.Safe as Safe
207 import Pipes.Safe (MonadSafe(..), Base(..))
208 import qualified Pipes.Prelude as P
209 import qualified System.IO as IO
210 import Data.Char (isSpace)
211 import Data.Word (Word8)
213 import Prelude hiding (
242 -- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's
243 fromLazy :: (Monad m) => TL.Text -> Producer' Text m ()
244 fromLazy = foldrChunks (\e a -> yield e >> a) (return ())
245 {-# INLINE fromLazy #-}
247 -- | Stream text from 'stdin'
248 stdin :: MonadIO m => Producer Text m ()
249 stdin = fromHandle IO.stdin
252 {-| Convert a 'IO.Handle' into a text stream using a text size
253 determined by the good sense of the text library; note that this
254 is distinctly slower than @decideUtf8 (Pipes.ByteString.fromHandle h)@
255 but uses the system encoding and has other `Data.Text.IO` features
258 fromHandle :: MonadIO m => IO.Handle -> Producer Text m ()
259 fromHandle h = go where
260 go = do txt <- liftIO (T.hGetChunk h)
261 unless (T.null txt) ( do yield txt
263 {-# INLINABLE fromHandle#-}
266 {-| Stream text from a file in the simple fashion of @Data.Text.IO@
268 >>> runSafeT $ runEffect $ Text.readFile "hello.hs" >-> Text.map toUpper >-> hoist lift Text.stdout
269 MAIN = PUTSTRLN "HELLO WORLD"
272 readFile :: MonadSafe m => FilePath -> Producer Text m ()
273 readFile file = Safe.withFile file IO.ReadMode fromHandle
274 {-# INLINE readFile #-}
277 {-| Stream text to 'stdout'
279 Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
281 Note: For best performance, it might be best just to use @(for source (liftIO . putStr))@
282 instead of @(source >-> stdout)@ .
284 stdout :: MonadIO m => Consumer' Text m ()
289 x <- liftIO $ try (T.putStr txt)
291 Left (G.IOError { G.ioe_type = G.ResourceVanished
292 , G.ioe_errno = Just ioe })
295 Left e -> liftIO (throwIO e)
297 {-# INLINABLE stdout #-}
300 {-| Convert a text stream into a 'Handle'
302 Note: again, for best performance, where possible use
303 @(for source (liftIO . hPutStr handle))@ instead of @(source >-> toHandle handle)@.
305 toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r
306 toHandle h = for cat (liftIO . T.hPutStr h)
307 {-# INLINABLE toHandle #-}
309 {-# RULES "p >-> toHandle h" forall p h .
310 p >-> toHandle h = for p (\txt -> liftIO (T.hPutStr h txt))
314 -- | Stream text into a file. Uses @pipes-safe@.
315 writeFile :: (MonadSafe m) => FilePath -> Consumer' Text m ()
316 writeFile file = Safe.withFile file IO.WriteMode toHandle
317 {-# INLINE writeFile #-}
320 type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)
322 type Iso' a b = forall f p . (Functor f, Profunctor p) => p b (f b) -> p a (f a)
324 (^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
325 a ^. lens = getConstant (lens Constant a)
328 -- | Apply a transformation to each 'Char' in the stream
329 map :: (Monad m) => (Char -> Char) -> Pipe Text Text m r
330 map f = P.map (T.map f)
331 {-# INLINABLE map #-}
333 {-# RULES "p >-> map f" forall p f .
334 p >-> map f = for p (\txt -> yield (T.map f txt))
337 -- | Map a function over the characters of a text stream and concatenate the results
339 :: (Monad m) => (Char -> Text) -> Pipe Text Text m r
340 concatMap f = P.map (T.concatMap f)
341 {-# INLINABLE concatMap #-}
343 {-# RULES "p >-> concatMap f" forall p f .
344 p >-> concatMap f = for p (\txt -> yield (T.concatMap f txt))
347 -- | Transform a Pipe of 'Text' into a Pipe of 'ByteString's using UTF-8
348 -- encoding; @encodeUtf8 = Pipes.Prelude.map TE.encodeUtf8@ so more complex
349 -- encoding pipes can easily be constructed with the functions in @Data.Text.Encoding@
350 encodeUtf8 :: Monad m => Pipe Text ByteString m r
351 encodeUtf8 = P.map TE.encodeUtf8
352 {-# INLINEABLE encodeUtf8 #-}
354 {-# RULES "p >-> encodeUtf8" forall p .
355 p >-> encodeUtf8 = for p (\txt -> yield (TE.encodeUtf8 txt))
358 -- | Transform a Pipe of 'String's into one of 'Text' chunks
359 pack :: Monad m => Pipe String Text m r
361 {-# INLINEABLE pack #-}
363 {-# RULES "p >-> pack" forall p .
364 p >-> pack = for p (\txt -> yield (T.pack txt))
367 -- | Transform a Pipes of 'Text' chunks into one of 'String's
368 unpack :: Monad m => Pipe Text String m r
369 unpack = for cat (\t -> yield (T.unpack t))
370 {-# INLINEABLE unpack #-}
372 {-# RULES "p >-> unpack" forall p .
373 p >-> unpack = for p (\txt -> yield (T.unpack txt))
376 -- | @toCaseFold@, @toLower@, @toUpper@ and @stripStart@ are standard 'Text' utilities,
377 -- here acting as 'Text' pipes, rather as they would on a lazy text
378 toCaseFold :: Monad m => Pipe Text Text m ()
379 toCaseFold = P.map T.toCaseFold
380 {-# INLINEABLE toCaseFold #-}
382 {-# RULES "p >-> toCaseFold" forall p .
383 p >-> toCaseFold = for p (\txt -> yield (T.toCaseFold txt))
387 -- | lowercase incoming 'Text'
388 toLower :: Monad m => Pipe Text Text m ()
389 toLower = P.map T.toLower
390 {-# INLINEABLE toLower #-}
392 {-# RULES "p >-> toLower" forall p .
393 p >-> toLower = for p (\txt -> yield (T.toLower txt))
396 -- | uppercase incoming 'Text'
397 toUpper :: Monad m => Pipe Text Text m ()
398 toUpper = P.map T.toUpper
399 {-# INLINEABLE toUpper #-}
401 {-# RULES "p >-> toUpper" forall p .
402 p >-> toUpper = for p (\txt -> yield (T.toUpper txt))
405 -- | Remove leading white space from an incoming succession of 'Text's
406 stripStart :: Monad m => Pipe Text Text m r
409 let text = T.stripStart chunk
414 {-# INLINEABLE stripStart #-}
416 -- | @(take n)@ only allows @n@ individual characters to pass;
417 -- contrast @Pipes.Prelude.take@ which would let @n@ chunks pass.
418 take :: (Monad m, Integral a) => a -> Pipe Text Text m ()
419 take n0 = go n0 where
424 let len = fromIntegral (T.length txt)
426 then yield (T.take (fromIntegral n) txt)
430 {-# INLINABLE take #-}
432 -- | @(drop n)@ drops the first @n@ characters
433 drop :: (Monad m, Integral a) => a -> Pipe Text Text m r
434 drop n0 = go n0 where
439 let len = fromIntegral (T.length txt)
442 yield (T.drop (fromIntegral n) txt)
445 {-# INLINABLE drop #-}
447 -- | Take characters until they fail the predicate
448 takeWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m ()
449 takeWhile predicate = go
453 let (prefix, suffix) = T.span predicate txt
459 {-# INLINABLE takeWhile #-}
461 -- | Drop characters until they fail the predicate
462 dropWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
463 dropWhile predicate = go where
466 case T.findIndex (not . predicate) txt of
471 {-# INLINABLE dropWhile #-}
473 -- | Only allows 'Char's to pass if they satisfy the predicate
474 filter :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
475 filter predicate = P.map (T.filter predicate)
476 {-# INLINABLE filter #-}
478 {-# RULES "p >-> filter q" forall p q .
479 p >-> filter q = for p (\txt -> yield (T.filter q txt))
482 -- | Strict left scan over the characters
485 => (Char -> Char -> Char) -> Char -> Pipe Text Text m r
486 scan step begin = go begin
490 let txt' = T.scanl step c txt
494 {-# INLINABLE scan #-}
496 {-| Fold a pure 'Producer' of strict 'Text's into a lazy
499 toLazy :: Producer Text Identity () -> TL.Text
500 toLazy = TL.fromChunks . P.toList
501 {-# INLINABLE toLazy #-}
503 {-| Fold an effectful 'Producer' of strict 'Text's into a lazy
506 Note: 'toLazyM' is not an idiomatic use of @pipes@, but I provide it for
507 simple testing purposes. Idiomatic @pipes@ style consumes the chunks
508 immediately as they are generated instead of loading them all into memory.
510 toLazyM :: (Monad m) => Producer Text m () -> m TL.Text
511 toLazyM = liftM TL.fromChunks . P.toListM
512 {-# INLINABLE toLazyM #-}
514 -- | Reduce the text stream using a strict left fold over characters
517 => (x -> Char -> x) -> x -> (x -> r) -> Producer Text m () -> m r
518 foldChars step begin done = P.fold (T.foldl' step) begin done
519 {-# INLINABLE foldChars #-}
521 -- | Retrieve the first 'Char'
522 head :: (Monad m) => Producer Text m () -> m (Maybe Char)
528 Left _ -> return Nothing
529 Right (c, _) -> return (Just c)
530 {-# INLINABLE head #-}
532 -- | Retrieve the last 'Char'
533 last :: (Monad m) => Producer Text m () -> m (Maybe Char)
543 else go (Just $ T.last txt) p'
544 {-# INLINABLE last #-}
546 -- | Determine if the stream is empty
547 null :: (Monad m) => Producer Text m () -> m Bool
549 {-# INLINABLE null #-}
551 -- | Count the number of characters in the stream
552 length :: (Monad m, Num n) => Producer Text m () -> m n
553 length = P.fold (\n txt -> n + fromIntegral (T.length txt)) 0 id
554 {-# INLINABLE length #-}
556 -- | Fold that returns whether 'M.Any' received 'Char's satisfy the predicate
557 any :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
558 any predicate = P.any (T.any predicate)
559 {-# INLINABLE any #-}
561 -- | Fold that returns whether 'M.All' received 'Char's satisfy the predicate
562 all :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
563 all predicate = P.all (T.all predicate)
564 {-# INLINABLE all #-}
566 -- | Return the maximum 'Char' within a text stream
567 maximum :: (Monad m) => Producer Text m () -> m (Maybe Char)
568 maximum = P.fold step Nothing id
573 else Just $ case mc of
574 Nothing -> T.maximum txt
575 Just c -> max c (T.maximum txt)
576 {-# INLINABLE maximum #-}
578 -- | Return the minimum 'Char' within a text stream (surely very useful!)
579 minimum :: (Monad m) => Producer Text m () -> m (Maybe Char)
580 minimum = P.fold step Nothing id
586 Nothing -> Just (T.minimum txt)
587 Just c -> Just (min c (T.minimum txt))
588 {-# INLINABLE minimum #-}
591 -- | Find the first element in the stream that matches the predicate
594 => (Char -> Bool) -> Producer Text m () -> m (Maybe Char)
595 find predicate p = head (p >-> filter predicate)
596 {-# INLINABLE find #-}
598 -- | Index into a text stream
600 :: (Monad m, Integral a)
601 => a-> Producer Text m () -> m (Maybe Char)
602 index n p = head (p >-> drop n)
603 {-# INLINABLE index #-}
606 -- | Store a tally of how many segments match the given 'Text'
607 count :: (Monad m, Num n) => Text -> Producer Text m () -> m n
608 count c p = P.fold (+) 0 id (p >-> P.map (fromIntegral . T.count c))
609 {-# INLINABLE count #-}
612 {-| Consume the first character from a stream of 'Text'
614 'next' either fails with a 'Left' if the 'Producer' has no more characters or
615 succeeds with a 'Right' providing the next character and the remainder of the
621 -> m (Either r (Char, Producer Text m r))
627 Left r -> return (Left r)
628 Right (txt, p') -> case (T.uncons txt) of
630 Just (c, txt') -> return (Right (c, yield txt' >> p'))
631 {-# INLINABLE nextChar #-}
633 {-| Draw one 'Char' from a stream of 'Text', returning 'Left' if the
636 drawChar :: (Monad m) => Parser Text m (Maybe Char)
640 Nothing -> return Nothing
641 Just txt -> case (T.uncons txt) of
646 {-# INLINABLE drawChar #-}
648 -- | Push back a 'Char' onto the underlying 'Producer'
649 unDrawChar :: (Monad m) => Char -> Parser Text m ()
650 unDrawChar c = modify (yield (T.singleton c) >>)
651 {-# INLINABLE unDrawChar #-}
653 {-| 'peekChar' checks the first 'Char' in the stream, but uses 'unDrawChar' to
659 > Left _ -> return ()
660 > Right c -> unDrawChar c
663 peekChar :: (Monad m) => Parser Text m (Maybe Char)
668 Just c -> unDrawChar c
670 {-# INLINABLE peekChar #-}
672 {-| Check if the underlying 'Producer' has no more characters
674 Note that this will skip over empty 'Text' chunks, unlike
675 'PP.isEndOfInput' from @pipes-parse@, which would consider
676 an empty 'Text' a valid bit of input.
678 > isEndOfChars = liftM isLeft peekChar
680 isEndOfChars :: (Monad m) => Parser Text m Bool
686 {-# INLINABLE isEndOfChars #-}
689 {- | An improper lens into a stream of 'ByteString' expected to be UTF-8 encoded; the associated
690 stream of Text ends by returning a stream of ByteStrings beginning at the point of failure.
693 decodeUtf8 :: Monad m => Lens' (Producer ByteString m r)
694 (Producer Text m (Producer ByteString m r))
695 decodeUtf8 k p0 = fmap (\p -> join (for p (yield . TE.encodeUtf8)))
696 (k (go B.empty PI.streamDecodeUtf8 p0)) where
697 go !carry dec0 p = do
699 case x of Left r -> return (if B.null carry
700 then return r -- all bytestring input was consumed
701 else (do yield carry -- a potentially valid fragment remains
704 Right (chunk, p') -> case dec0 chunk of
705 PI.Some text carry2 dec -> do yield text
707 PI.Other text bs -> do yield text
708 return (do yield bs -- an invalid blob remains
710 {-# INLINABLE decodeUtf8 #-}
713 -- | Splits a 'Producer' after the given number of characters
715 :: (Monad m, Integral n)
717 -> Lens' (Producer Text m r)
718 (Producer Text m (Producer Text m r))
719 splitAt n0 k p0 = fmap join (k (go n0 p0))
725 Left r -> return (return r)
726 Right (txt, p') -> do
727 let len = fromIntegral (T.length txt)
733 let (prefix, suffix) = T.splitAt (fromIntegral n) txt
735 return (yield suffix >> p')
736 {-# INLINABLE splitAt #-}
739 {-| Split a text stream in two, where the first text stream is the longest
740 consecutive group of text that satisfy the predicate
745 -> Lens' (Producer Text m r)
746 (Producer Text m (Producer Text m r))
747 span predicate k p0 = fmap join (k (go p0))
752 Left r -> return (return r)
753 Right (txt, p') -> do
754 let (prefix, suffix) = T.span predicate txt
761 return (yield suffix >> p')
762 {-# INLINABLE span #-}
764 {-| Split a text stream in two, where the first text stream is the longest
765 consecutive group of characters that don't satisfy the predicate
770 -> Lens' (Producer Text m r)
771 (Producer Text m (Producer Text m r))
772 break predicate = span (not . predicate)
773 {-# INLINABLE break #-}
775 {-| Improper lens that splits after the first group of equivalent Chars, as
776 defined by the given equivalence relation
780 => (Char -> Char -> Bool)
781 -> Lens' (Producer Text m r)
782 (Producer Text m (Producer Text m r))
783 groupBy equals k p0 = fmap join (k ((go p0))) where
787 Left r -> return (return r)
788 Right (txt, p') -> case T.uncons txt of
790 Just (c, _) -> (yield txt >> p') ^. span (equals c)
791 {-# INLINABLE groupBy #-}
793 -- | Improper lens that splits after the first succession of identical 'Char' s
795 => Lens' (Producer Text m r)
796 (Producer Text m (Producer Text m r))
798 {-# INLINABLE group #-}
800 {-| Improper lens that splits a 'Producer' after the first word
802 Unlike 'words', this does not drop leading whitespace
805 => Lens' (Producer Text m r)
806 (Producer Text m (Producer Text m r))
807 word k p0 = fmap join (k (to p0))
810 p' <- p^.span isSpace
812 {-# INLINABLE word #-}
816 => Lens' (Producer Text m r)
817 (Producer Text m (Producer Text m r))
818 line = break (== '\n')
820 {-# INLINABLE line #-}
823 -- | Intersperse a 'Char' in between the characters of stream of 'Text'
825 :: (Monad m) => Char -> Producer Text m r -> Producer Text m r
832 Right (txt, p') -> do
833 yield (T.intersperse c txt)
839 Right (txt, p') -> do
840 yield (T.singleton c)
841 yield (T.intersperse c txt)
843 {-# INLINABLE intersperse #-}
847 -- | Improper isomorphism between a 'Producer' of 'ByteString's and 'Word8's
848 packChars :: Monad m => Iso' (Producer Char m x) (Producer Text m x)
849 packChars = Data.Profunctor.dimap to (fmap from)
851 -- to :: Monad m => Producer Char m x -> Producer Text m x
852 to p = PG.folds step id done (p^.PG.chunksOf defaultChunkSize)
854 step diffAs c = diffAs . (c:)
856 done diffAs = T.pack (diffAs [])
858 -- from :: Monad m => Producer Text m x -> Producer Char m x
859 from p = for p (each . T.unpack)
860 {-# INLINABLE packChars #-}
863 -- | Split a text stream into 'FreeT'-delimited text streams of fixed size
865 :: (Monad m, Integral n)
866 => n -> Lens' (Producer Text m r)
867 (FreeT (Producer Text m) m r)
868 chunksOf n k p0 = fmap concats (k (FreeT (go p0)))
874 Right (txt, p') -> Free $ do
875 p'' <- (yield txt >> p') ^. splitAt n
876 return $ FreeT (go p'')
877 {-# INLINABLE chunksOf #-}
880 {-| Split a text stream into sub-streams delimited by characters that satisfy the
887 -> FreeT (Producer Text m) m r
888 splitsWith predicate p0 = FreeT (go0 p0)
893 Left r -> return (Pure r)
897 else return $ Free $ do
898 p'' <- (yield txt >> p') ^. span (not . predicate)
899 return $ FreeT (go1 p'')
904 Right (_, p') -> Free $ do
905 p'' <- p' ^. span (not . predicate)
906 return $ FreeT (go1 p'')
907 {-# INLINABLE splitsWith #-}
909 -- | Split a text stream using the given 'Char' as the delimiter
912 -> Lens' (Producer Text m r)
913 (FreeT (Producer Text m) m r)
915 fmap (PG.intercalates (yield (T.singleton c))) (k (splitsWith (c ==) p))
916 {-# INLINABLE splits #-}
918 {-| Isomorphism between a stream of 'Text' and groups of equivalent 'Char's , using the
919 given equivalence relation
923 => (Char -> Char -> Bool)
924 -> Lens' (Producer Text m x) (FreeT (Producer Text m) m x)
925 groupsBy equals k p0 = fmap concats (k (FreeT (go p0))) where
926 go p = do x <- next p
927 case x of Left r -> return (Pure r)
928 Right (bs, p') -> case T.uncons bs of
930 Just (c, _) -> do return $ Free $ do
931 p'' <- (yield bs >> p')^.span (equals c)
932 return $ FreeT (go p'')
933 {-# INLINABLE groupsBy #-}
936 -- | Like 'groupsBy', where the equality predicate is ('==')
939 => Lens' (Producer Text m x) (FreeT (Producer Text m) m x)
940 groups = groupsBy (==)
941 {-# INLINABLE groups #-}
945 {-| Split a text stream into 'FreeT'-delimited lines
948 :: (Monad m) => Iso' (Producer Text m r) (FreeT (Producer Text m) m r)
949 lines = Data.Profunctor.dimap _lines (fmap _unlines)
951 _lines p0 = FreeT (go0 p0)
956 Left r -> return (Pure r)
960 else return $ Free $ go1 (yield txt >> p')
962 p' <- p ^. break ('\n' ==)
966 Left r -> return $ Pure r
967 Right (_, p'') -> go0 p''
970 -- => FreeT (Producer Text m) m x -> Producer Text m x
971 _unlines = concats . PG.maps (<* yield (T.singleton '\n'))
974 {-# INLINABLE lines #-}
977 -- | Split a text stream into 'FreeT'-delimited words
979 :: (Monad m) => Iso' (Producer Text m r) (FreeT (Producer Text m) m r)
980 words = Data.Profunctor.dimap go (fmap _unwords)
983 x <- next (p >-> dropWhile isSpace)
986 Right (bs, p') -> Free $ do
987 p'' <- (yield bs >> p') ^. break isSpace
989 _unwords = PG.intercalates (yield $ T.singleton ' ')
991 {-# INLINABLE words #-}
994 {-| 'intercalate' concatenates the 'FreeT'-delimited text streams after
995 interspersing a text stream in between them
999 => Producer Text m ()
1000 -> FreeT (Producer Text m) m r
1001 -> Producer Text m r
1002 intercalate p0 = go0
1005 x <- lift (runFreeT f)
1012 x <- lift (runFreeT f)
1019 {-# INLINABLE intercalate #-}
1021 {-| Join 'FreeT'-delimited lines into a text stream
1024 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1028 x <- lift (runFreeT f)
1033 yield $ T.singleton '\n'
1035 {-# INLINABLE unlines #-}
1037 {-| Join 'FreeT'-delimited words into a text stream
1040 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1041 unwords = intercalate (yield $ T.singleton ' ')
1042 {-# INLINABLE unwords #-}
1045 The following parsing utilities are single-character analogs of the ones found
1051 @Data.Text@ re-exports the 'Text' type.
1053 @Pipes.Parse@ re-exports 'input', 'concat', 'FreeT' (the type) and the 'Parse' synonym.
1056 {- | Use a 'Codec' as a pipes-style 'Lens' into a byte stream; the available 'Codec' s are
1057 'utf8', 'utf16_le', 'utf16_be', 'utf32_le', 'utf32_be' . The 'Codec' concept and the
1058 individual 'Codec' definitions follow the enumerator and conduit libraries.
1060 Utf8 is handled differently in this library -- without the use of 'unsafePerformIO' &co
1061 to catch 'Text' exceptions; but the same 'mypipe ^. codec utf8' interface can be used.
1062 'mypipe ^. decodeUtf8' should be the same, but has a somewhat more direct and thus perhaps
1063 better implementation.
1066 codec :: Monad m => Codec -> Lens' (Producer ByteString m r) (Producer Text m (Producer ByteString m r))
1067 codec (Codec _ enc dec) k p0 = fmap (\p -> join (for p (yield . fst . enc)))
1068 (k (decoder (dec B.empty) p0) ) where
1069 decoder :: Monad m => PI.Decoding -> Producer ByteString m r -> Producer Text m (Producer ByteString m r)
1070 decoder !d p0 = case d of
1071 PI.Other txt bad -> do yield txt
1072 return (do yield bad
1074 PI.Some txt extra dec -> do yield txt
1076 case x of Left r -> return (do yield extra
1078 Right (chunk,p1) -> decoder (dec chunk) p1
1080 {- | ascii and latin encodings only represent a small fragment of 'Text'; thus we cannot
1081 use the pipes 'Lens' style to work with them. Rather we simply define functions
1084 'encodeAscii' : Reduce as much of your stream of 'Text' actually is ascii to a byte stream,
1085 returning the rest of the 'Text' at the first non-ascii 'Char'
1087 encodeAscii :: Monad m => Producer Text m r -> Producer ByteString m (Producer Text m r)
1088 encodeAscii = go where
1089 go p = do echunk <- lift (next p)
1091 Left r -> return (return r)
1092 Right (chunk, p') ->
1095 else let (safe, unsafe) = T.span (\c -> ord c <= 0x7F) chunk
1096 in do yield (B8.pack (T.unpack safe))
1099 else return $ do yield unsafe
1101 {- | Reduce as much of your stream of 'Text' actually is iso8859 or latin1 to a byte stream,
1102 returning the rest of the 'Text' upon hitting any non-latin 'Char'
1104 encodeIso8859_1 :: Monad m => Producer Text m r -> Producer ByteString m (Producer Text m r)
1105 encodeIso8859_1 = go where
1106 go p = do etxt <- lift (next p)
1108 Left r -> return (return r)
1112 else let (safe, unsafe) = T.span (\c -> ord c <= 0xFF) txt
1113 in do yield (B8.pack (T.unpack safe))
1116 else return $ do yield unsafe
1119 {- | Reduce a byte stream to a corresponding stream of ascii chars, returning the
1120 unused 'ByteString' upon hitting an un-ascii byte.
1122 decodeAscii :: Monad m => Producer ByteString m r -> Producer Text m (Producer ByteString m r)
1123 decodeAscii = go where
1124 go p = do echunk <- lift (next p)
1126 Left r -> return (return r)
1127 Right (chunk, p') ->
1130 else let (safe, unsafe) = B.span (<= 0x7F) chunk
1131 in do yield (T.pack (B8.unpack safe))
1134 else return $ do yield unsafe
1137 {- | Reduce a byte stream to a corresponding stream of ascii chars, returning the
1138 unused 'ByteString' upon hitting the rare un-latinizable byte.
1140 decodeIso8859_1 :: Monad m => Producer ByteString m r -> Producer Text m (Producer ByteString m r)
1141 decodeIso8859_1 = go where
1142 go p = do echunk <- lift (next p)
1144 Left r -> return (return r)
1145 Right (chunk, p') ->
1148 else let (safe, unsafe) = B.span (<= 0xFF) chunk
1149 in do yield (T.pack (B8.unpack safe))
1152 else return $ do yield unsafe