diff options
-rw-r--r-- | Pipes/Text/IO.hs | 220 |
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 | ||
4 | module Pipes.Text.IO | 4 | module 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 | ||
24 | import qualified System.IO as IO | 41 | import qualified System.IO as IO |
@@ -30,14 +47,164 @@ import qualified Data.Text as T | |||
30 | import qualified Data.Text.IO as T | 47 | import qualified Data.Text.IO as T |
31 | import Pipes | 48 | import Pipes |
32 | import qualified Pipes.Safe.Prelude as Safe | 49 | import qualified Pipes.Safe.Prelude as Safe |
33 | import Pipes.Safe (MonadSafe(..)) | 50 | import Pipes.Safe (MonadSafe(..), runSafeT, runSafeP) |
34 | import Prelude hiding (readFile, writeFile) | 51 | import 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" | ||
70 | one<Enter> | ||
71 | two<Enter> | ||
72 | three<Enter> | ||
73 | >>> :! cat "threelines.txt" | ||
74 | ONE | ||
75 | TWO | ||
76 | THREE | ||
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 | -} | ||
102 | stdinLn :: MonadIO m => Producer' T.Text m () | ||
103 | stdinLn = 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 | -} | ||
111 | stdoutLn :: MonadIO m => Consumer' T.Text m () | ||
112 | stdoutLn = 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 | -} | ||
131 | stdoutLn' :: MonadIO m => Consumer' T.Text m r | ||
132 | stdoutLn' = 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 | -} | ||
145 | fromHandleLn :: MonadIO m => IO.Handle -> Producer' Text m () | ||
146 | fromHandleLn 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' | ||
171 | toHandleLn :: MonadIO m => IO.Handle -> Consumer' T.Text m r | ||
172 | toHandleLn 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 | -} | ||
184 | readFileLn :: MonadSafe m => FilePath -> Producer Text m () | ||
185 | readFileLn 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 | -} | ||
193 | writeFileLn :: (MonadSafe m) => FilePath -> Consumer' Text m r | ||
194 | writeFileLn 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 | ||
125 | fromHandleLn :: MonadIO m => IO.Handle -> Producer Text m () | ||
126 | fromHandleLn 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' |
138 | stdin :: MonadIO m => Producer Text m () | 278 | stdin :: MonadIO m => Producer Text m () |
139 | stdin = fromHandle IO.stdin | 279 | stdin = 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 | -} | ||
156 | readFileLn :: MonadSafe m => FilePath -> Producer Text m () | ||
157 | readFileLn 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 | ||