1 {-#LANGUAGE RankNTypes#-}
22 import qualified System.IO as IO
23 import Control.Exception (throwIO, try)
24 import Foreign.C.Error (Errno(Errno), ePIPE)
25 import qualified GHC.IO.Exception as G
26 import Data.Text (Text)
27 import qualified Data.Text as T
28 import qualified Data.Text.IO as T
30 import qualified Pipes.Safe.Prelude as Safe
31 import qualified Pipes.Safe as Safe
32 import Pipes.Safe (MonadSafe(..), Base(..))
33 import Prelude hiding (readFile, writeFile)
36 Where pipes IO replaces lazy IO, @Producer Text m r@ replaces lazy 'Text'.
37 This module exports some convenient functions for producing and consuming
38 pipes 'Text' in IO, with caveats described below.
40 The main points are as in
41 <https://hackage.haskell.org/package/pipes-bytestring-1.0.0/docs/Pipes-ByteString.html Pipes.ByteString>
43 An 'IO.Handle' can be associated with a 'Producer' or 'Consumer' according
44 as it is read or written to.
46 To stream to or from 'IO.Handle's, one can use 'fromHandle' or 'toHandle'. For
47 example, the following program copies a document from one file to another:
50 > import qualified Pipes.Text as Text
51 > import qualified Pipes.Text.IO as Text
55 > withFile "inFile.txt" ReadMode $ \hIn ->
56 > withFile "outFile.txt" WriteMode $ \hOut ->
57 > runEffect $ Text.fromHandle hIn >-> Text.toHandle hOut
59 To stream from files, the following is perhaps more Prelude-like (note that it uses Pipes.Safe):
62 > import qualified Pipes.Text as Text
63 > import qualified Pipes.Text.IO as Text
66 > main = runSafeT $ runEffect $ Text.readFile "inFile.txt" >-> Text.writeFile "outFile.txt"
68 You can stream to and from 'stdin' and 'stdout' using the predefined 'stdin'
69 and 'stdout' pipes, as with the following \"echo\" program:
71 > main = runEffect $ Text.stdin >-> Text.stdout
78 The operations exported here are a convenience, like the similar operations in
79 @Data.Text.IO@ (or rather, @Data.Text.Lazy.IO@, since, again, @Producer Text m r@ is
80 'effectful text' and something like the pipes equivalent of lazy Text.)
82 * Like the functions in @Data.Text.IO@, they attempt to work with the system encoding.
84 * Like the functions in @Data.Text.IO@, they are slower than ByteString operations. Where
85 you know what encoding you are working with, use @Pipes.ByteString@ and @Pipes.Text.Encoding@ instead,
86 e.g. @view utf8 Bytes.stdin@ instead of @Text.stdin@
88 * Like the functions in @Data.Text.IO@ , they use Text exceptions.
92 > view utf8 . Bytes.fromHandle :: Handle -> Producer Text IO (Producer ByteString m ())
94 yields a stream of Text, and follows
95 standard pipes protocols by reverting to (i.e. returning) the underlying byte stream
96 upon reaching any decoding error. (See especially the pipes-binary package.)
98 By contrast, something like
100 > Text.fromHandle :: Handle -> Producer Text IO ()
102 supplies a stream of text returning '()', which is convenient for many tasks,
103 but violates the pipes @pipes-binary@ approach to decoding errors and
104 throws an exception of the kind characteristic of the @text@ library instead.
109 {-| Convert a 'IO.Handle' into a text stream using a text size
110 determined by the good sense of the text library. Note with the remarks
111 at the head of this module that this
112 is slower than @view utf8 (Pipes.ByteString.fromHandle h)@
113 but uses the system encoding and has other nice @Data.Text.IO@ features
116 fromHandle :: MonadIO m => IO.Handle -> Producer Text m ()
117 fromHandle h = go where
118 go = do txt <- liftIO (T.hGetChunk h)
119 if T.null txt then return ()
122 {-# INLINABLE fromHandle#-}
124 -- | Stream text from 'stdin'
125 stdin :: MonadIO m => Producer Text m ()
126 stdin = fromHandle IO.stdin
130 {-| Stream text from a file in the simple fashion of @Data.Text.IO@
132 >>> runSafeT $ runEffect $ Text.readFile "hello.hs" >-> Text.map toUpper >-> hoist lift Text.stdout
133 MAIN = PUTSTRLN "HELLO WORLD"
136 readFile :: MonadSafe m => FilePath -> Producer Text m ()
137 readFile file = Safe.withFile file IO.ReadMode fromHandle
138 {-# INLINE readFile #-}
141 {-| Stream text to 'stdout'
143 Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
145 Note: For best performance, it might be best just to use @(for source (liftIO . putStr))@
146 instead of @(source >-> stdout)@ .
148 stdout :: MonadIO m => Consumer' Text m ()
153 x <- liftIO $ try (T.putStr txt)
155 Left (G.IOError { G.ioe_type = G.ResourceVanished
156 , G.ioe_errno = Just ioe })
159 Left e -> liftIO (throwIO e)
161 {-# INLINABLE stdout #-}
164 {-| Convert a text stream into a 'Handle'
166 Note: again, for best performance, where possible use
167 @(for source (liftIO . hPutStr handle))@ instead of @(source >-> toHandle handle)@.
169 toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r
170 toHandle h = for cat (liftIO . T.hPutStr h)
171 {-# INLINABLE toHandle #-}
173 {-# RULES "p >-> toHandle h" forall p h .
174 p >-> toHandle h = for p (\txt -> liftIO (T.hPutStr h txt))
178 -- | Stream text into a file. Uses @pipes-safe@.
179 writeFile :: (MonadSafe m) => FilePath -> Consumer' Text m ()
180 writeFile file = Safe.withFile file IO.WriteMode toHandle
181 {-# INLINE writeFile #-}