]> git.immae.eu Git - github/fretlink/text-pipes.git/blob - Pipes/Text/IO.hs
23aff6956422c08fb56bdf23cda50f7f33b089cf
[github/fretlink/text-pipes.git] / Pipes / Text / IO.hs
1 {-#LANGUAGE RankNTypes#-}
2
3
4 module Pipes.Text.IO
5 (
6 -- * Text IO
7 -- $textio
8
9 -- * Caveats
10 -- $caveats
11
12 -- * Producers
13 fromHandle
14 , stdin
15 , readFile
16 -- * Consumers
17 , toHandle
18 , stdout
19 , writeFile
20 ) where
21
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
29 import Pipes
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)
34
35 {- $textio
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.
39
40 The main points are as in
41 <https://hackage.haskell.org/package/pipes-bytestring-1.0.0/docs/Pipes-ByteString.html Pipes.ByteString>
42
43 An 'IO.Handle' can be associated with a 'Producer' or 'Consumer' according
44 as it is read or written to.
45
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:
48
49 > import Pipes
50 > import qualified Pipes.Text as Text
51 > import qualified Pipes.Text.IO as Text
52 > import System.IO
53 >
54 > main =
55 > withFile "inFile.txt" ReadMode $ \hIn ->
56 > withFile "outFile.txt" WriteMode $ \hOut ->
57 > runEffect $ Text.fromHandle hIn >-> Text.toHandle hOut
58
59 To stream from files, the following is perhaps more Prelude-like (note that it uses Pipes.Safe):
60
61 > import Pipes
62 > import qualified Pipes.Text as Text
63 > import qualified Pipes.Text.IO as Text
64 > import Pipes.Safe
65 >
66 > main = runSafeT $ runEffect $ Text.readFile "inFile.txt" >-> Text.writeFile "outFile.txt"
67
68 You can stream to and from 'stdin' and 'stdout' using the predefined 'stdin'
69 and 'stdout' pipes, as with the following \"echo\" program:
70
71 > main = runEffect $ Text.stdin >-> Text.stdout
72
73 -}
74
75
76 {- $caveats
77
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.)
81
82 * Like the functions in @Data.Text.IO@, they attempt to work with the system encoding.
83
84 * Like the functions in @Data.Text.IO@, they significantly 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@
87
88 * Like the functions in @Data.Text.IO@ , they use Text exceptions, not the standard Pipes protocols.
89
90 Something like
91
92 > view utf8 . Bytes.fromHandle :: Handle -> Producer Text IO (Producer ByteString m ())
93
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.)
97
98 By contrast, something like
99
100 > Text.fromHandle :: Handle -> Producer Text IO ()
101
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.
105
106
107 -}
108
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
114 -}
115
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 ()
120 else do yield txt
121 go
122 {-# INLINABLE fromHandle#-}
123
124 -- | Stream text from 'stdin'
125 stdin :: MonadIO m => Producer Text m ()
126 stdin = fromHandle IO.stdin
127 {-# INLINE stdin #-}
128
129
130 {-| Stream text from a file in the simple fashion of @Data.Text.IO@
131
132 >>> runSafeT $ runEffect $ Text.readFile "hello.hs" >-> Text.map toUpper >-> hoist lift Text.stdout
133 MAIN = PUTSTRLN "HELLO WORLD"
134 -}
135
136 readFile :: MonadSafe m => FilePath -> Producer Text m ()
137 readFile file = Safe.withFile file IO.ReadMode fromHandle
138 {-# INLINE readFile #-}
139
140
141 {-| Stream text to 'stdout'
142
143 Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
144
145 Note: For best performance, it might be best just to use @(for source (liftIO . putStr))@
146 instead of @(source >-> stdout)@ .
147 -}
148 stdout :: MonadIO m => Consumer' Text m ()
149 stdout = go
150 where
151 go = do
152 txt <- await
153 x <- liftIO $ try (T.putStr txt)
154 case x of
155 Left (G.IOError { G.ioe_type = G.ResourceVanished
156 , G.ioe_errno = Just ioe })
157 | Errno ioe == ePIPE
158 -> return ()
159 Left e -> liftIO (throwIO e)
160 Right () -> go
161 {-# INLINABLE stdout #-}
162
163
164 {-| Convert a text stream into a 'Handle'
165
166 Note: again, for best performance, where possible use
167 @(for source (liftIO . hPutStr handle))@ instead of @(source >-> toHandle handle)@.
168 -}
169 toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r
170 toHandle h = for cat (liftIO . T.hPutStr h)
171 {-# INLINABLE toHandle #-}
172
173 {-# RULES "p >-> toHandle h" forall p h .
174 p >-> toHandle h = for p (\txt -> liftIO (T.hPutStr h txt))
175 #-}
176
177
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 #-}