{-#LANGUAGE RankNTypes#-}
-- | The operations exported here are a convenience, like the similar operations in
-- @Data.Text.IO@ , or rather, @Data.Text.Lazy.IO@, since @Producer Text m r@ is
-- 'effectful text' and something like the pipes equivalent of lazy Text.
--
-- * Like the functions in @Data.Text.IO@, they attempt to work with the system encoding.
--
-- * Like the functions in @Data.Text.IO@, they are slower than ByteString operations. Where
-- you know what encoding you are working with, use @Pipes.ByteString@ and @Pipes.Text.Encoding@ instead,
-- e.g. @view utf8 Bytes.stdin@ instead of @Text.stdin@
--
-- * Like the functions in @Data.Text.IO@ , they use Text exceptions.
--
-- Something like
--
-- > view utf8 . Bytes.fromHandle :: Handle -> Producer Text IO (Producer ByteString m ())
--
-- yields a stream of Text, and follows
-- standard pipes protocols by reverting to (i.e. returning) the underlying byte stream
-- upon reaching any decoding error. (See especially the pipes-binary package.)
--
-- By contrast, something like
--
-- > Text.fromHandle :: Handle -> Producer Text IO ()
--
-- supplies a stream of text returning '()', which is convenient for many tasks,
-- but violates the pipes @pipes-binary@ approach to decoding errors and
-- throws an exception of the kind characteristic of the @text@ library instead.
module Pipes.Text.IO
(
-- * Producers
fromHandle
, stdin
, readFile
-- * Consumers
, toHandle
, stdout
, writeFile
) where
import qualified System.IO as IO
import Control.Exception (throwIO, try)
import Foreign.C.Error (Errno(Errno), ePIPE)
import qualified GHC.IO.Exception as G
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Pipes
import qualified Pipes.Safe.Prelude as Safe
import qualified Pipes.Safe as Safe
import Pipes.Safe (MonadSafe(..), Base(..))
import Prelude hiding (readFile, writeFile)
{-| Convert a 'IO.Handle' into a text stream using a text size
determined by the good sense of the text library. Note with the remarks
at the head of this module that this
is slower than @view utf8 (Pipes.ByteString.fromHandle h)@
but uses the system encoding and has other nice @Data.Text.IO@ features
-}
fromHandle :: MonadIO m => IO.Handle -> Producer' Text m ()
fromHandle h = go where
go = do txt <- liftIO (T.hGetChunk h)
if T.null txt then return ()
else do yield txt
go
{-# INLINABLE fromHandle#-}
-- | Stream text from 'stdin'
stdin :: MonadIO m => Producer' Text m ()
stdin = fromHandle IO.stdin
{-# INLINE stdin #-}
{-| Stream text from a file in the simple fashion of @Data.Text.IO@
>>> runSafeT $ runEffect $ Text.readFile "hello.hs" >-> Text.map toUpper >-> hoist lift Text.stdout
MAIN = PUTSTRLN "HELLO WORLD"
-}
readFile :: MonadSafe m => FilePath -> Producer' Text m ()
readFile file = Safe.withFile file IO.ReadMode fromHandle
{-# INLINE readFile #-}
{-| Stream text to 'stdout'
Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
Note: For best performance, it might be best just to use @(for source (liftIO . putStr))@
instead of @(source >-> stdout)@ .
-}
stdout :: MonadIO m => Consumer' Text m ()
stdout = go
where
go = do
txt <- await
x <- liftIO $ try (T.putStr txt)
case x of
Left (G.IOError { G.ioe_type = G.ResourceVanished
, G.ioe_errno = Just ioe })
| Errno ioe == ePIPE
-> return ()
Left e -> liftIO (throwIO e)
Right () -> go
{-# INLINABLE stdout #-}
{-| Convert a text stream into a 'Handle'
Note: again, for best performance, where possible use
@(for source (liftIO . hPutStr handle))@ instead of @(source >-> toHandle handle)@.
-}
toHandle :: MonadIO m => IO.Handle -> Consumer' Text m r
toHandle h = for cat (liftIO . T.hPutStr h)
{-# INLINABLE toHandle #-}
{-# RULES "p >-> toHandle h" forall p h .
p >-> toHandle h = for p (\txt -> liftIO (T.hPutStr h txt))
#-}
-- | Stream text into a file. Uses @pipes-safe@.
writeFile :: (MonadSafe m) => FilePath -> Consumer' Text m ()
writeFile file = Safe.withFile file IO.WriteMode toHandle
{-# INLINE writeFile #-}