packages feed

enumerator-0.1: 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 Data.Enumerator
import qualified Data.ByteString as B
import qualified Foreign as F
import System.IO
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 SomeException B.ByteString IO b
enumHandle bufferSize h = Iteratee . io where
	intSize = fromInteger bufferSize
	
	-- Allocate a single buffer for reading from the file
	io step = F.allocaBytes intSize $ \p -> loop step p
	
	-- If more input is required before the enumerator's iteratee can
	-- yield a result, feed it from the handle. If a different step is
	-- received ('Error' or 'Yield'), just pass it through.
	loop (Continue k) = read' k
	loop step = const $ return step
	
	read' k p = do
		-- While not strictly necessary to proper operation, catching
		-- exceptions here allows more unified exception handling when
		-- the enumerator/iteratee is run.
		eitherN <- E.try $ hGetBuf h p intSize
		case eitherN of
			Left err -> return $ Error err
			
			-- if 'hGetBuf' returns 0, then the handle has reached
			-- EOF. The enumerator has two choices:
			--
			-- * Send EOF to its iteratee, and return the result
			-- * Return a Continue, with the current continuation
			--
			-- The second is better, because it allows enumerators
			-- to be composed with (>>==). If EOF is sent, only
			-- one enumerator can be read at a time.
			Right 0 -> return $ Continue k
			
			-- 'hGetBuf' was at least partially successful, so read
			-- bytes into a ByteString and pass it through to the
			-- iteratee.
			Right n -> do
				bytes <- B.packCStringLen (p, n)
				step <- runIteratee (k (Chunks [bytes]))
				loop step p

enumFile :: FilePath -> Enumerator E.SomeException 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 SomeException 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)
	
	-- This line looks a bit weird, because the (>>==) causes a visual
	-- "flow" from the iteratee to the enumerator. However, data is
	-- actually being sent from the enumerator to the iteratee. It's
	-- better to think of it in terms of continuation passing.
	res <- run (iterHandle stdout >>== enum)
	
	-- 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 ()