]>
Commit | Line | Data |
---|---|---|
91727d11 | 1 | {-# LANGUAGE RankNTypes, TypeFamilies #-} |
2 | ||
3 | {-| This module provides @pipes@ utilities for \"byte streams\", which are | |
4 | streams of strict 'Text's chunks. Use byte streams to interact | |
5 | with both 'IO.Handle's and lazy 'Text's. | |
6 | ||
7 | To stream to or from 'IO.Handle's, use 'fromHandle' or 'toHandle'. For | |
8 | example, the following program copies data from one file to another: | |
9 | ||
10 | > import Pipes | |
11 | > import qualified Pipes.Text as P | |
12 | > import System.IO | |
13 | > | |
14 | > main = | |
15 | > withFile "inFile.txt" ReadMode $ \hIn -> | |
16 | > withFile "outFile.txt" WriteMode $ \hOut -> | |
17 | > runEffect $ P.fromHandle hIn >-> P.toHandle hOut | |
18 | ||
19 | You can stream to and from 'stdin' and 'stdout' using the predefined 'stdin' | |
20 | and 'stdout' proxies, like in the following \"echo\" program: | |
21 | ||
22 | > main = runEffect $ P.stdin >-> P.stdout | |
23 | ||
24 | You can also translate pure lazy 'TL.Text's to and from proxies: | |
25 | ||
26 | > import qualified Data.Text.Lazy as TL | |
27 | > | |
28 | > main = runEffect $ P.fromLazy (TL.pack "Hello, world!\n") >-> P.stdout | |
29 | ||
30 | In addition, this module provides many functions equivalent to lazy | |
31 | 'Text' functions so that you can transform or fold byte streams. For | |
32 | example, to stream only the first three lines of 'stdin' to 'stdout' you | |
33 | would write: | |
34 | ||
35 | > import Pipes | |
36 | > import qualified Pipes.Text as PT | |
37 | > import qualified Pipes.Parse as PP | |
38 | > | |
39 | > main = runEffect $ takeLines 3 PB.stdin >-> PT.stdout | |
40 | > where | |
41 | > takeLines n = PB.unlines . PP.takeFree n . PT.lines | |
42 | ||
43 | The above program will never bring more than one chunk (~ 32 KB) into | |
44 | memory, no matter how long the lines are. | |
45 | ||
46 | Note that functions in this library are designed to operate on streams that | |
47 | are insensitive to chunk boundaries. This means that they may freely split | |
48 | chunks into smaller chunks and /discard empty chunks/. However, they will | |
49 | /never concatenate chunks/ in order to provide strict upper bounds on memory | |
50 | usage. | |
51 | -} | |
52 | ||
53 | module Data.Text.Pipes ( | |
54 | -- * Producers | |
55 | fromLazy, | |
56 | stdin, | |
57 | fromHandle, | |
58 | readFile, | |
59 | stdinLn, | |
60 | -- hGetSome, | |
61 | -- hGet, | |
62 | ||
63 | -- * Servers | |
64 | -- hGetSomeN, | |
65 | -- hGetN, | |
66 | ||
67 | -- * Consumers | |
68 | stdout, | |
69 | stdoutLn, | |
70 | toHandle, | |
71 | writeFile, | |
72 | ||
73 | -- * Pipes | |
74 | map, | |
75 | concatMap, | |
76 | take, | |
77 | drop, | |
78 | takeWhile, | |
79 | dropWhile, | |
80 | filter, | |
81 | -- elemIndices, | |
82 | -- findIndices, | |
83 | scan, | |
84 | ||
85 | -- * Folds | |
86 | toLazy, | |
87 | toLazyM, | |
88 | fold, | |
89 | head, | |
90 | last, | |
91 | null, | |
92 | length, | |
93 | any, | |
94 | all, | |
95 | maximum, | |
96 | minimum, | |
97 | -- elem, | |
98 | -- notElem, | |
99 | find, | |
100 | index, | |
101 | -- elemIndex, | |
102 | -- findIndex, | |
103 | -- count, | |
104 | ||
105 | -- * Splitters | |
106 | splitAt, | |
107 | chunksOf, | |
108 | span, | |
109 | break, | |
110 | splitWith, | |
111 | split, | |
112 | groupBy, | |
113 | group, | |
114 | lines, | |
115 | words, | |
116 | ||
117 | -- * Transformations | |
118 | intersperse, | |
119 | ||
120 | -- * Joiners | |
121 | intercalate, | |
122 | unlines, | |
123 | unwords, | |
124 | ||
125 | -- * Low-level Parsers | |
126 | -- $parse | |
127 | nextByte, | |
128 | drawByte, | |
129 | unDrawByte, | |
130 | peekByte, | |
131 | isEndOfBytes, | |
132 | -- takeWhile', | |
133 | ||
134 | -- * Re-exports | |
135 | -- $reexports | |
136 | module Data.Text, | |
137 | -- module Data.Word, | |
138 | module Pipes.Parse | |
139 | ) where | |
140 | ||
141 | import Control.Exception (throwIO, try) | |
142 | import Control.Monad (liftM, unless) | |
143 | import Control.Monad.Trans.State.Strict (StateT) | |
144 | import qualified Data.Text as T | |
145 | import qualified Data.Text.IO as T | |
146 | import Data.Text (Text) | |
147 | import qualified Data.Text.Lazy as TL | |
148 | import qualified Data.Text.Lazy.IO as TL | |
149 | import Data.Text.Lazy.Internal (foldrChunks, defaultChunkSize) | |
150 | import Data.ByteString.Unsafe (unsafeTake, unsafeDrop) | |
151 | import Data.Char (ord) | |
152 | import Data.Functor.Identity (Identity) | |
153 | import qualified Data.List as List | |
154 | import Foreign.C.Error (Errno(Errno), ePIPE) | |
155 | import qualified GHC.IO.Exception as G | |
156 | import Pipes | |
157 | import qualified Pipes.ByteString.Parse as PBP | |
158 | import Pipes.ByteString.Parse ( | |
159 | nextByte, drawByte, unDrawByte, peekByte, isEndOfBytes ) | |
160 | import Pipes.Core (respond, Server') | |
161 | import qualified Pipes.Parse as PP | |
162 | import Pipes.Parse (input, concat, FreeT) | |
163 | import qualified Pipes.Safe.Prelude as Safe | |
164 | import qualified Pipes.Safe as Safe | |
165 | import Pipes.Safe (MonadSafe(..), Base(..)) | |
166 | import qualified Pipes.Prelude as P | |
167 | import qualified System.IO as IO | |
168 | import Data.Char (isSpace) | |
169 | import Prelude hiding ( | |
170 | all, | |
171 | any, | |
172 | break, | |
173 | concat, | |
174 | concatMap, | |
175 | drop, | |
176 | dropWhile, | |
177 | elem, | |
178 | filter, | |
179 | head, | |
180 | last, | |
181 | lines, | |
182 | length, | |
183 | map, | |
184 | maximum, | |
185 | minimum, | |
186 | notElem, | |
187 | null, | |
188 | readFile, | |
189 | span, | |
190 | splitAt, | |
191 | take, | |
192 | takeWhile, | |
193 | unlines, | |
194 | unwords, | |
195 | words, | |
196 | writeFile ) | |
197 | ||
198 | -- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's | |
199 | fromLazy :: (Monad m) => TL.Text -> Producer' Text m () | |
200 | fromLazy = foldrChunks (\e a -> yield e >> a) (return ()) | |
201 | {-# INLINABLE fromLazy #-} | |
202 | ||
203 | -- | Stream bytes from 'stdin' | |
204 | stdin :: MonadIO m => Producer' Text m () | |
205 | stdin = fromHandle IO.stdin | |
206 | {-# INLINABLE stdin #-} | |
207 | ||
6f6f9974 | 208 | -- | Convert a 'IO.Handle' into a text stream using a chunk size |
209 | -- determined by the good sense of the text library. | |
91727d11 | 210 | fromHandle :: MonadIO m => IO.Handle -> Producer' Text m () |
211 | fromHandle h = go where | |
212 | go = do txt <- liftIO (T.hGetChunk h) | |
213 | unless (T.null txt) $ do yield txt | |
214 | go | |
215 | {-# INLINABLE fromHandle#-} | |
216 | ||
6f6f9974 | 217 | {-| Stream text from a file using Pipes.Safe |
218 | ||
219 | >>> runSafeT $ runEffect $ readFile "README.md" >-> map toUpper >-> hoist lift stdout | |
220 | TEXT-PIPES | |
221 | ========== | |
222 | TEXT PIPES, SOMEHOW TO BE FUSED WITH `PIPES-TEXT`. | |
223 | ... | |
224 | >>> | |
225 | -} | |
226 | ||
91727d11 | 227 | readFile :: (MonadSafe m, Base m ~ IO) => FilePath -> Producer' Text m () |
228 | readFile file = Safe.withFile file IO.ReadMode fromHandle | |
229 | {-# INLINABLE readFile #-} | |
230 | ||
231 | stdinLn :: MonadIO m => Producer' Text m () | |
232 | stdinLn = go | |
233 | where | |
234 | go = do | |
235 | eof <- liftIO (IO.hIsEOF IO.stdin) | |
236 | unless eof $ do | |
237 | txt <- liftIO (T.hGetLine IO.stdin) | |
238 | yield txt | |
239 | go | |
240 | ||
241 | {-| Convert a handle into a byte stream using a fixed chunk size | |
242 | ||
243 | 'hGet' waits until exactly the requested number of bytes are available for | |
244 | each chunk. | |
245 | -} | |
246 | -- hGet :: MonadIO m => Int -> IO.Handle -> Producer' Text m () | |
247 | -- hGet size h = go where | |
248 | -- go = do | |
249 | -- eof <- liftIO (IO.hIsEOF h) | |
250 | -- if eof | |
251 | -- then return () | |
252 | -- else do | |
253 | -- bs <- liftIO (T.hGet h size) | |
254 | -- yield bs | |
255 | -- go | |
256 | -- {-# INLINABLE hGet #-} | |
257 | ||
258 | {-| Like 'hGetSome', except you can vary the maximum chunk size for each request | |
259 | -} | |
260 | -- hGetSomeN :: MonadIO m => IO.Handle -> Int -> Server' Int Text m () | |
261 | -- hGetSomeN h = go where | |
262 | -- go size = do | |
263 | -- eof <- liftIO (IO.hIsEOF h) | |
264 | -- if eof | |
265 | -- then return () | |
266 | -- else do | |
267 | -- bs <- liftIO (T.hGetSome h size) | |
268 | -- size2 <- respond bs | |
269 | -- go size2 | |
270 | -- {-# INLINABLE hGetSomeN #-} | |
271 | -- | |
272 | -- -- | Like 'hGet', except you can vary the chunk size for each request | |
273 | -- hGetN :: MonadIO m => IO.Handle -> Int -> Server' Int Text m () | |
274 | -- hGetN h = go where | |
275 | -- go size = do | |
276 | -- eof <- liftIO (IO.hIsEOF h) | |
277 | -- if eof | |
278 | -- then return () | |
279 | -- else do | |
280 | -- bs <- liftIO (T.hGet h size) | |
281 | -- size2 <- respond bs | |
282 | -- go size2 | |
283 | -- {-# INLINABLE hGetN #-} | |
284 | ||
285 | {-| Stream bytes to 'stdout' | |
286 | ||
287 | Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe. | |
288 | ||
289 | Note: For best performance, use @(for source (liftIO . putStr))@ instead of | |
290 | @(source >-> stdout)@. | |
291 | -} | |
292 | stdout :: MonadIO m => Consumer' Text m () | |
293 | stdout = go | |
294 | where | |
295 | go = do | |
296 | txt <- await | |
297 | x <- liftIO $ try (T.putStr txt) | |
298 | case x of | |
299 | Left (G.IOError { G.ioe_type = G.ResourceVanished | |
300 | , G.ioe_errno = Just ioe }) | |
301 | | Errno ioe == ePIPE | |
302 | -> return () | |
303 | Left e -> liftIO (throwIO e) | |
304 | Right () -> go | |
305 | {-# INLINABLE stdout #-} | |
306 | ||
307 | stdoutLn :: (MonadIO m) => Consumer' Text m () | |
308 | stdoutLn = go | |
309 | where | |
310 | go = do | |
311 | str <- await | |
312 | x <- liftIO $ try (T.putStrLn str) | |
313 | case x of | |
314 | Left (G.IOError { G.ioe_type = G.ResourceVanished | |
315 | , G.ioe_errno = Just ioe }) | |
316 | | Errno ioe == ePIPE | |
317 | -> return () | |
318 | Left e -> liftIO (throwIO e) | |
319 | Right () -> go | |
320 | {-# INLINABLE stdoutLn #-} | |
321 | ||
322 | {-| Convert a byte stream into a 'Handle' | |
323 | ||
324 | Note: For best performance, use @(for source (liftIO . hPutStr handle))@ | |
325 | instead of @(source >-> toHandle handle)@. | |
326 | -} | |
327 | toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r | |
328 | toHandle h = for cat (liftIO . T.hPutStr h) | |
329 | {-# INLINABLE toHandle #-} | |
330 | ||
331 | writeFile :: (MonadSafe m, Base m ~ IO) => FilePath -> Consumer' Text m () | |
332 | writeFile file = Safe.withFile file IO.WriteMode toHandle | |
333 | ||
334 | -- | Apply a transformation to each 'Char' in the stream | |
335 | map :: (Monad m) => (Char -> Char) -> Pipe Text Text m r | |
336 | map f = P.map (T.map f) | |
337 | {-# INLINABLE map #-} | |
338 | ||
339 | -- | Map a function over the byte stream and concatenate the results | |
340 | concatMap | |
341 | :: (Monad m) => (Char -> Text) -> Pipe Text Text m r | |
342 | concatMap f = P.map (T.concatMap f) | |
343 | {-# INLINABLE concatMap #-} | |
344 | ||
345 | -- | @(take n)@ only allows @n@ bytes to pass | |
346 | take :: (Monad m, Integral a) => a -> Pipe Text Text m () | |
347 | take n0 = go n0 where | |
348 | go n | |
349 | | n <= 0 = return () | |
350 | | otherwise = do | |
351 | bs <- await | |
352 | let len = fromIntegral (T.length bs) | |
353 | if (len > n) | |
354 | then yield (T.take (fromIntegral n) bs) | |
355 | else do | |
356 | yield bs | |
357 | go (n - len) | |
358 | {-# INLINABLE take #-} | |
359 | ||
360 | -- | @(dropD n)@ drops the first @n@ bytes | |
361 | drop :: (Monad m, Integral a) => a -> Pipe Text Text m r | |
362 | drop n0 = go n0 where | |
363 | go n | |
364 | | n <= 0 = cat | |
365 | | otherwise = do | |
366 | bs <- await | |
367 | let len = fromIntegral (T.length bs) | |
368 | if (len >= n) | |
369 | then do | |
370 | yield (T.drop (fromIntegral n) bs) | |
371 | cat | |
372 | else go (n - len) | |
373 | {-# INLINABLE drop #-} | |
374 | ||
375 | -- | Take bytes until they fail the predicate | |
376 | takeWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m () | |
377 | takeWhile predicate = go | |
378 | where | |
379 | go = do | |
380 | bs <- await | |
381 | let (prefix, suffix) = T.span predicate bs | |
382 | if (T.null suffix) | |
383 | then do | |
384 | yield bs | |
385 | go | |
386 | else yield prefix | |
387 | {-# INLINABLE takeWhile #-} | |
388 | ||
389 | -- | Drop bytes until they fail the predicate | |
390 | dropWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r | |
391 | dropWhile predicate = go where | |
392 | go = do | |
393 | bs <- await | |
394 | case T.findIndex (not . predicate) bs of | |
395 | Nothing -> go | |
396 | Just i -> do | |
397 | yield (T.drop i bs) | |
398 | cat | |
399 | {-# INLINABLE dropWhile #-} | |
400 | ||
401 | -- | Only allows 'Char's to pass if they satisfy the predicate | |
402 | filter :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r | |
403 | filter predicate = P.map (T.filter predicate) | |
404 | {-# INLINABLE filter #-} | |
405 | ||
406 | -- | Stream all indices whose elements match the given 'Char' | |
407 | -- elemIndices :: (Monad m, Num n) => Char -> Pipe Text n m r | |
408 | -- elemIndices w8 = findIndices (w8 ==) | |
409 | -- {-# INLINABLE elemIndices #-} | |
410 | ||
411 | -- | Stream all indices whose elements satisfy the given predicate | |
412 | -- findIndices :: (Monad m, Num n) => (Char -> Bool) -> Pipe Text n m r | |
413 | -- findIndices predicate = go 0 | |
414 | -- where | |
415 | -- go n = do | |
416 | -- bs <- await | |
417 | -- each $ List.map (\i -> n + fromIntegral i) (T.findIndices predicate bs) | |
418 | -- go $! n + fromIntegral (T.length bs) | |
419 | -- {-# INLINABLE findIndices #-} | |
420 | ||
421 | -- | Strict left scan over the bytes | |
422 | scan | |
423 | :: (Monad m) | |
424 | => (Char -> Char -> Char) -> Char -> Pipe Text Text m r | |
425 | scan step begin = go begin | |
426 | where | |
427 | go w8 = do | |
428 | bs <- await | |
429 | let bs' = T.scanl step w8 bs | |
430 | w8' = T.last bs' | |
431 | yield bs' | |
432 | go w8' | |
433 | {-# INLINABLE scan #-} | |
434 | ||
435 | {-| Fold a pure 'Producer' of strict 'Text's into a lazy | |
436 | 'TL.Text' | |
437 | -} | |
438 | toLazy :: Producer Text Identity () -> TL.Text | |
439 | toLazy = TL.fromChunks . P.toList | |
440 | {-# INLINABLE toLazy #-} | |
441 | ||
442 | {-| Fold an effectful 'Producer' of strict 'Text's into a lazy | |
443 | 'TL.Text' | |
444 | ||
445 | Note: 'toLazyM' is not an idiomatic use of @pipes@, but I provide it for | |
446 | simple testing purposes. Idiomatic @pipes@ style consumes the chunks | |
447 | immediately as they are generated instead of loading them all into memory. | |
448 | -} | |
449 | toLazyM :: (Monad m) => Producer Text m () -> m TL.Text | |
450 | toLazyM = liftM TL.fromChunks . P.toListM | |
451 | {-# INLINABLE toLazyM #-} | |
452 | ||
453 | -- | Reduce the stream of bytes using a strict left fold | |
454 | fold | |
455 | :: Monad m | |
456 | => (x -> Char -> x) -> x -> (x -> r) -> Producer Text m () -> m r | |
457 | fold step begin done = P.fold (\x bs -> T.foldl' step x bs) begin done | |
458 | {-# INLINABLE fold #-} | |
459 | ||
460 | -- | Retrieve the first 'Char' | |
461 | head :: (Monad m) => Producer Text m () -> m (Maybe Char) | |
462 | head = go | |
463 | where | |
464 | go p = do | |
465 | x <- nextChar p | |
466 | case x of | |
467 | Left _ -> return Nothing | |
468 | Right (w8, _) -> return (Just w8) | |
469 | {-# INLINABLE head #-} | |
470 | ||
471 | -- | Retrieve the last 'Char' | |
472 | last :: (Monad m) => Producer Text m () -> m (Maybe Char) | |
473 | last = go Nothing | |
474 | where | |
475 | go r p = do | |
476 | x <- next p | |
477 | case x of | |
478 | Left () -> return r | |
479 | Right (bs, p') -> | |
480 | if (T.null bs) | |
481 | then go r p' | |
482 | else go (Just $ T.last bs) p' | |
483 | -- TODO: Change this to 'unsafeLast' when bytestring-0.10.2.0 | |
484 | -- becomes more widespread | |
485 | {-# INLINABLE last #-} | |
486 | ||
487 | -- | Determine if the stream is empty | |
488 | null :: (Monad m) => Producer Text m () -> m Bool | |
489 | null = P.all T.null | |
490 | {-# INLINABLE null #-} | |
491 | ||
492 | -- | Count the number of bytes | |
493 | length :: (Monad m, Num n) => Producer Text m () -> m n | |
494 | length = P.fold (\n bs -> n + fromIntegral (T.length bs)) 0 id | |
495 | {-# INLINABLE length #-} | |
496 | ||
497 | -- | Fold that returns whether 'M.Any' received 'Char's satisfy the predicate | |
498 | any :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool | |
499 | any predicate = P.any (T.any predicate) | |
500 | {-# INLINABLE any #-} | |
501 | ||
502 | -- | Fold that returns whether 'M.All' received 'Char's satisfy the predicate | |
503 | all :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool | |
504 | all predicate = P.all (T.all predicate) | |
505 | {-# INLINABLE all #-} | |
506 | ||
507 | -- | Return the maximum 'Char' within a byte stream | |
508 | maximum :: (Monad m) => Producer Text m () -> m (Maybe Char) | |
509 | maximum = P.fold step Nothing id | |
510 | where | |
511 | step mw8 bs = | |
512 | if (T.null bs) | |
513 | then mw8 | |
514 | else Just $ case mw8 of | |
515 | Nothing -> T.maximum bs | |
516 | Just w8 -> max w8 (T.maximum bs) | |
517 | {-# INLINABLE maximum #-} | |
518 | ||
519 | -- | Return the minimum 'Char' within a byte stream | |
520 | minimum :: (Monad m) => Producer Text m () -> m (Maybe Char) | |
521 | minimum = P.fold step Nothing id | |
522 | where | |
523 | step mw8 bs = | |
524 | if (T.null bs) | |
525 | then mw8 | |
526 | else case mw8 of | |
527 | Nothing -> Just (T.minimum bs) | |
528 | Just w8 -> Just (min w8 (T.minimum bs)) | |
529 | {-# INLINABLE minimum #-} | |
530 | ||
531 | -- | Determine whether any element in the byte stream matches the given 'Char' | |
532 | -- elem :: (Monad m) => Char -> Producer Text m () -> m Bool | |
533 | -- elem w8 = P.any (T.elem w8) | |
534 | -- {-# INLINABLE elem #-} | |
535 | -- | |
536 | -- {-| Determine whether all elements in the byte stream do not match the given | |
537 | -- 'Char' | |
538 | -- -} | |
539 | -- notElem :: (Monad m) => Char -> Producer Text m () -> m Bool | |
540 | -- notElem w8 = P.all (T.notElem w8) | |
541 | -- {-# INLINABLE notElem #-} | |
542 | ||
543 | -- | Find the first element in the stream that matches the predicate | |
544 | find | |
545 | :: (Monad m) | |
546 | => (Char -> Bool) -> Producer Text m () -> m (Maybe Char) | |
547 | find predicate p = head (p >-> filter predicate) | |
548 | {-# INLINABLE find #-} | |
549 | ||
550 | -- | Index into a byte stream | |
551 | index | |
552 | :: (Monad m, Integral a) | |
553 | => a-> Producer Text m () -> m (Maybe Char) | |
554 | index n p = head (p >-> drop n) | |
555 | {-# INLINABLE index #-} | |
556 | ||
557 | -- | Find the index of an element that matches the given 'Char' | |
558 | -- elemIndex | |
559 | -- :: (Monad m, Num n) => Char -> Producer Text m () -> m (Maybe n) | |
560 | -- elemIndex w8 = findIndex (w8 ==) | |
561 | -- {-# INLINABLE elemIndex #-} | |
562 | ||
563 | -- | Store the first index of an element that satisfies the predicate | |
564 | -- findIndex | |
565 | -- :: (Monad m, Num n) | |
566 | -- => (Char -> Bool) -> Producer Text m () -> m (Maybe n) | |
567 | -- findIndex predicate p = P.head (p >-> findIndices predicate) | |
568 | -- {-# INLINABLE findIndex #-} | |
569 | -- | |
570 | -- -- | Store a tally of how many elements match the given 'Char' | |
571 | -- count :: (Monad m, Num n) => Char -> Producer Text m () -> m n | |
572 | -- count w8 p = P.fold (+) 0 id (p >-> P.map (fromIntegral . T.count w8)) | |
573 | -- {-# INLINABLE count #-} | |
574 | ||
575 | -- | Splits a 'Producer' after the given number of bytes | |
576 | splitAt | |
577 | :: (Monad m, Integral n) | |
578 | => n | |
579 | -> Producer Text m r | |
580 | -> Producer' Text m (Producer Text m r) | |
581 | splitAt = go | |
582 | where | |
583 | go 0 p = return p | |
584 | go n p = do | |
585 | x <- lift (next p) | |
586 | case x of | |
587 | Left r -> return (return r) | |
588 | Right (bs, p') -> do | |
589 | let len = fromIntegral (T.length bs) | |
590 | if (len <= n) | |
591 | then do | |
592 | yield bs | |
593 | go (n - len) p' | |
594 | else do | |
595 | let (prefix, suffix) = T.splitAt (fromIntegral n) bs | |
596 | yield prefix | |
597 | return (yield suffix >> p') | |
598 | {-# INLINABLE splitAt #-} | |
599 | ||
600 | -- | Split a byte stream into 'FreeT'-delimited byte streams of fixed size | |
601 | chunksOf | |
602 | :: (Monad m, Integral n) | |
603 | => n -> Producer Text m r -> FreeT (Producer Text m) m r | |
604 | chunksOf n p0 = PP.FreeT (go p0) | |
605 | where | |
606 | go p = do | |
607 | x <- next p | |
608 | return $ case x of | |
609 | Left r -> PP.Pure r | |
610 | Right (bs, p') -> PP.Free $ do | |
611 | p'' <- splitAt n (yield bs >> p') | |
612 | return $ PP.FreeT (go p'') | |
613 | {-# INLINABLE chunksOf #-} | |
614 | ||
615 | {-| Split a byte stream in two, where the first byte stream is the longest | |
616 | consecutive group of bytes that satisfy the predicate | |
617 | -} | |
618 | span | |
619 | :: (Monad m) | |
620 | => (Char -> Bool) | |
621 | -> Producer Text m r | |
622 | -> Producer' Text m (Producer Text m r) | |
623 | span predicate = go | |
624 | where | |
625 | go p = do | |
626 | x <- lift (next p) | |
627 | case x of | |
628 | Left r -> return (return r) | |
629 | Right (bs, p') -> do | |
630 | let (prefix, suffix) = T.span predicate bs | |
631 | if (T.null suffix) | |
632 | then do | |
633 | yield bs | |
634 | go p' | |
635 | else do | |
636 | yield prefix | |
637 | return (yield suffix >> p') | |
638 | {-# INLINABLE span #-} | |
639 | ||
640 | {-| Split a byte stream in two, where the first byte stream is the longest | |
641 | consecutive group of bytes that don't satisfy the predicate | |
642 | -} | |
643 | break | |
644 | :: (Monad m) | |
645 | => (Char -> Bool) | |
646 | -> Producer Text m r | |
647 | -> Producer Text m (Producer Text m r) | |
648 | break predicate = span (not . predicate) | |
649 | {-# INLINABLE break #-} | |
650 | ||
651 | {-| Split a byte stream into sub-streams delimited by bytes that satisfy the | |
652 | predicate | |
653 | -} | |
654 | splitWith | |
655 | :: (Monad m) | |
656 | => (Char -> Bool) | |
657 | -> Producer Text m r | |
658 | -> PP.FreeT (Producer Text m) m r | |
659 | splitWith predicate p0 = PP.FreeT (go0 p0) | |
660 | where | |
661 | go0 p = do | |
662 | x <- next p | |
663 | case x of | |
664 | Left r -> return (PP.Pure r) | |
665 | Right (bs, p') -> | |
666 | if (T.null bs) | |
667 | then go0 p' | |
668 | else return $ PP.Free $ do | |
669 | p'' <- span (not . predicate) (yield bs >> p') | |
670 | return $ PP.FreeT (go1 p'') | |
671 | go1 p = do | |
672 | x <- nextChar p | |
673 | return $ case x of | |
674 | Left r -> PP.Pure r | |
675 | Right (_, p') -> PP.Free $ do | |
676 | p'' <- span (not . predicate) p' | |
677 | return $ PP.FreeT (go1 p'') | |
678 | {-# INLINABLE splitWith #-} | |
679 | ||
680 | -- | Split a byte stream using the given 'Char' as the delimiter | |
681 | split :: (Monad m) | |
682 | => Char | |
683 | -> Producer Text m r | |
684 | -> FreeT (Producer Text m) m r | |
685 | split w8 = splitWith (w8 ==) | |
686 | {-# INLINABLE split #-} | |
687 | ||
688 | {-| Group a byte stream into 'FreeT'-delimited byte streams using the supplied | |
689 | equality predicate | |
690 | -} | |
691 | groupBy | |
692 | :: (Monad m) | |
693 | => (Char -> Char -> Bool) | |
694 | -> Producer Text m r | |
695 | -> FreeT (Producer Text m) m r | |
696 | groupBy equal p0 = PP.FreeT (go p0) | |
697 | where | |
698 | go p = do | |
699 | x <- next p | |
700 | case x of | |
701 | Left r -> return (PP.Pure r) | |
702 | Right (bs, p') -> case (T.uncons bs) of | |
703 | Nothing -> go p' | |
704 | Just (w8, _) -> do | |
705 | return $ PP.Free $ do | |
706 | p'' <- span (equal w8) (yield bs >> p') | |
707 | return $ PP.FreeT (go p'') | |
708 | {-# INLINABLE groupBy #-} | |
709 | ||
710 | -- | Group a byte stream into 'FreeT'-delimited byte streams of identical bytes | |
711 | group | |
712 | :: (Monad m) => Producer Text m r -> FreeT (Producer Text m) m r | |
713 | group = groupBy (==) | |
714 | {-# INLINABLE group #-} | |
715 | ||
716 | {-| Split a byte stream into 'FreeT'-delimited lines | |
717 | ||
718 | Note: This function is purely for demonstration purposes since it assumes a | |
719 | particular encoding. You should prefer the 'Data.Text.Text' equivalent of | |
720 | this function from the upcoming @pipes-text@ library. | |
721 | -} | |
722 | lines | |
723 | :: (Monad m) => Producer Text m r -> FreeT (Producer Text m) m r | |
724 | lines p0 = PP.FreeT (go0 p0) | |
725 | where | |
726 | go0 p = do | |
727 | x <- next p | |
728 | case x of | |
729 | Left r -> return (PP.Pure r) | |
730 | Right (bs, p') -> | |
731 | if (T.null bs) | |
732 | then go0 p' | |
733 | else return $ PP.Free $ go1 (yield bs >> p') | |
734 | go1 p = do | |
735 | p' <- break ('\n' ==) p | |
736 | return $ PP.FreeT (go2 p') | |
737 | go2 p = do | |
738 | x <- nextChar p | |
739 | return $ case x of | |
740 | Left r -> PP.Pure r | |
741 | Right (_, p') -> PP.Free (go1 p') | |
742 | {-# INLINABLE lines #-} | |
743 | nextChar = undefined | |
744 | {-| Split a byte stream into 'FreeT'-delimited words | |
745 | ||
746 | Note: This function is purely for demonstration purposes since it assumes a | |
747 | particular encoding. You should prefer the 'Data.Text.Text' equivalent of | |
748 | this function from the upcoming @pipes-text@ library. | |
749 | -} | |
750 | words | |
751 | :: (Monad m) => Producer Text m r -> FreeT (Producer Text m) m r | |
752 | words p0 = removeEmpty (splitWith isSpace p0) | |
753 | where | |
754 | removeEmpty f = PP.FreeT $ do | |
755 | x <- PP.runFreeT f | |
756 | case x of | |
757 | PP.Pure r -> return (PP.Pure r) | |
758 | PP.Free p -> do | |
759 | y <- next p | |
760 | case y of | |
761 | Left f' -> PP.runFreeT (removeEmpty f') | |
762 | Right (bs, p') -> return $ PP.Free $ do | |
763 | yield bs | |
764 | f' <- p' | |
765 | return (removeEmpty f') | |
766 | {-# INLINABLE words #-} | |
767 | ||
768 | -- | Intersperse a 'Char' in between the bytes of the byte stream | |
769 | intersperse | |
770 | :: (Monad m) => Char -> Producer Text m r -> Producer Text m r | |
771 | intersperse w8 = go0 | |
772 | where | |
773 | go0 p = do | |
774 | x <- lift (next p) | |
775 | case x of | |
776 | Left r -> return r | |
777 | Right (bs, p') -> do | |
778 | yield (T.intersperse w8 bs) | |
779 | go1 p' | |
780 | go1 p = do | |
781 | x <- lift (next p) | |
782 | case x of | |
783 | Left r -> return r | |
784 | Right (bs, p') -> do | |
785 | yield (T.singleton w8) | |
786 | yield (T.intersperse w8 bs) | |
787 | go1 p' | |
788 | {-# INLINABLE intersperse #-} | |
789 | ||
790 | {-| 'intercalate' concatenates the 'FreeT'-delimited byte streams after | |
791 | interspersing a byte stream in between them | |
792 | -} | |
793 | intercalate | |
794 | :: (Monad m) | |
795 | => Producer Text m () | |
796 | -> FreeT (Producer Text m) m r | |
797 | -> Producer Text m r | |
798 | intercalate p0 = go0 | |
799 | where | |
800 | go0 f = do | |
801 | x <- lift (PP.runFreeT f) | |
802 | case x of | |
803 | PP.Pure r -> return r | |
804 | PP.Free p -> do | |
805 | f' <- p | |
806 | go1 f' | |
807 | go1 f = do | |
808 | x <- lift (PP.runFreeT f) | |
809 | case x of | |
810 | PP.Pure r -> return r | |
811 | PP.Free p -> do | |
812 | p0 | |
813 | f' <- p | |
814 | go1 f' | |
815 | {-# INLINABLE intercalate #-} | |
816 | ||
817 | {-| Join 'FreeT'-delimited lines into a byte stream | |
818 | ||
819 | Note: This function is purely for demonstration purposes since it assumes a | |
820 | particular encoding. You should prefer the 'Data.Text.Text' equivalent of | |
821 | this function from the upcoming @pipes-text@ library. | |
822 | -} | |
823 | unlines | |
824 | :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r | |
825 | unlines = go | |
826 | where | |
827 | go f = do | |
828 | x <- lift (PP.runFreeT f) | |
829 | case x of | |
830 | PP.Pure r -> return r | |
831 | PP.Free p -> do | |
832 | f' <- p | |
833 | yield $ T.singleton '\n' | |
834 | go f' | |
835 | {-# INLINABLE unlines #-} | |
836 | ||
837 | {-| Join 'FreeT'-delimited words into a byte stream | |
838 | ||
839 | Note: This function is purely for demonstration purposes since it assumes a | |
840 | particular encoding. You should prefer the 'Data.Text.Text' equivalent of | |
841 | this function from the upcoming @pipes-text@ library. | |
842 | -} | |
843 | unwords | |
844 | :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r | |
845 | unwords = intercalate (yield $ T.pack " ") | |
846 | {-# INLINABLE unwords #-} | |
847 | ||
848 | {- $parse | |
849 | The following parsing utilities are single-byte analogs of the ones found | |
850 | in @pipes-parse@. | |
851 | -} | |
852 | ||
853 | {-| Take bytes until they fail the predicate | |
854 | ||
855 | Unlike 'takeWhile', this 'PP.unDraw's unused bytes | |
856 | -} | |
857 | -- takeWhile' | |
858 | -- :: (Monad m) | |
859 | -- => (Char -> Bool) | |
860 | -- -> Pipe Text Text (StateT (Producer Text m r) m) () | |
861 | -- takeWhile' = PBP.takeWhile | |
862 | -- {-# INLINABLE takeWhile' #-} | |
863 | -- {-# DEPRECATED takeWhile' "Use Pipes.Text.Parse.takeWhile instead" #-} | |
864 | ||
865 | {- $reexports | |
866 | "Pipes.Text.Parse" re-exports 'nextByte', 'drawByte', 'unDrawByte', | |
867 | 'peekByte', and 'isEndOfBytes'. | |
868 | ||
869 | @Data.Text@ re-exports the 'Text' type. | |
870 | ||
871 | @Data.Word@ re-exports the 'Char' type. | |
872 | ||
873 | @Pipes.Parse@ re-exports 'input', 'concat', and 'FreeT' (the type). | |
874 | -} |