From 5f3c3763ec355affc1b3219c9c6a072d36349ce3 Mon Sep 17 00:00:00 2001 From: michaelt Date: Sat, 6 Feb 2016 17:09:45 -0500 Subject: separated line-based material --- Pipes/Prelude/Text.hs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++ Pipes/Text/IO.hs | 158 +-------------------------------------------- pipes-text.cabal | 4 +- 3 files changed, 176 insertions(+), 159 deletions(-) create mode 100644 Pipes/Prelude/Text.hs diff --git a/Pipes/Prelude/Text.hs b/Pipes/Prelude/Text.hs new file mode 100644 index 0000000..faa096c --- /dev/null +++ b/Pipes/Prelude/Text.hs @@ -0,0 +1,173 @@ +{-#LANGUAGE RankNTypes#-} + + +module Pipes.Prelude.Text + ( + -- * Simple line-based Text IO + -- $lineio + + fromHandleLn + , toHandleLn + , stdinLn + , stdoutLn + , stdoutLn' + , readFileLn + , writeFileLn + ) 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 Pipes.Safe (MonadSafe(..), runSafeT, runSafeP) +import Prelude hiding (readFile, writeFile) + +{- $lineio + Line-based operations are marked with a final \-@Ln@, like 'stdinLn', 'readFileLn'. They are + drop-in replacements for the line-based operations in @Pipes.Prelude@ and + @Pipes.Safe.Prelude@ - the final \-@Ln@ being added where necessary. + With them, one is producing, piping and consuming semantically significant individual texts, + understood as lines, just as one would pipe 'Int's. The standard materials from @Pipes@ and @Pipes.Prelude@ and + @Data.Text@ are all you need to interact with these lines as you read or write them. + You can use these operations without using any of the other material in this package. + + Thus, to take a trivial case, here we upper-case three lines from standard input and write + them to a file. + +>>> import Pipes +>>> import qualified Pipes.Prelude as P +>>> import qualified Pipes.Text.IO 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 +two +three +>>> :! cat "threelines.txt" +ONE +TWO +THREE + + The point of view is very much that of @Pipes.Prelude@ and the user who needs no more + can use them ignoring the rest of this package. + + The line-based operations are, however, subject to a number of caveats. + First, where they read from a handle, they will of course happily + accumulate indefinitely long lines. This is likely to be legitimate for input + typed in by a user, and for locally produced log files and other known material, but + otherwise not. See the post on + + to see why @pipes-bytestring@ and this package take a different approach. Furthermore, + like those in @Data.Text.IO@, the operations use the system encoding (and @T.hGetLine@) + and thus are slower than the \'official\' route, which would use bytestring IO and + the encoding and decoding functions in @Pipes.Text.Encoding@. Finally, they will generate + text exceptions after the fashion of @Data.Text.Encoding@ rather than returning the + undigested bytes in the style of @Pipes.Text.Encoding@ + +-} + + +{-| Read separate lines of 'Text' from 'IO.stdin' using 'T.getLine' + This function will accumulate indefinitely long strict 'Text's. See the caveats above. + + Terminates on end of input +-} +stdinLn :: MonadIO m => Producer' T.Text m () +stdinLn = fromHandleLn IO.stdin +{-# INLINABLE stdinLn #-} + + +{-| Write 'String's to 'IO.stdout' using 'putStrLn' + + Unlike 'toHandle', 'stdoutLn' gracefully terminates on a broken output pipe +-} +stdoutLn :: MonadIO m => Consumer' T.Text m () +stdoutLn = go + where + go = do + str <- await + x <- liftIO $ try (T.putStrLn str) + 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 stdoutLn #-} + +{-| Write lines of 'Text's 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' = for cat (\str -> liftIO (T.putStrLn str)) +{-# INLINABLE stdoutLn' #-} + +{-# RULES + "p >-> stdoutLn'" forall p . + p >-> stdoutLn' = for p (\str -> liftIO (T.putStrLn str)) + #-} + +{-| Read separate lines of 'Text' from a 'IO.Handle' using 'T.hGetLine'. + This operation will accumulate indefinitely large strict texts. See the caveats above. + + Terminates on end of input +-} +fromHandleLn :: MonadIO m => IO.Handle -> Producer' Text m () +fromHandleLn h = go where + getLine :: IO (Either G.IOException Text) + getLine = try (T.hGetLine h) + + go = do txt <- liftIO getLine + case txt of + Left e -> return () + Right y -> do yield y + go +{-# INLINABLE 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 = for cat (\str -> liftIO (T.hPutStrLn handle str)) +{-# INLINABLE toHandleLn #-} + +{-# RULES + "p >-> toHandleLn handle" forall p handle . + p >-> toHandleLn handle = for p (\str -> liftIO (T.hPutStrLn handle str)) + #-} + + +{-| Stream separate lines of text from a file. This operation will accumulate + indefinitely long strict text chunks. See the caveats above. +-} +readFileLn :: MonadSafe m => FilePath -> Producer Text m () +readFileLn file = Safe.withFile file IO.ReadMode fromHandleLn +{-# INLINE readFileLn #-} + + + +{-| Write lines to a file, automatically opening and closing the file as + necessary +-} +writeFileLn :: (MonadSafe m) => FilePath -> Consumer' Text m r +writeFileLn file = Safe.withFile file IO.WriteMode toHandleLn +{-# INLINABLE writeFileLn #-} + diff --git a/Pipes/Text/IO.hs b/Pipes/Text/IO.hs index d30f13c..2a34be7 100644 --- a/Pipes/Text/IO.hs +++ b/Pipes/Text/IO.hs @@ -3,17 +3,6 @@ module Pipes.Text.IO ( - -- * Simple line-based Text IO - -- $lineio - - fromHandleLn - , toHandleLn - , stdinLn - , stdoutLn - , stdoutLn' - , readFileLn - , writeFileLn - -- * Simple streaming text IO -- $textio @@ -22,7 +11,7 @@ module Pipes.Text.IO -- $caveats -- * Producers - , fromHandle + fromHandle , stdin , readFile @@ -50,151 +39,6 @@ import qualified Pipes.Safe.Prelude as Safe import Pipes.Safe (MonadSafe(..), runSafeT, runSafeP) import Prelude hiding (readFile, writeFile) -{- $lineio - Line-based operations are marked with a final \-@Ln@, like 'stdinLn', 'readFileLn'. They are - drop-in replacements for the line-based operations in @Pipes.Prelude@ and - @Pipes.Safe.Prelude@ - the final \-@Ln@ being added where necessary. - With them, one is producing, piping and consuming semantically significant individual texts, - understood as lines, just as one would pipe 'Int's. The standard materials from @Pipes@ and @Pipes.Prelude@ and - @Data.Text@ are all you need to interact with these lines as you read or write them. - You can use these operations without using any of the other material in this package. - - Thus, to take a trivial case, here we upper-case three lines from standard input and write - them to a file. - ->>> import Pipes ->>> import qualified Pipes.Prelude as P ->>> import qualified Pipes.Text.IO 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 -two -three ->>> :! cat "threelines.txt" -ONE -TWO -THREE - - The point of view is very much that of @Pipes.Prelude@ and the user who needs no more - can use them ignoring the rest of this package. - - The line-based operations are, however, subject to a number of caveats. - First, where they read from a handle, they will of course happily - accumulate indefinitely long lines. This is likely to be legitimate for input - typed in by a user, and for locally produced log files and other known material, but - otherwise not. See the post on - - to see why @pipes-bytestring@ and this package take a different approach. Furthermore, - like those in @Data.Text.IO@, the operations use the system encoding and @T.hGetLine@ - and thus are slower than the \'official\' route, which would use bytestring IO and - the encoding and decoding functions in @Pipes.Text.Encoding@. Finally, they will generate - text exceptions after the fashion of @Data.Text.Encoding@ rather than returning the - undigested bytes in the style of @Pipes.Text.Encoding@ - --} - - -{-| Read separate lines of 'Text' from 'IO.stdin' using 'T.getLine' - This function will accumulate indefinitely long strict 'Text's. See the caveats above. - - Terminates on end of input --} -stdinLn :: MonadIO m => Producer' T.Text m () -stdinLn = fromHandleLn IO.stdin -{-# INLINABLE stdinLn #-} - - -{-| Write 'String's to 'IO.stdout' using 'putStrLn' - - Unlike 'toHandle', 'stdoutLn' gracefully terminates on a broken output pipe --} -stdoutLn :: MonadIO m => Consumer' T.Text m () -stdoutLn = go - where - go = do - str <- await - x <- liftIO $ try (T.putStrLn str) - 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 stdoutLn #-} - -{-| Write lines of 'Text's 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' = for cat (\str -> liftIO (T.putStrLn str)) -{-# INLINABLE stdoutLn' #-} - -{-# RULES - "p >-> stdoutLn'" forall p . - p >-> stdoutLn' = for p (\str -> liftIO (T.putStrLn str)) - #-} - -{-| Read separate lines of 'Text' from a 'IO.Handle' using 'T.hGetLine'. - This operation will accumulate indefinitely large strict texts. See the caveats above. - - Terminates on end of input --} -fromHandleLn :: MonadIO m => IO.Handle -> Producer' Text m () -fromHandleLn h = go where - getLine :: IO (Either G.IOException Text) - getLine = try (T.hGetLine h) - - go = do txt <- liftIO getLine - case txt of - Left e -> return () - Right y -> do yield y - go -{-# INLINABLE 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 = for cat (\str -> liftIO (T.hPutStrLn handle str)) -{-# INLINABLE toHandleLn #-} - -{-# RULES - "p >-> toHandleLn handle" forall p handle . - p >-> toHandleLn handle = for p (\str -> liftIO (T.hPutStrLn handle str)) - #-} - - -{-| Stream separate lines of text from a file. This operation will accumulate - indefinitely long strict text chunks. See the caveats above. --} -readFileLn :: MonadSafe m => FilePath -> Producer Text m () -readFileLn file = Safe.withFile file IO.ReadMode fromHandleLn -{-# INLINE readFileLn #-} - - - -{-| Write lines to a file, automatically opening and closing the file as - necessary --} -writeFileLn :: (MonadSafe m) => FilePath -> Consumer' Text m r -writeFileLn file = Safe.withFile file IO.WriteMode toHandleLn -{-# INLINABLE writeFileLn #-} - - {- $textio Where pipes @IO@ replaces lazy @IO@, @Producer Text IO r@ replaces lazy 'Text'. diff --git a/pipes-text.cabal b/pipes-text.cabal index bd58457..fe77644 100644 --- a/pipes-text.cabal +++ b/pipes-text.cabal @@ -1,5 +1,5 @@ name: pipes-text -version: 0.0.1.0 +version: 0.0.2.0 synopsis: Text pipes. description: * This organization of the package follows the rule . @@ -48,5 +48,5 @@ library ghc-options: -O2 if !flag(noio) - exposed-modules: Pipes.Text.IO, Pipes.Text.Tutorial + exposed-modules: Pipes.Text.IO, Pipes.Text.Tutorial, Pipes.Prelude.Text build-depends: text >=0.11.3 && < 1.3 -- cgit v1.2.3