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'.
110 -- * Primitive Character Parsers
149 , module Data.ByteString
151 , module Data.Profunctor
157 import Control.Exception (throwIO, try)
158 import Control.Applicative ((<*))
159 import Control.Monad (liftM, unless, join)
160 import Control.Monad.Trans.State.Strict (StateT(..), modify)
161 import Data.Monoid ((<>))
162 import qualified Data.Text as T
163 import qualified Data.Text.IO as T
164 import qualified Data.Text.Encoding as TE
165 import qualified Data.Text.Encoding.Error as TE
166 import Data.Text (Text)
167 import qualified Data.Text.Lazy as TL
168 import qualified Data.Text.Lazy.IO as TL
169 import Data.Text.Lazy.Internal (foldrChunks, defaultChunkSize)
170 import Data.ByteString.Unsafe (unsafeTake, unsafeDrop)
171 import Data.ByteString (ByteString)
172 import qualified Data.ByteString as B
173 import Data.Char (ord, isSpace)
174 import Data.Functor.Constant (Constant(Constant, getConstant))
175 import Data.Functor.Identity (Identity)
176 import Data.Profunctor (Profunctor)
177 import qualified Data.Profunctor
178 import qualified Data.List as List
179 import Foreign.C.Error (Errno(Errno), ePIPE)
180 import qualified GHC.IO.Exception as G
182 import qualified Pipes.ByteString as PB
183 import qualified Pipes.Text.Internal as PE
184 import Pipes.Text.Internal (Codec(..))
185 import Pipes.Core (respond, Server')
186 import Pipes.Group (concats, intercalates, transFreeT, FreeT(..), FreeF(..))
187 import qualified Pipes.Group as PG
188 import qualified Pipes.Parse as PP
189 import Pipes.Parse (Parser)
190 import qualified Pipes.Safe.Prelude as Safe
191 import qualified Pipes.Safe as Safe
192 import Pipes.Safe (MonadSafe(..), Base(..))
193 import qualified Pipes.Prelude as P
194 import qualified System.IO as IO
195 import Data.Char (isSpace)
196 import Data.Word (Word8)
198 import Prelude hiding (
227 -- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's
228 fromLazy :: (Monad m) => TL.Text -> Producer' Text m ()
229 fromLazy = foldrChunks (\e a -> yield e >> a) (return ())
230 {-# INLINE fromLazy #-}
232 -- | Stream text from 'stdin'
233 stdin :: MonadIO m => Producer Text m ()
234 stdin = fromHandle IO.stdin
237 {-| Convert a 'IO.Handle' into a text stream using a text size
238 determined by the good sense of the text library; note that this
239 is distinctly slower than @decideUtf8 (Pipes.ByteString.fromHandle h)@
240 but uses the system encoding and has other `Data.Text.IO` features
243 fromHandle :: MonadIO m => IO.Handle -> Producer Text m ()
244 fromHandle h = go where
245 go = do txt <- liftIO (T.hGetChunk h)
246 unless (T.null txt) $ do yield txt
248 {-# INLINABLE fromHandle#-}
251 {-| Stream text from a file in the simple fashion of @Data.Text.IO@
253 >>> runSafeT $ runEffect $ Text.readFile "hello.hs" >-> Text.map toUpper >-> hoist lift Text.stdout
254 MAIN = PUTSTRLN "HELLO WORLD"
257 readFile :: MonadSafe m => FilePath -> Producer Text m ()
258 readFile file = Safe.withFile file IO.ReadMode fromHandle
259 {-# INLINE readFile #-}
261 {-| Stream lines of text from stdin (for testing in ghci etc.)
263 >>> let safely = runSafeT . runEffect
264 >>> safely $ for Text.stdinLn (lift . lift . print . T.length)
271 stdinLn :: MonadIO m => Producer' Text m ()
274 eof <- liftIO (IO.hIsEOF IO.stdin)
276 txt <- liftIO (T.hGetLine IO.stdin)
279 {-# INLINABLE stdinLn #-}
281 {-| Stream text to 'stdout'
283 Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
285 Note: For best performance, use @(for source (liftIO . putStr))@ instead of
286 @(source >-> stdout)@ in suitable cases.
288 stdout :: MonadIO m => Consumer' Text m ()
293 x <- liftIO $ try (T.putStr txt)
295 Left (G.IOError { G.ioe_type = G.ResourceVanished
296 , G.ioe_errno = Just ioe })
299 Left e -> liftIO (throwIO e)
301 {-# INLINABLE stdout #-}
303 stdoutLn :: (MonadIO m) => Consumer' Text m ()
308 x <- liftIO $ try (T.putStrLn str)
310 Left (G.IOError { G.ioe_type = G.ResourceVanished
311 , G.ioe_errno = Just ioe })
314 Left e -> liftIO (throwIO e)
316 {-# INLINABLE stdoutLn #-}
318 {-| Convert a text stream into a 'Handle'
320 Note: again, for best performance, where possible use
321 @(for source (liftIO . hPutStr handle))@ instead of @(source >-> toHandle handle)@.
323 toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r
324 toHandle h = for cat (liftIO . T.hPutStr h)
325 {-# INLINABLE toHandle #-}
327 {-# RULES "p >-> toHandle h" forall p h .
328 p >-> toHandle h = for p (\txt -> liftIO (T.hPutStr h txt))
332 -- | Stream text into a file. Uses @pipes-safe@.
333 writeFile :: (MonadSafe m) => FilePath -> Consumer' Text m ()
334 writeFile file = Safe.withFile file IO.WriteMode toHandle
335 {-# INLINE writeFile #-}
338 type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)
340 type Iso' a b = forall f p . (Functor f, Profunctor p) => p b (f b) -> p a (f a)
342 (^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
343 a ^. lens = getConstant (lens Constant a)
346 -- | Apply a transformation to each 'Char' in the stream
347 map :: (Monad m) => (Char -> Char) -> Pipe Text Text m r
348 map f = P.map (T.map f)
349 {-# INLINABLE map #-}
351 {-# RULES "p >-> map f" forall p f .
352 p >-> map f = for p (\txt -> yield (T.map f txt))
355 -- | Map a function over the characters of a text stream and concatenate the results
357 :: (Monad m) => (Char -> Text) -> Pipe Text Text m r
358 concatMap f = P.map (T.concatMap f)
359 {-# INLINABLE concatMap #-}
361 {-# RULES "p >-> concatMap f" forall p f .
362 p >-> concatMap f = for p (\txt -> yield (T.concatMap f txt))
365 -- | Transform a Pipe of 'Text' into a Pipe of 'ByteString's using UTF-8
366 -- encoding; @encodeUtf8 = Pipes.Prelude.map TE.encodeUtf8@ so more complex
367 -- encoding pipes can easily be constructed with the functions in @Data.Text.Encoding@
368 encodeUtf8 :: Monad m => Pipe Text ByteString m r
369 encodeUtf8 = P.map TE.encodeUtf8
370 {-# INLINEABLE encodeUtf8 #-}
372 {-# RULES "p >-> encodeUtf8" forall p .
373 p >-> encodeUtf8 = for p (\txt -> yield (TE.encodeUtf8 txt))
376 -- | Transform a Pipe of 'String's into one of 'Text' chunks
377 pack :: Monad m => Pipe String Text m r
379 {-# INLINEABLE pack #-}
381 {-# RULES "p >-> pack" forall p .
382 p >-> pack = for p (\txt -> yield (T.pack txt))
385 -- | Transform a Pipes of 'Text' chunks into one of 'String's
386 unpack :: Monad m => Pipe Text String m r
387 unpack = for cat (\t -> yield (T.unpack t))
388 {-# INLINEABLE unpack #-}
390 {-# RULES "p >-> unpack" forall p .
391 p >-> unpack = for p (\txt -> yield (T.unpack txt))
394 -- | @toCaseFold@, @toLower@, @toUpper@ and @stripStart@ are standard 'Text' utilities,
395 -- here acting as 'Text' pipes, rather as they would on a lazy text
396 toCaseFold :: Monad m => Pipe Text Text m ()
397 toCaseFold = P.map T.toCaseFold
398 {-# INLINEABLE toCaseFold #-}
400 {-# RULES "p >-> toCaseFold" forall p .
401 p >-> toCaseFold = for p (\txt -> yield (T.toCaseFold txt))
405 -- | lowercase incoming 'Text'
406 toLower :: Monad m => Pipe Text Text m ()
407 toLower = P.map T.toLower
408 {-# INLINEABLE toLower #-}
410 {-# RULES "p >-> toLower" forall p .
411 p >-> toLower = for p (\txt -> yield (T.toLower txt))
414 -- | uppercase incoming 'Text'
415 toUpper :: Monad m => Pipe Text Text m ()
416 toUpper = P.map T.toUpper
417 {-# INLINEABLE toUpper #-}
419 {-# RULES "p >-> toUpper" forall p .
420 p >-> toUpper = for p (\txt -> yield (T.toUpper txt))
423 -- | Remove leading white space from an incoming succession of 'Text's
424 stripStart :: Monad m => Pipe Text Text m r
427 let text = T.stripStart chunk
432 {-# INLINEABLE stripStart #-}
434 -- | @(take n)@ only allows @n@ individual characters to pass;
435 -- contrast @Pipes.Prelude.take@ which would let @n@ chunks pass.
436 take :: (Monad m, Integral a) => a -> Pipe Text Text m ()
437 take n0 = go n0 where
442 let len = fromIntegral (T.length txt)
444 then yield (T.take (fromIntegral n) txt)
448 {-# INLINABLE take #-}
450 -- | @(drop n)@ drops the first @n@ characters
451 drop :: (Monad m, Integral a) => a -> Pipe Text Text m r
452 drop n0 = go n0 where
457 let len = fromIntegral (T.length txt)
460 yield (T.drop (fromIntegral n) txt)
463 {-# INLINABLE drop #-}
465 -- | Take characters until they fail the predicate
466 takeWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m ()
467 takeWhile predicate = go
471 let (prefix, suffix) = T.span predicate txt
477 {-# INLINABLE takeWhile #-}
479 -- | Drop characters until they fail the predicate
480 dropWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
481 dropWhile predicate = go where
484 case T.findIndex (not . predicate) txt of
489 {-# INLINABLE dropWhile #-}
491 -- | Only allows 'Char's to pass if they satisfy the predicate
492 filter :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
493 filter predicate = P.map (T.filter predicate)
494 {-# INLINABLE filter #-}
496 {-# RULES "p >-> filter q" forall p q .
497 p >-> filter q = for p (\txt -> yield (T.filter q txt))
500 -- | Strict left scan over the characters
503 => (Char -> Char -> Char) -> Char -> Pipe Text Text m r
504 scan step begin = go begin
508 let txt' = T.scanl step c txt
512 {-# INLINABLE scan #-}
514 {-| Fold a pure 'Producer' of strict 'Text's into a lazy
517 toLazy :: Producer Text Identity () -> TL.Text
518 toLazy = TL.fromChunks . P.toList
519 {-# INLINABLE toLazy #-}
521 {-| Fold an effectful 'Producer' of strict 'Text's into a lazy
524 Note: 'toLazyM' is not an idiomatic use of @pipes@, but I provide it for
525 simple testing purposes. Idiomatic @pipes@ style consumes the chunks
526 immediately as they are generated instead of loading them all into memory.
528 toLazyM :: (Monad m) => Producer Text m () -> m TL.Text
529 toLazyM = liftM TL.fromChunks . P.toListM
530 {-# INLINABLE toLazyM #-}
532 -- | Reduce the text stream using a strict left fold over characters
535 => (x -> Char -> x) -> x -> (x -> r) -> Producer Text m () -> m r
536 foldChars step begin done = P.fold (T.foldl' step) begin done
537 {-# INLINABLE foldChars #-}
539 -- | Retrieve the first 'Char'
540 head :: (Monad m) => Producer Text m () -> m (Maybe Char)
546 Left _ -> return Nothing
547 Right (c, _) -> return (Just c)
548 {-# INLINABLE head #-}
550 -- | Retrieve the last 'Char'
551 last :: (Monad m) => Producer Text m () -> m (Maybe Char)
561 else go (Just $ T.last txt) p'
562 {-# INLINABLE last #-}
564 -- | Determine if the stream is empty
565 null :: (Monad m) => Producer Text m () -> m Bool
567 {-# INLINABLE null #-}
569 -- | Count the number of characters in the stream
570 length :: (Monad m, Num n) => Producer Text m () -> m n
571 length = P.fold (\n txt -> n + fromIntegral (T.length txt)) 0 id
572 {-# INLINABLE length #-}
574 -- | Fold that returns whether 'M.Any' received 'Char's satisfy the predicate
575 any :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
576 any predicate = P.any (T.any predicate)
577 {-# INLINABLE any #-}
579 -- | Fold that returns whether 'M.All' received 'Char's satisfy the predicate
580 all :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
581 all predicate = P.all (T.all predicate)
582 {-# INLINABLE all #-}
584 -- | Return the maximum 'Char' within a text stream
585 maximum :: (Monad m) => Producer Text m () -> m (Maybe Char)
586 maximum = P.fold step Nothing id
591 else Just $ case mc of
592 Nothing -> T.maximum txt
593 Just c -> max c (T.maximum txt)
594 {-# INLINABLE maximum #-}
596 -- | Return the minimum 'Char' within a text stream (surely very useful!)
597 minimum :: (Monad m) => Producer Text m () -> m (Maybe Char)
598 minimum = P.fold step Nothing id
604 Nothing -> Just (T.minimum txt)
605 Just c -> Just (min c (T.minimum txt))
606 {-# INLINABLE minimum #-}
609 -- | Find the first element in the stream that matches the predicate
612 => (Char -> Bool) -> Producer Text m () -> m (Maybe Char)
613 find predicate p = head (p >-> filter predicate)
614 {-# INLINABLE find #-}
616 -- | Index into a text stream
618 :: (Monad m, Integral a)
619 => a-> Producer Text m () -> m (Maybe Char)
620 index n p = head (p >-> drop n)
621 {-# INLINABLE index #-}
624 -- | Store a tally of how many segments match the given 'Text'
625 count :: (Monad m, Num n) => Text -> Producer Text m () -> m n
626 count c p = P.fold (+) 0 id (p >-> P.map (fromIntegral . T.count c))
627 {-# INLINABLE count #-}
630 {-| Consume the first character from a stream of 'Text'
632 'next' either fails with a 'Left' if the 'Producer' has no more characters or
633 succeeds with a 'Right' providing the next character and the remainder of the
639 -> m (Either r (Char, Producer Text m r))
645 Left r -> return (Left r)
646 Right (txt, p') -> case (T.uncons txt) of
648 Just (c, txt') -> return (Right (c, yield txt' >> p'))
649 {-# INLINABLE nextChar #-}
651 {-| Draw one 'Char' from a stream of 'Text', returning 'Left' if the
654 drawChar :: (Monad m) => Parser Text m (Maybe Char)
658 Nothing -> return Nothing
659 Just txt -> case (T.uncons txt) of
664 {-# INLINABLE drawChar #-}
666 -- | Push back a 'Char' onto the underlying 'Producer'
667 unDrawChar :: (Monad m) => Char -> Parser Text m ()
668 unDrawChar c = modify (yield (T.singleton c) >>)
669 {-# INLINABLE unDrawChar #-}
671 {-| 'peekChar' checks the first 'Char' in the stream, but uses 'unDrawChar' to
677 > Left _ -> return ()
678 > Right c -> unDrawChar c
681 peekChar :: (Monad m) => Parser Text m (Maybe Char)
686 Just c -> unDrawChar c
688 {-# INLINABLE peekChar #-}
690 {-| Check if the underlying 'Producer' has no more characters
692 Note that this will skip over empty 'Text' chunks, unlike
693 'PP.isEndOfInput' from @pipes-parse@, which would consider
694 an empty 'Text' a valid bit of input.
696 > isEndOfChars = liftM isLeft peekChar
698 isEndOfChars :: (Monad m) => Parser Text m Bool
704 {-# INLINABLE isEndOfChars #-}
710 -- | Transform a Pipe of 'ByteString's expected to be UTF-8 encoded into a Pipe of Text
711 -- returning a Pipe of ByteStrings that begins at the point of failure.
713 decodeUtf8 :: Monad m => Lens' (Producer ByteString m r)
714 (Producer Text m (Producer ByteString m r))
715 decodeUtf8 k p0 = fmap (\p -> join (for p (yield . TE.encodeUtf8)))
716 (k (go B.empty PE.streamDecodeUtf8 p0)) where
717 go !carry dec0 p = do
719 case x of Left r -> if B.null carry
720 then return (return r) -- all bytestrinput was consumed
721 else return (do yield carry -- a potentially valid fragment remains
724 Right (chunk, p') -> case dec0 chunk of
725 PE.Some text carry2 dec -> do yield text
727 PE.Other text bs -> do yield text
728 return (do yield bs -- an invalid blob remains
730 {-# INLINABLE decodeUtf8 #-}
733 -- | Splits a 'Producer' after the given number of characters
735 :: (Monad m, Integral n)
737 -> Lens' (Producer Text m r)
738 (Producer Text m (Producer Text m r))
739 splitAt n0 k p0 = fmap join (k (go n0 p0))
745 Left r -> return (return r)
746 Right (txt, p') -> do
747 let len = fromIntegral (T.length txt)
753 let (prefix, suffix) = T.splitAt (fromIntegral n) txt
755 return (yield suffix >> p')
756 {-# INLINABLE splitAt #-}
759 {-| Split a text stream in two, where the first text stream is the longest
760 consecutive group of text that satisfy the predicate
765 -> Lens' (Producer Text m r)
766 (Producer Text m (Producer Text m r))
767 span predicate k p0 = fmap join (k (go p0))
772 Left r -> return (return r)
773 Right (txt, p') -> do
774 let (prefix, suffix) = T.span predicate txt
781 return (yield suffix >> p')
782 {-# INLINABLE span #-}
784 {-| Split a text stream in two, where the first text stream is the longest
785 consecutive group of characters that don't satisfy the predicate
790 -> Lens' (Producer Text m r)
791 (Producer Text m (Producer Text m r))
792 break predicate = span (not . predicate)
793 {-# INLINABLE break #-}
795 {-| Improper lens that splits after the first group of equivalent Chars, as
796 defined by the given equivalence relation
800 => (Char -> Char -> Bool)
801 -> Lens' (Producer Text m r)
802 (Producer Text m (Producer Text m r))
803 groupBy equals k p0 = fmap join (k ((go p0))) where
807 Left r -> return (return r)
808 Right (txt, p') -> case T.uncons txt of
810 Just (c, _) -> (yield txt >> p') ^. span (equals c)
811 {-# INLINABLE groupBy #-}
813 -- | Improper lens that splits after the first succession of identical 'Char' s
815 => Lens' (Producer Text m r)
816 (Producer Text m (Producer Text m r))
818 {-# INLINABLE group #-}
820 {-| Improper lens that splits a 'Producer' after the first word
822 Unlike 'words', this does not drop leading whitespace
825 => Lens' (Producer Text m r)
826 (Producer Text m (Producer Text m r))
827 word k p0 = fmap join (k (to p0))
830 p' <- p^.span isSpace
832 {-# INLINABLE word #-}
836 => Lens' (Producer Text m r)
837 (Producer Text m (Producer Text m r))
838 line = break (== '\n')
840 {-# INLINABLE line #-}
843 -- | Intersperse a 'Char' in between the characters of stream of 'Text'
845 :: (Monad m) => Char -> Producer Text m r -> Producer Text m r
852 Right (txt, p') -> do
853 yield (T.intersperse c txt)
859 Right (txt, p') -> do
860 yield (T.singleton c)
861 yield (T.intersperse c txt)
863 {-# INLINABLE intersperse #-}
867 -- | Improper isomorphism between a 'Producer' of 'ByteString's and 'Word8's
868 packChars :: Monad m => Iso' (Producer Char m x) (Producer Text m x)
869 packChars = Data.Profunctor.dimap to (fmap from)
871 -- to :: Monad m => Producer Char m x -> Producer Text m x
872 to p = PG.folds step id done (p^.PG.chunksOf defaultChunkSize)
874 step diffAs c = diffAs . (c:)
876 done diffAs = T.pack (diffAs [])
878 -- from :: Monad m => Producer Text m x -> Producer Char m x
879 from p = for p (each . T.unpack)
880 {-# INLINABLE packChars #-}
883 -- | Split a text stream into 'FreeT'-delimited text streams of fixed size
885 :: (Monad m, Integral n)
886 => n -> Lens' (Producer Text m r)
887 (FreeT (Producer Text m) m r)
888 chunksOf n k p0 = fmap concats (k (FreeT (go p0)))
894 Right (txt, p') -> Free $ do
895 p'' <- (yield txt >> p') ^. splitAt n
896 return $ FreeT (go p'')
897 {-# INLINABLE chunksOf #-}
900 {-| Split a text stream into sub-streams delimited by characters that satisfy the
907 -> FreeT (Producer Text m) m r
908 splitsWith predicate p0 = FreeT (go0 p0)
913 Left r -> return (Pure r)
917 else return $ Free $ do
918 p'' <- (yield txt >> p') ^. span (not . predicate)
919 return $ FreeT (go1 p'')
924 Right (_, p') -> Free $ do
925 p'' <- p' ^. span (not . predicate)
926 return $ FreeT (go1 p'')
927 {-# INLINABLE splitsWith #-}
929 -- | Split a text stream using the given 'Char' as the delimiter
932 -> Lens' (Producer Text m r)
933 (FreeT (Producer Text m) m r)
935 fmap (PG.intercalates (yield (T.singleton c))) (k (splitsWith (c ==) p))
936 {-# INLINABLE splits #-}
938 {-| Isomorphism between a stream of 'Text' and groups of equivalent 'Char's , using the
939 given equivalence relation
943 => (Char -> Char -> Bool)
944 -> Lens' (Producer Text m x) (FreeT (Producer Text m) m x)
945 groupsBy equals k p0 = fmap concats (k (FreeT (go p0))) where
946 go p = do x <- next p
947 case x of Left r -> return (Pure r)
948 Right (bs, p') -> case T.uncons bs of
950 Just (c, _) -> do return $ Free $ do
951 p'' <- (yield bs >> p')^.span (equals c)
952 return $ FreeT (go p'')
953 {-# INLINABLE groupsBy #-}
956 -- | Like 'groupsBy', where the equality predicate is ('==')
959 => Lens' (Producer Text m x) (FreeT (Producer Text m) m x)
960 groups = groupsBy (==)
961 {-# INLINABLE groups #-}
965 {-| Split a text stream into 'FreeT'-delimited lines
968 :: (Monad m) => Iso' (Producer Text m r) (FreeT (Producer Text m) m r)
969 lines = Data.Profunctor.dimap _lines (fmap _unlines)
971 _lines p0 = FreeT (go0 p0)
976 Left r -> return (Pure r)
980 else return $ Free $ go1 (yield txt >> p')
982 p' <- p ^. break ('\n' ==)
986 Left r -> return $ Pure r
987 Right (_, p'') -> go0 p''
990 -- => FreeT (Producer Text m) m x -> Producer Text m x
991 _unlines = concats . transFreeT addNewline
994 -- :: Monad m => Producer Text m r -> Producer Text m r
995 addNewline p = p <* yield (T.singleton '\n')
996 {-# INLINABLE lines #-}
1000 -- | Split a text stream into 'FreeT'-delimited words
1002 :: (Monad m) => Iso' (Producer Text m r) (FreeT (Producer Text m) m r)
1003 words = Data.Profunctor.dimap go (fmap _unwords)
1006 x <- next (p >-> dropWhile isSpace)
1009 Right (bs, p') -> Free $ do
1010 p'' <- (yield bs >> p') ^. break isSpace
1012 _unwords = PG.intercalates (yield $ T.singleton ' ')
1014 {-# INLINABLE words #-}
1017 {-| 'intercalate' concatenates the 'FreeT'-delimited text streams after
1018 interspersing a text stream in between them
1022 => Producer Text m ()
1023 -> FreeT (Producer Text m) m r
1024 -> Producer Text m r
1025 intercalate p0 = go0
1028 x <- lift (runFreeT f)
1035 x <- lift (runFreeT f)
1042 {-# INLINABLE intercalate #-}
1044 {-| Join 'FreeT'-delimited lines into a text stream
1047 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1051 x <- lift (runFreeT f)
1056 yield $ T.singleton '\n'
1058 {-# INLINABLE unlines #-}
1060 {-| Join 'FreeT'-delimited words into a text stream
1063 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1064 unwords = intercalate (yield $ T.pack " ")
1065 {-# INLINABLE unwords #-}
1068 The following parsing utilities are single-character analogs of the ones found
1074 @Data.Text@ re-exports the 'Text' type.
1076 @Pipes.Parse@ re-exports 'input', 'concat', 'FreeT' (the type) and the 'Parse' synonym.
1081 decode :: Monad m => PE.Decoding -> Producer ByteString m r -> Producer Text m (Producer ByteString m r)
1082 -- decode codec = go B.empty where
1084 -- do x <- lift (next p0)
1085 -- case x of Right (chunk, p) ->
1086 -- do let (text, stuff) = codecDecode codec (B.append extra chunk)
1088 -- case stuff of Right extra' -> go extra' p
1089 -- Left (exc,bs) -> do yield text
1090 -- return (do yield bs
1092 -- Left r -> return (do yield extra
1095 decode d p0 = case d of
1096 PE.Other txt bad -> do yield txt
1097 return (do yield bad
1099 PE.Some txt extra dec -> do yield txt
1101 case x of Left r -> return (do yield extra
1103 Right (chunk,p1) -> decode (dec chunk) p1
1105 -- go !carry dec0 p = do
1106 -- x <- lift (next p)
1107 -- case x of Left r -> if B.null carry
1108 -- then return (return r) -- all bytestrinput was consumed
1109 -- else return (do yield carry -- a potentially valid fragment remains
1112 -- Right (chunk, p') -> case dec0 chunk of
1113 -- PE.Some text carry2 dec -> do yield text
1115 -- PE.Other text bs -> do yield text
1116 -- return (do yield bs -- an invalid blob remains
1118 -- {-# INLINABLE decodeUtf8 #-}