----------------------------------------------------------------------------- -- | -- 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 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.IO", with additional comments so they're easier -- to understand. enumHandle :: Integer -- ^ Buffer size -> Handle -> Enumerator B.ByteString IO b enumHandle bufferSize h = Iteratee . 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 <- 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 -> return $ Error err -- The socket has reached EOF; pass control to the -- next enumerator Right bytes | B.null bytes -> return (Continue k) -- Bytes were read successfully; feed them to the -- iteratee and continue looping Right bytes -> runIteratee (k (Chunks [bytes])) >>= loop -- If a different step is received ('Error' or 'Yield'), just pass -- it through. loop step = return step enumFile :: FilePath -> Enumerator B.ByteString IO b enumFile path s = Iteratee $ 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 <- E.try $ openBinaryFile path ReadMode case eitherH of Left err -> return $ Error err Right h -> 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) = Iteratee $ do eitherErr <- E.try $ mapM_ (B.hPut h) bytes return $ case eitherErr of Left err -> Error err _ -> 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 ()