packages feed

enumerator-0.4.5: src/util.anansi

\section{Misc. utilities}

A few special-case utilities that are used by similar libraries, or were
present in previous versions of {\tt enumerator}, or otherwise don't have a
good place to go.

:d Data.Enumerator exports
-- * Misc. utilities
:

\subsection{Enumeratees}

Sequencing a fixed set of enumerators is easy, but for more complex
cases, it's useful to have a small utility wrapper.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.concatEnums|
concatEnums :: Monad m => [Enumerator a m b]
            -> Enumerator a m b
concatEnums = Prelude.foldl (>==>) returnI
:

:d Data.Enumerator exports
, concatEnums
:

{\tt joinI} is used to ``flatten'' enumeratees, to transform them into an
{\tt Iteratee}.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.joinI|
joinI :: Monad m => Iteratee a m (Step a' m b)
      -> Iteratee a m b
joinI outer = outer >>= check where
	check (Continue k) = k EOF >>== \s -> case s of
		Continue _ -> error "joinI: divergent iteratee"
		_ -> check s
	check (Yield x _) = return x
	check (Error e) = throwError e
:

{\tt joinE} is similar, except it flattens an enumerator/enumeratee pair
into a single enumerator.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.joinE|
joinE :: Monad m
      => Enumerator ao m (Step ai m b)
      -> Enumeratee ao ai m b
      -> Enumerator ai m b
joinE enum enee s = Iteratee $ do
	step <- runIteratee (enumEOF $$ enum $$ enee s)
	case step of
		Error err -> return (Error err)
		Yield x _ -> return x
		Continue _ -> error "joinE: divergent iteratee"
:

{\tt sequence} repeatedly runs its parameter to transform the stream.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.sequence|
sequence :: Monad m => Iteratee ao m ai
         -> Enumeratee ao ai m b
sequence i = loop where
	loop = checkDone check
	check k = isEOF >>= \f -> if f
		then yield (Continue k) EOF
		else step k
	step k = i >>= \v -> k (Chunks [v]) >>== loop
:

Another small, useful enumerator separates an input list into chunks, and
sends them to the iteratee. This is useful for testing iteratees in pure
code.

:d Data.Enumerator imports
import Data.List (genericSplitAt)
:

:f Data/Enumerator.hs
|apidoc Data.Enumerator.enumList|
enumList :: Monad m => Integer -> [a] -> Enumerator a m b
enumList n = loop where
	loop xs (Continue k) | not (null xs) = let
		(s1, s2) = genericSplitAt n xs
		in k (Chunks s1) >>== loop s2
	loop _ step = returnI step
:

:d Data.Enumerator exports
, joinI
, joinE
, Data.Enumerator.sequence
, enumList
:

\subsection{Running iteratees}

To simplify running iteratees, {\tt run} sends {\tt EOF} and then examines
the result. It is not possible for the result to be {\tt Continue}, because
{\tt enumEOF} calls {\tt error} for divergent iteratees.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.run|
run :: Monad m => Iteratee a m b
    -> m (Either Exc.SomeException b)
run i = do
	mStep <- runIteratee $ enumEOF ==<< i
	case mStep of
		Error err -> return $ Left err
		Yield x _ -> return $ Right x
		Continue _ -> error "run: divergent iteratee"
:

:f Data/Enumerator.hs
|apidoc Data.Enumerator.enumEOF|
enumEOF :: Monad m => Enumerator a m b
enumEOF (Yield x _) = yield x EOF
enumEOF (Error err) = throwError err
enumEOF (Continue k) = k EOF >>== check where
	check (Continue _) = error "enumEOF: divergent iteratee"
	check s = enumEOF s
:

{\tt run\_} is even more simplified; it's used in simple scripts, where the
user doesn't care about error handling.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.run_|
run_ :: Monad m => Iteratee a m b -> m b
run_ i = run i >>= either Exc.throw return
:

:d Data.Enumerator exports
, enumEOF
, run
, run_
:

\subsection{{\tt checkDone} and {\tt checkDoneEx}}

A common pattern in {\tt Enumeratee} implementations is to check whether
the inner {\tt Iteratee} has finished, and if so, to return its output.
{\tt checkDone} passes its parameter a continuation if the {\tt Iteratee}
can still consume input, or yields otherwise.

Oleg's version of {\tt checkDone} has a problem---when the enumeratee has
some sort of input buffer, but the underlying iteratee enters {\tt Yield},
it will discard the output buffer. {\tt checkDoneEx} corrects this; for
backwards compatibility, {\tt checkDone} remains.

:f Data/Enumerator.hs
|apidoc Data.Enumerator.checkDoneEx|
checkDoneEx :: Monad m =>
	Stream a' ->
	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
	Enumeratee a' a m b
checkDoneEx _     f (Continue k) = f k
checkDoneEx extra _ step         = yield step extra

|apidoc Data.Enumerator.checkDone|
checkDone :: Monad m =>
	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
	Enumeratee a' a m b
checkDone = checkDoneEx (Chunks [])
:

:d Data.Enumerator exports
, checkDone
, checkDoneEx
:

:f Data/Enumerator.hs
|apidoc Data.Enumerator.isEOF|
isEOF :: Monad m => Iteratee a m Bool
isEOF = continue $ \s -> case s of
	EOF -> yield True s
	_ -> yield False s
:

:d Data.Enumerator exports
, isEOF
:

{\tt Data.Enumerator.Util} is a hidden module for functions used by several
public modules, but not logically part of the {\tt enumerator} API.

:f Data/Enumerator/Util.hs
{-# LANGUAGE CPP #-}
module Data.Enumerator.Util where
import Data.Enumerator

import Data.Char (toUpper, intToDigit, ord)
import Data.Word (Word8)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL

import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Control.Exception as Exc
import Numeric (showIntAtBase)
:

:f Data/Enumerator/Util.hs
tryStep :: MonadIO m => IO t -> (t -> Iteratee a m b) -> Iteratee a m b
tryStep get io = do
	tried <- liftIO (Exc.try get)
	case tried of
		Right t -> io t
		Left err -> throwError (err :: Exc.SomeException)
:

:f Data/Enumerator/Util.hs
pad0 :: Int -> String -> String
pad0 size str = padded where
	len = Prelude.length str
	padded = if len >= size
		then str
		else Prelude.replicate (size - len) '0' ++ str
:

:f Data/Enumerator/Util.hs
reprChar :: Char -> String
reprChar c = "U+" ++ (pad0 4 (showIntAtBase 16 (toUpper . intToDigit) (ord c) ""))
:

:f Data/Enumerator/Util.hs
reprWord :: Word8 -> String
reprWord w = "0x" ++ (pad0 2 (showIntAtBase 16 (toUpper . intToDigit) w ""))
:

{\tt text-0.11} changed some function names to appease a few bikeshedding
idiots in -cafe; to support it, a bit of compatibility code is needed.

I had a choice between using the preprocessor, or a separate module plus
some Cabal magic. It turns out that {\tt cabal sdist} doesn't properly
handle multiple source directories selected by flags, so the preprocessor
is used for now.

:f Data/Enumerator/Util.hs
tSpanBy  :: (Char -> Bool) -> T.Text -> (T.Text, T.Text)
tlSpanBy :: (Char -> Bool) -> TL.Text -> (TL.Text, TL.Text)
#if MIN_VERSION_text(0,11,0)
tSpanBy = T.span
tlSpanBy = TL.span
#else
tSpanBy = T.spanBy
tlSpanBy = TL.spanBy
#endif
: