packages feed

netlines-1.0.0: Data/Enumerator/NetLines.hs

-- |
-- Module:     Data.Enumerator.NetLines
-- Copyright:  (c) 2010 Ertugrul Soeylemez
-- License:    BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
-- Stability:  beta
--
-- Enumerator tools for working with text-based network protocols.

{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}

module Data.Enumerator.NetLines
    ( -- * Reexports
      module Data.Enumerator.NetLines.Error,
      module Data.Enumerator.NetLines.IO,

      -- * Iteratees
      netLine,
      netLineEmpty,
      netWord,
      netWordEmpty,

      -- * Enumeratees
      netLines,
      netLinesEmpty,
      netWords,
      netWordsEmpty,

      -- * General stream splitters
      netSplitBy,
      netSplitsBy
    )
    where

import Control.ContStuff as Monad
import Data.Enumerator as E
import Data.Enumerator.NetLines.Class as NL
import Data.Enumerator.NetLines.Error
import Data.Enumerator.NetLines.IO


-- | 'True' for ASCII whitespace.

isSpace :: Char -> Bool
isSpace n = n == ' ' || (n >= '\t' && n <= '\r')


-- | Get the next nonempty line from the stream using 'netLineEmpty'.

netLine :: (Monad m, Splittable str) => Int -> Iteratee str m (Maybe str)
netLine = nonEmpty . netLineEmpty


-- | Get the next line from the stream, length-limited by the given
-- 'Int'.  This iteratee is error-tolerant by using LF as the line
-- terminator and simply ignoring all CR characters.

netLineEmpty :: (Monad m, Splittable str) => Int -> Iteratee str m (Maybe str)
netLineEmpty = netSplitBy (== '\n') (/= '\r')


-- | Convert a raw byte stream to a stream of lines based on 'netLine'.

netLines :: (Monad m, Splittable str) => Int -> Enumeratee str str m b
netLines = netSplitsBy . netLine


-- | Convert a raw byte stream to a stream of lines based on
-- 'netLineEmpty'.

netLinesEmpty :: (Monad m, Splittable str) => Int -> Enumeratee str str m b
netLinesEmpty = netSplitsBy . netLineEmpty


-- | Get the next token, where tokens are splitted by the first given
-- predicate and filtered by the second.  Tokens are length-limited by
-- the given 'Int' and are truncated safely in constant space.

netSplitBy ::
    forall m str. (Monad m, Splittable str) =>
    (Char -> Bool) -> (Char -> Bool) -> Int ->
    Iteratee str m (Maybe str)
netSplitBy breakP filterP n =
    continue (loop NL.empty)

    where
    loop :: str -> Stream str -> Iteratee str m (Maybe str)
    loop line' EOF = yield (if NL.null line' then Nothing else Just line') EOF
    loop line' (Chunks []) = continue (loop line')
    loop line' (Chunks (str:strs)) =
        if NL.null line2'
          then line `seq` loop line (Chunks strs)
          else yield (Just line) (Chunks (line2:strs))

        where
        (line1', line2') = NL.break breakP str
        line1            = NL.filter filterP line1'
        line2            = NL.tail line2'
        line             = NL.take n $ NL.append line' line1


-- | Split the stream using the supplied iteratee.

netSplitsBy ::
    forall b m str. (Monad m, Splittable str) =>
    Iteratee str m (Maybe str) -> Enumeratee str str m b
netSplitsBy getLine = loop
    where
    loop :: Enumeratee str str m b
    loop (Continue k) = do
        mLine <- getLine
        case mLine of
          Just line -> k (Chunks [line]) >>== loop
          Nothing   -> k EOF >>== loop
    loop step = return step


-- | Get the next nonempty word from the stream with the given maximum
-- length.  Based on 'netWordEmpty'.

netWord :: (Monad m, Splittable str) => Int -> Iteratee str m (Maybe str)
netWord = nonEmpty . netWordEmpty


-- | Get the next word from the stream with the given maximum length.
-- This iteratee is error-tolerant by using ASCII whitespace as
-- splitting characters.

netWordEmpty :: (Monad m, Splittable str) => Int -> Iteratee str m (Maybe str)
netWordEmpty = netSplitBy isSpace (const True)


-- | Split the raw byte stream into words based on 'netWord'.

netWords :: (Monad m, Splittable str) => Int -> Enumeratee str str m b
netWords = netSplitsBy . netWord


-- | Split the raw byte stream into words based on 'netWords'.

netWordsEmpty :: (Monad m, Splittable str) => Int -> Enumeratee str str m b
netWordsEmpty = netSplitsBy . netWordEmpty


-- | Apply the given iteratee, until it returns a nonempty string.

nonEmpty ::
    forall a m str. (Monad m, Splittable str) =>
    Iteratee a m (Maybe str) -> Iteratee a m (Maybe str)
nonEmpty getStr = evalMaybeT loop
    where
    loop :: MaybeT r (Iteratee a m) str
    loop = do
        line <- liftF getStr
        if NL.null line then loop else return line