{-# LANGUAGE RankNTypes #-}

module Pipes.Prelude.Text
  ( -- * Simple line-based Text IO
    -- $lineio
    fromHandleLn,
    toHandleLn,
    stdinLn,
    stdoutLn,
    stdoutLn',
    readFileLn,
    writeFileLn,
  )
where

import Control.Exception (throwIO, try)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Foreign.C.Error (Errno (Errno), ePIPE)
import qualified GHC.IO.Exception as G
import Pipes
import Pipes.Safe (MonadSafe (..))
import qualified Pipes.Safe.Prelude as Safe
import qualified System.IO as IO
import Prelude hiding (readFile, writeFile)

-- $lineio
--   Line-based operations are marked with a final \-@Ln@, like 'stdinLn', 'readFileLn', etc.
--   They are drop-in 'Text' replacements for the corresponding 'String' operations in
--   @Pipes.Prelude@ and @Pipes.Safe.Prelude@ - a final \-@Ln@ being added where necessary.
--   This module can thus be imported unqualified if @Pipes.Prelude@ is imported qualified, as
--   it must be.
--
--   In using the line-based operations, one is producing and consuming semantically significant individual texts,
--   understood as lines, just as one would produce or pipe 'Int's or 'Char's or anything else.
--   The standard materials from @Pipes@ and @Pipes.Prelude@ and
--   @Data.Text@ are all you need to work with them, and
--   you can use these operations without using any of the other modules in this package.
--
--   Thus, to take a trivial case, here we upper-case three lines from standard input and write
--   them to a file.  (@runSafeT@ from @Pipes.Safe@ just makes sure to close any handles opened in its scope;
--   it is only needed for @readFileLn@ and @writeFileLn@.)
--
-- >>> import Pipes
-- >>> import qualified Pipes.Prelude as P
-- >>> import qualified Pipes.Prelude.Text as Text
-- >>> import qualified Data.Text as T
-- >>> Text.runSafeT $ runEffect $ Text.stdinLn >-> P.take 3 >-> P.map T.toUpper >-> Text.writeFileLn "threelines.txt"
-- one<Enter>
-- two<Enter>
-- three<Enter>
-- >>> :! cat "threelines.txt"
-- ONE
-- TWO
-- THREE
--
--   The point of view is very much that of @Pipes.Prelude@, substituting @Text@ for @String@.
--   It would still be the same even if
--   we did something a bit more sophisticated, like run an ordinary attoparsec 'Text' parser on
--   each line, as is frequently desirable.  Here we use
--   a minimal attoparsec number parser, @scientific@, on separate lines of standard input,
--   dropping bad parses with @P.concat@:
--
-- >>> import Data.Attoparsec.Text (parseOnly, scientific)
-- >>> P.toListM $ Text.stdinLn >-> P.takeWhile (/= "quit") >-> P.map (parseOnly scientific) >-> P.concat
-- 1<Enter>
-- 2<Enter>
-- bad<Enter>
-- 3<Enter>
-- quit<Enter>
-- [1.0,2.0,3.0]
--
--   The line-based operations are, however, subject to a number of caveats.
--
--   * Where these line-based operations read from a handle, they will
--     accumulate indefinitely long lines. This makes sense for input
--     typed in by a user, and for locally produced files of known characteristics, but
--     otherwise not. See the post on
--     <http://www.haskellforall.com/2013/09/perfect-streaming-using-pipes-bytestring.html perfect streaming>
--     to see why @pipes-bytestring@ and this package, outside this module, take a different approach, in which
--     lines themselves are permitted to stream without accumulation.
--
--   * The line-based operations,
--     like those in @Data.Text.IO@, use the system encoding (and @T.hGetLine@, @T.hPutLine@ etc.)
--     and thus are slower than the \'official\' route, which would use the very fast
--     bytestring IO operations from @Pipes.ByteString@ and the
--     encoding and decoding functions in @Pipes.Text.Encoding@, which are also quite fast
--     thanks to the @streaming-commons@ package.
--
--   * The line-based operations (again like those in @Data.Text.IO@) will
--     generate text exceptions after the fashion of
--     @Data.Text.Encoding@, rather than returning the undigested bytes in the
--     style of @Pipes.Text.Encoding@. This is the standard practice in the pipes libraries.

-- | Read separate lines of 'Text' from 'IO.stdin' using 'T.getLine', terminating on end of input.
--
--    This function will accumulate indefinitely long strict 'Text's. See the caveats above.
stdinLn :: MonadIO m => Producer' T.Text m ()
stdinLn :: Producer' Text m ()
stdinLn = Handle -> Producer' Text m ()
forall (m :: * -> *). MonadIO m => Handle -> Producer' Text m ()
fromHandleLn Handle
IO.stdin
{-# INLINEABLE stdinLn #-}

-- | Write 'Text' lines to 'IO.stdout' using 'putStrLn', terminating without error on a broken output pipe
stdoutLn :: MonadIO m => Consumer' T.Text m ()
stdoutLn :: Consumer' Text m ()
stdoutLn = Proxy () Text y' y m ()
Consumer' Text m ()
go
  where
    go :: Proxy () Text y' y m ()
go = do
      Text
str <- Proxy () Text y' y m Text
forall (m :: * -> *) a. Functor m => Consumer' a m a
await
      Either IOException ()
x <- IO (Either IOException ())
-> Proxy () Text y' y m (Either IOException ())
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either IOException ())
 -> Proxy () Text y' y m (Either IOException ()))
-> IO (Either IOException ())
-> Proxy () Text y' y m (Either IOException ())
forall a b. (a -> b) -> a -> b
$ IO () -> IO (Either IOException ())
forall e a. Exception e => IO a -> IO (Either e a)
try (Text -> IO ()
T.putStrLn Text
str)
      case Either IOException ()
x of
        Left
          G.IOError
            { ioe_type :: IOException -> IOErrorType
G.ioe_type = IOErrorType
G.ResourceVanished,
              ioe_errno :: IOException -> Maybe CInt
G.ioe_errno = Just CInt
ioe
            }
            | CInt -> Errno
Errno CInt
ioe Errno -> Errno -> Bool
forall a. Eq a => a -> a -> Bool
== Errno
ePIPE ->
              () -> Proxy () Text y' y m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Left IOException
e -> IO () -> Proxy () Text y' y m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IOException -> IO ()
forall e a. Exception e => e -> IO a
throwIO IOException
e)
        Right () -> Proxy () Text y' y m ()
go
{-# INLINEABLE stdoutLn #-}

-- | Write lines of 'Text' to 'IO.stdout'. This does not handle a broken output pipe,
--    but has a polymorphic return value.
stdoutLn' :: MonadIO m => Consumer' T.Text m r
stdoutLn' :: Consumer' Text m r
stdoutLn' = Proxy () Text () Text m r
-> (Text -> Proxy () Text y' y m ()) -> Proxy () Text y' y m r
forall (m :: * -> *) x' x b' b a' c' c.
Functor m =>
Proxy x' x b' b m a'
-> (b -> Proxy x' x c' c m b') -> Proxy x' x c' c m a'
for Proxy () Text () Text m r
forall (m :: * -> *) a r. Functor m => Pipe a a m r
cat (IO () -> Proxy () Text y' y m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> Proxy () Text y' y m ())
-> (Text -> IO ()) -> Text -> Proxy () Text y' y m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> IO ()
T.putStrLn)
{-# INLINE [1] stdoutLn' #-}

{-# RULES
"p >-> stdoutLn'" forall p.
  p >-> stdoutLn' =
    for p (liftIO . T.putStrLn)
  #-}

-- | Read separate lines of 'Text' from a 'IO.Handle' using 'T.hGetLine',
--    terminating at the end of input
--
--    This operation will accumulate indefinitely large strict texts. See the caveats above.
fromHandleLn :: MonadIO m => IO.Handle -> Producer' Text m ()
fromHandleLn :: Handle -> Producer' Text m ()
fromHandleLn Handle
h = Proxy x' x () Text m ()
Producer' Text m ()
go
  where
    getLine :: IO (Either G.IOException Text)
    getLine :: IO (Either IOException Text)
getLine = IO Text -> IO (Either IOException Text)
forall e a. Exception e => IO a -> IO (Either e a)
try (Handle -> IO Text
T.hGetLine Handle
h)

    go :: Proxy x' x () Text m ()
go = do
      Either IOException Text
txt <- IO (Either IOException Text)
-> Proxy x' x () Text m (Either IOException Text)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO (Either IOException Text)
getLine
      case Either IOException Text
txt of
        Left IOException
_ -> () -> Proxy x' x () Text m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Right Text
y -> do
          Text -> Producer' Text m ()
forall (m :: * -> *) a. Functor m => a -> Producer' a m ()
yield Text
y
          Proxy x' x () Text m ()
go
{-# INLINEABLE fromHandleLn #-}

-- to do: investigate differences from the above:
-- fromHandleLn :: MonadIO m => IO.Handle -> Producer' T.Text m ()
-- fromHandleLn h = go
--   where
--     go = do
--         eof <- liftIO $ IO.hIsEOF h
--         unless eof $ do
--             str <- liftIO $ T.hGetLine h
--             yield str
--             go
-- {-# INLINABLE fromHandleLn #-}

-- | Write separate lines of 'Text' to a 'IO.Handle' using 'T.hPutStrLn'
toHandleLn :: MonadIO m => IO.Handle -> Consumer' T.Text m r
toHandleLn :: Handle -> Consumer' Text m r
toHandleLn Handle
handle = Proxy () Text () Text m r
-> (Text -> Proxy () Text y' y m ()) -> Proxy () Text y' y m r
forall (m :: * -> *) x' x b' b a' c' c.
Functor m =>
Proxy x' x b' b m a'
-> (b -> Proxy x' x c' c m b') -> Proxy x' x c' c m a'
for Proxy () Text () Text m r
forall (m :: * -> *) a r. Functor m => Pipe a a m r
cat (IO () -> Proxy () Text y' y m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> Proxy () Text y' y m ())
-> (Text -> IO ()) -> Text -> Proxy () Text y' y m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Handle -> Text -> IO ()
T.hPutStrLn Handle
handle)
{-# INLINE [1] toHandleLn #-}

{-# RULES
"p >-> toHandleLn handle" forall p handle.
  p >-> toHandleLn handle =
    for p (liftIO . T.hPutStrLn handle)
  #-}

-- | Stream separate lines of text from a file. Apply @runSafeT@ after running the
--    pipeline to manage the opening and closing of the handle.
--
--    This operation will accumulate indefinitely long strict text chunks.
--    See the caveats above.
readFileLn :: MonadSafe m => FilePath -> Producer Text m ()
readFileLn :: FilePath -> Producer Text m ()
readFileLn FilePath
file = FilePath
-> IOMode -> (Handle -> Producer Text m ()) -> Producer Text m ()
forall (m :: * -> *) r.
MonadSafe m =>
FilePath -> IOMode -> (Handle -> m r) -> m r
Safe.withFile FilePath
file IOMode
IO.ReadMode Handle -> Producer Text m ()
forall (m :: * -> *). MonadIO m => Handle -> Producer' Text m ()
fromHandleLn
{-# INLINE readFileLn #-}

-- | Write lines to a file. Apply @runSafeT@ after running the
--    pipeline to manage the opening and closing of the handle.
writeFileLn :: (MonadSafe m) => FilePath -> Consumer' Text m r
writeFileLn :: FilePath -> Consumer' Text m r
writeFileLn FilePath
file = FilePath
-> IOMode
-> (Handle -> Proxy () Text y' y m r)
-> Proxy () Text y' y m r
forall (m :: * -> *) r.
MonadSafe m =>
FilePath -> IOMode -> (Handle -> m r) -> m r
Safe.withFile FilePath
file IOMode
IO.WriteMode Handle -> Proxy () Text y' y m r
forall (m :: * -> *) r. MonadIO m => Handle -> Consumer' Text m r
toHandleLn
{-# INLINEABLE writeFileLn #-}