aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Pipes/Text/IO.hs220
1 files changed, 177 insertions, 43 deletions
diff --git a/Pipes/Text/IO.hs b/Pipes/Text/IO.hs
index 51c6926..d30f13c 100644
--- a/Pipes/Text/IO.hs
+++ b/Pipes/Text/IO.hs
@@ -3,22 +3,39 @@
3 3
4module Pipes.Text.IO 4module Pipes.Text.IO
5 ( 5 (
6 -- * Text IO 6 -- * Simple line-based Text IO
7 -- $lineio
8
9 fromHandleLn
10 , toHandleLn
11 , stdinLn
12 , stdoutLn
13 , stdoutLn'
14 , readFileLn
15 , writeFileLn
16
17
18 -- * Simple streaming text IO
7 -- $textio 19 -- $textio
8 20
9 -- * Caveats 21 -- * Caveats
10 -- $caveats 22 -- $caveats
11 23
12 -- * Producers 24 -- * Producers
13 fromHandle 25 , fromHandle
14 , fromHandleLn
15 , stdin 26 , stdin
16 , readFile 27 , readFile
17 , readFileLn 28
18 -- * Consumers 29 -- * Consumers
19 , toHandle 30 , toHandle
20 , stdout 31 , stdout
21 , writeFile 32 , writeFile
33
34 -- * Re-exports
35 , MonadSafe(..)
36 , runSafeT
37 , runSafeP
38 , Safe.withFile
22 ) where 39 ) where
23 40
24import qualified System.IO as IO 41import qualified System.IO as IO
@@ -30,14 +47,164 @@ import qualified Data.Text as T
30import qualified Data.Text.IO as T 47import qualified Data.Text.IO as T
31import Pipes 48import Pipes
32import qualified Pipes.Safe.Prelude as Safe 49import qualified Pipes.Safe.Prelude as Safe
33import Pipes.Safe (MonadSafe(..)) 50import Pipes.Safe (MonadSafe(..), runSafeT, runSafeP)
34import Prelude hiding (readFile, writeFile) 51import Prelude hiding (readFile, writeFile)
35 52
53{- $lineio
54 Line-based operations are marked with a final \-@Ln@, like 'stdinLn', 'readFileLn'. They are
55 drop-in replacements for the line-based operations in @Pipes.Prelude@ and
56 @Pipes.Safe.Prelude@ - the final \-@Ln@ being added where necessary.
57 With them, one is producing, piping and consuming semantically significant individual texts,
58 understood as lines, just as one would pipe 'Int's. The standard materials from @Pipes@ and @Pipes.Prelude@ and
59 @Data.Text@ are all you need to interact with these lines as you read or write them.
60 You can use these operations without using any of the other material in this package.
61
62 Thus, to take a trivial case, here we upper-case three lines from standard input and write
63 them to a file.
64
65>>> import Pipes
66>>> import qualified Pipes.Prelude as P
67>>> import qualified Pipes.Text.IO as Text
68>>> import qualified Data.Text as T
69>>> Text.runSafeT $ runEffect $ Text.stdinLn >-> P.take 3 >-> P.map T.toUpper >-> Text.writeFileLn "threelines.txt"
70one<Enter>
71two<Enter>
72three<Enter>
73>>> :! cat "threelines.txt"
74ONE
75TWO
76THREE
77
78 The point of view is very much that of @Pipes.Prelude@ and the user who needs no more
79 can use them ignoring the rest of this package.
80
81 The line-based operations are, however, subject to a number of caveats.
82 First, where they read from a handle, they will of course happily
83 accumulate indefinitely long lines. This is likely to be legitimate for input
84 typed in by a user, and for locally produced log files and other known material, but
85 otherwise not. See the post on
86 <http://www.haskellforall.com/2013/09/perfect-streaming-using-pipes-bytestring.html perfect streaming>
87 to see why @pipes-bytestring@ and this package take a different approach. Furthermore,
88 like those in @Data.Text.IO@, the operations use the system encoding and @T.hGetLine@
89 and thus are slower than the \'official\' route, which would use bytestring IO and
90 the encoding and decoding functions in @Pipes.Text.Encoding@. Finally, they will generate
91 text exceptions after the fashion of @Data.Text.Encoding@ rather than returning the
92 undigested bytes in the style of @Pipes.Text.Encoding@
93
94-}
95
96
97{-| Read separate lines of 'Text' from 'IO.stdin' using 'T.getLine'
98 This function will accumulate indefinitely long strict 'Text's. See the caveats above.
99
100 Terminates on end of input
101-}
102stdinLn :: MonadIO m => Producer' T.Text m ()
103stdinLn = fromHandleLn IO.stdin
104{-# INLINABLE stdinLn #-}
105
106
107{-| Write 'String's to 'IO.stdout' using 'putStrLn'
108
109 Unlike 'toHandle', 'stdoutLn' gracefully terminates on a broken output pipe
110-}
111stdoutLn :: MonadIO m => Consumer' T.Text m ()
112stdoutLn = go
113 where
114 go = do
115 str <- await
116 x <- liftIO $ try (T.putStrLn str)
117 case x of
118 Left (G.IOError { G.ioe_type = G.ResourceVanished
119 , G.ioe_errno = Just ioe })
120 | Errno ioe == ePIPE
121 -> return ()
122 Left e -> liftIO (throwIO e)
123 Right () -> go
124{-# INLINABLE stdoutLn #-}
125
126{-| Write lines of 'Text's to 'IO.stdout'.
127
128 This does not handle a broken output pipe, but has a polymorphic return
129 value.
130-}
131stdoutLn' :: MonadIO m => Consumer' T.Text m r
132stdoutLn' = for cat (\str -> liftIO (T.putStrLn str))
133{-# INLINABLE stdoutLn' #-}
134
135{-# RULES
136 "p >-> stdoutLn'" forall p .
137 p >-> stdoutLn' = for p (\str -> liftIO (T.putStrLn str))
138 #-}
139
140{-| Read separate lines of 'Text' from a 'IO.Handle' using 'T.hGetLine'.
141 This operation will accumulate indefinitely large strict texts. See the caveats above.
142
143 Terminates on end of input
144-}
145fromHandleLn :: MonadIO m => IO.Handle -> Producer' Text m ()
146fromHandleLn h = go where
147 getLine :: IO (Either G.IOException Text)
148 getLine = try (T.hGetLine h)
149
150 go = do txt <- liftIO getLine
151 case txt of
152 Left e -> return ()
153 Right y -> do yield y
154 go
155{-# INLINABLE fromHandleLn #-}
156
157-- to do: investigate differences from the above:
158-- fromHandleLn :: MonadIO m => IO.Handle -> Producer' T.Text m ()
159-- fromHandleLn h = go
160-- where
161-- go = do
162-- eof <- liftIO $ IO.hIsEOF h
163-- unless eof $ do
164-- str <- liftIO $ T.hGetLine h
165-- yield str
166-- go
167-- {-# INLINABLE fromHandleLn #-}
168
169
170-- | Write separate lines of 'Text' to a 'IO.Handle' using 'T.hPutStrLn'
171toHandleLn :: MonadIO m => IO.Handle -> Consumer' T.Text m r
172toHandleLn handle = for cat (\str -> liftIO (T.hPutStrLn handle str))
173{-# INLINABLE toHandleLn #-}
174
175{-# RULES
176 "p >-> toHandleLn handle" forall p handle .
177 p >-> toHandleLn handle = for p (\str -> liftIO (T.hPutStrLn handle str))
178 #-}
179
180
181{-| Stream separate lines of text from a file. This operation will accumulate
182 indefinitely long strict text chunks. See the caveats above.
183-}
184readFileLn :: MonadSafe m => FilePath -> Producer Text m ()
185readFileLn file = Safe.withFile file IO.ReadMode fromHandleLn
186{-# INLINE readFileLn #-}
187
188
189
190{-| Write lines to a file, automatically opening and closing the file as
191 necessary
192-}
193writeFileLn :: (MonadSafe m) => FilePath -> Consumer' Text m r
194writeFileLn file = Safe.withFile file IO.WriteMode toHandleLn
195{-# INLINABLE writeFileLn #-}
196
197
198
36{- $textio 199{- $textio
37 Where pipes @IO@ replaces lazy @IO@, @Producer Text IO r@ replaces lazy 'Text'. 200 Where pipes @IO@ replaces lazy @IO@, @Producer Text IO r@ replaces lazy 'Text'.
38 This module exports some convenient functions for producing and consuming 201 The official IO of this package and the pipes ecosystem generally would use the
39 pipes 'Text' in @IO@, namely, 'readFile', 'writeFile', 'fromHandle', 'toHandle', 202 IO functions in @Pipes.ByteString@ and the encoding and decoding material in
40 'stdin' and 'stdout'. Some caveats described below. 203 @Pipes.Text.Encoding@.
204
205 The streaming functions exported here, namely, 'readFile', 'writeFile', 'fromHandle', 'toHandle',
206 'stdin' and 'stdout' simplify this and use the system encoding on the model of @Data.Text.IO@
207 and @Data.Text.Lazy.IO@ Some caveats described below.
41 208
42 The main points are as in 209 The main points are as in
43 <https://hackage.haskell.org/package/pipes-bytestring-1.0.0/docs/Pipes-ByteString.html Pipes.ByteString>: 210 <https://hackage.haskell.org/package/pipes-bytestring-1.0.0/docs/Pipes-ByteString.html Pipes.ByteString>:
@@ -69,6 +236,8 @@ To stream from files, the following is perhaps more Prelude-like (note that it u
69 236
70> main = runEffect $ Text.stdin >-> Text.stdout 237> main = runEffect $ Text.stdin >-> Text.stdout
71 238
239 These programs, unlike the corresponding programs written with the line-based functions,
240 will pass along a 1 terabyte line without affecting memory use.
72 241
73-} 242-}
74 243
@@ -87,23 +256,6 @@ To stream from files, the following is perhaps more Prelude-like (note that it u
87 256
88 * Like the functions in @Data.Text.IO@ , they use Text exceptions, not the standard Pipes protocols. 257 * Like the functions in @Data.Text.IO@ , they use Text exceptions, not the standard Pipes protocols.
89 258
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-} 259-}
108 260
109{-| Convert a 'IO.Handle' into a text stream using a text size 261{-| Convert a 'IO.Handle' into a text stream using a text size
@@ -122,18 +274,6 @@ fromHandle h = go where
122{-# INLINABLE fromHandle#-} 274{-# INLINABLE fromHandle#-}
123 275
124 276
125fromHandleLn :: MonadIO m => IO.Handle -> Producer Text m ()
126fromHandleLn h = go where
127 getLine :: IO (Either G.IOException Text)
128 getLine = try (T.hGetLine h)
129
130 go = do txt <- liftIO getLine
131 case txt of
132 Left e -> return ()
133 Right y -> do yield y
134 go
135{-# INLINABLE fromHandleLn #-}
136
137-- | Stream text from 'stdin' 277-- | Stream text from 'stdin'
138stdin :: MonadIO m => Producer Text m () 278stdin :: MonadIO m => Producer Text m ()
139stdin = fromHandle IO.stdin 279stdin = fromHandle IO.stdin
@@ -151,12 +291,6 @@ readFile file = Safe.withFile file IO.ReadMode fromHandle
151{-# INLINE readFile #-} 291{-# INLINE readFile #-}
152 292
153 293
154{-| Stream lines of text from a file
155-}
156readFileLn :: MonadSafe m => FilePath -> Producer Text m ()
157readFileLn file = Safe.withFile file IO.ReadMode fromHandleLn
158{-# INLINE readFileLn #-}
159
160 294
161{-| Stream text to 'stdout' 295{-| Stream text to 'stdout'
162 296