enumerator-0.4.5: examples/cat.hs
-----------------------------------------------------------------------------
-- |
-- Copyright: 2010 John Millikin
-- License: MIT
--
-- Maintainer: jmillikin@gmail.com
-- Portability: portable
--
-----------------------------------------------------------------------------
module Main (main) where
import Prelude as Prelude
import Control.Exception as E
import Control.Monad.IO.Class (liftIO)
import Data.Enumerator
import qualified Data.ByteString as B
import qualified Foreign as F
import System.IO
import System.IO.Error (isEOFError)
import System.Environment (getArgs)
-- The following definitions of 'enumHandle', 'enumFile', and 'iterHandle' are
-- copied from "Data.Enumerator.Binary", with additional comments so they're
-- easier to understand.
enumHandle :: Integer -- ^ Buffer size
-> Handle
-> Enumerator B.ByteString IO b
enumHandle bufferSize h = loop where
intSize = fromInteger bufferSize
-- If more input is required before the enumerator's iteratee can
-- yield a result, feed it from the handle.
loop (Continue k) = do
-- While not strictly necessary to proper operation, catching
-- exceptions here allows more unified exception handling when
-- the enumerator/iteratee is run.
eitherBytes <- liftIO $ E.try $ do
-- The enumerator must function normally when the
-- handle is something like a slow file, or network
-- socket; if there's not enough data to fill the
-- buffer yet, a partial read is returned.
hasInput <- E.catch
(hWaitForInput h (1))
(\err -> if isEOFError err
then return False
else E.throwIO err)
-- An EOF is represented by the empty bytestring
if hasInput
then B.hGetNonBlocking h intSize
else return B.empty
case eitherBytes of
-- Interacting with the socket threw an IO error of
-- some sort
Left err -> throwError (err :: E.SomeException)
-- The socket has reached EOF; pass control to the
-- next enumerator
Right bytes | B.null bytes -> continue k
-- Bytes were read successfully; feed them to the
-- iteratee and continue looping
Right bytes -> k (Chunks [bytes]) >>== loop
-- If a different step is received ('Error' or 'Yield'), just pass
-- it through.
loop step = returnI step
enumFile :: FilePath -> Enumerator B.ByteString IO b
enumFile path s = do
-- Opening the file can be performed either inside or outside of the
-- Iteratee. Inside allows exceptions to be caught and propagated
-- through the 'Error' step constructor.
eitherH <- liftIO (E.try (openBinaryFile path ReadMode))
case eitherH of
Left err -> throwError (err :: E.SomeException)
Right h -> Iteratee $ finally
(runIteratee (enumHandle 4096 h s))
(hClose h)
-- 'iterHandle' is the opposite of 'enumHandle', in that it *writes to* a
-- handle instead of reading from it. An enumerator is a source, an iteratee
-- is a sink.
iterHandle :: Handle -> Iteratee B.ByteString IO ()
-- Most iteratees start in the 'Continue' state, as they need some
-- input before they can produce any value.
iterHandle h = continue step where
-- This iteratee produces no value; its only purpose is its
-- side-effects. When 'EOF' is received, it simply yields ().
step EOF = yield () EOF
-- When some chunks are received from the Enumeratee, they're written
-- to the handle. Any exceptions are caught and reported, as in
-- 'enumHandle'.
step (Chunks bytes) = do
eitherErr <- liftIO (E.try (mapM_ (B.hPut h) bytes))
case eitherErr of
Left err -> throwError (err :: E.SomeException)
_ -> continue step
main :: IO ()
main = do
-- Our example enumlates standard /bin/cat, where if the argument list
-- is empty, data is echoed from stdin.
args <- getArgs
let enum = if null args
then enumHandle 1 stdin
else concatEnums (Prelude.map enumFile args)
-- 'run' sends an EOF to an iteratee and returns its output, which
-- is either a 'Yield' or an 'Error'.
res <- run (enum $$ iterHandle stdout)
-- Finally, 'run' has returned either an error or the iteratee's
-- result. 'iterHandle' doesn't return a useful result, so as long
-- as it succeeded the actual value is ignored.
case res of
Left err -> putStrLn $ "ERROR: " ++ show err
Right _ -> return ()