diff --git a/Data/Enumerator/NetLines.hs b/Data/Enumerator/NetLines.hs
new file mode 100644
--- /dev/null
+++ b/Data/Enumerator/NetLines.hs
@@ -0,0 +1,192 @@
+-- |
+-- 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 ScopedTypeVariables #-}
+
+module Data.Enumerator.NetLines
+    ( -- * Iteratees
+      netLine,
+      netLineEmpty,
+      netWord,
+      netWordEmpty,
+
+      -- * Enumeratees
+      netLines,
+      netLinesEmpty,
+      netWords,
+      netWordsEmpty,
+
+      -- * General stream splitters
+      netSplitBy,
+      netSplitsBy,
+
+      -- * Enumerators
+      enumHandleTimeout
+    )
+    where
+
+import qualified Data.ByteString as B
+import Control.Arrow
+import Control.ContStuff as Monad
+import Data.ByteString (ByteString)
+import Data.Enumerator as E
+import Data.Word
+import System.IO
+import System.IO.Error as IOErr
+
+
+-- | Enumerate from a handle with the given buffer size (first argument)
+-- and timeout in milliseconds (second argument).  If the timeout is
+-- exceeded an exception is thrown via 'throwError'.
+
+enumHandleTimeout :: forall b m. MonadIO m =>
+                     Int -> Int -> Handle -> Enumerator ByteString m b
+enumHandleTimeout bufSize timeout h = loop
+    where
+    loop :: Enumerator ByteString m b
+    loop (Continue k) = do
+          mHaveInput <- liftIO $ IOErr.try (hWaitForInput h timeout)
+          case mHaveInput of
+            Left err
+                | isEOFError err -> continue k
+                | otherwise      -> throwError err
+            Right False -> throwError $ userError "Handle timed out"
+            Right True  -> do
+                mStr <- liftIO $ IOErr.try (B.hGetNonBlocking h bufSize)
+                str <- either throwError return mStr
+                if B.null str
+                  then continue k
+                  else k (Chunks [str]) >>== loop
+    loop step = returnI step
+
+
+-- | Predicate for ASCII vertical whitespace.
+
+isSpace :: Word8 -> Bool
+isSpace n = n == 32 || (n >= 9 && n <= 13)
+
+
+-- | Get the next nonempty line from the stream using 'netLineEmpty'.
+
+netLine :: forall m. Monad m => Int -> Iteratee ByteString m (Maybe ByteString)
+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 => Int -> Iteratee ByteString m (Maybe ByteString)
+netLineEmpty = netSplitBy (== 10) (/= 13)
+
+
+-- | Convert a raw byte stream to a stream of lines based on 'netLine'.
+
+netLines :: Monad m => Int -> Enumeratee ByteString ByteString m b
+netLines = netSplitsBy . netLine
+
+
+-- | Convert a raw byte stream to a stream of lines based on
+-- 'netLineEmpty'.
+
+netLinesEmpty :: Monad m => Int -> Enumeratee ByteString ByteString 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. Monad m =>
+    (Word8 -> Bool) ->
+    (Word8 -> Bool) ->
+    Int ->
+    Iteratee ByteString m (Maybe ByteString)
+netSplitBy breakP filterP n =
+    continue (loop n)
+
+    where
+    loop :: Int -> Stream ByteString -> Iteratee ByteString m (Maybe ByteString)
+    loop _ EOF = return Nothing
+    loop n (Chunks []) = continue (loop n)
+    loop n (Chunks strs)
+        | B.null str = continue (loop n)
+        | otherwise = do
+            case (B.null line1, B.null line2') of
+              (True, True) -> yield Nothing EOF
+              (False, True) ->
+                  case n of
+                    0 -> continue (loop 0)
+                    _ -> seq pfx $
+                         continue (loop (n - B.length pfx) >=>
+                                   maybe (yield (Just pfx) EOF)
+                                         (return . Just . B.append pfx))
+              (_, False) -> yield (Just pfx) (Chunks [line2])
+
+        where
+        str             = B.concat strs
+        (line1, line2') = B.break breakP str
+        (pfx, _)        = first (B.filter filterP) $ B.splitAt n line1
+        line2           = B.tail line2'
+
+
+-- | Split the stream using the supplied iteratee.
+
+netSplitsBy ::
+    forall b m. Monad m =>
+    Iteratee ByteString m (Maybe ByteString) -> Enumeratee ByteString ByteString m b
+netSplitsBy getLine = loop
+    where
+    loop :: Enumeratee ByteString ByteString 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 => Int -> Iteratee ByteString m (Maybe ByteString)
+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 => Int -> Iteratee ByteString m (Maybe ByteString)
+netWordEmpty = netSplitBy isSpace (const True)
+
+
+-- | Split the raw byte stream into words based on 'netWord'.
+
+netWords :: Monad m => Int -> Enumeratee ByteString ByteString m b
+netWords = netSplitsBy . netWord
+
+
+-- | Split the raw byte stream into words based on 'netWords'.
+
+netWordsEmpty :: Monad m => Int -> Enumeratee ByteString ByteString m b
+netWordsEmpty = netSplitsBy . netWordEmpty
+
+
+-- | Apply the given iteratee, until it returns a nonempty line.
+
+nonEmpty ::
+    forall a m. Monad m =>
+    Iteratee a m (Maybe ByteString) -> Iteratee a m (Maybe ByteString)
+nonEmpty getStr = evalMaybeT loop
+    where
+    loop :: MaybeT r (Iteratee a m) ByteString
+    loop = do
+        line <- liftF getStr
+        if B.null line then loop else return line
diff --git a/Network/NetLines.hs b/Network/NetLines.hs
deleted file mode 100644
--- a/Network/NetLines.hs
+++ /dev/null
@@ -1,139 +0,0 @@
--- |
--- Module:     Network.NetLines
--- Copyright:  (c) 2010 Ertugrul Soeylemez
--- License:    BSD3
--- Maintainer: Ertugrul Soeylemez <es@ertes.de>
--- Stability:  experimental
---
--- Enumerator tools for working with text-based network protocols.
-
-{-# LANGUAGE ScopedTypeVariables #-}
-
-module Network.NetLines
-    ( -- * Conversion to lines
-      netLine,
-      netLines,
-
-      -- * Conversion to sparse lines
-      netLineEmpty,
-      netLinesEmpty,
-
-      -- * Enumerators
-      enumHandleTimeout
-    )
-    where
-
-import qualified Data.ByteString as B
-import Control.ContStuff as Monad
-import Data.ByteString (ByteString)
-import Data.Enumerator as E
-import Data.Enumerator.Binary as EB
-import Data.Word
-import System.IO
-import System.IO.Error as IOErr
-
-
--- | Enumerate from a handle with the given buffer size (first argument)
--- and timeout in milliseconds (second argument).  If the timeout is
--- exceeded an exception is thrown via 'throwError'.
-
-enumHandleTimeout :: forall b m. MonadIO m =>
-                     Int -> Int -> Handle -> Enumerator ByteString m b
-enumHandleTimeout bufSize timeout h = loop
-    where
-    loop :: Enumerator ByteString m b
-    loop (Continue k) = do
-          mHaveInput <- liftIO $ IOErr.try (hWaitForInput h timeout)
-          case mHaveInput of
-            Left err
-                | isEOFError err -> continue k
-                | otherwise      -> throwError err
-            Right False -> throwError $ userError "Handle timed out"
-            Right True  -> do
-                mStr <- liftIO $ IOErr.try (B.hGetNonBlocking h bufSize)
-                str <- either throwError return mStr
-                if B.null str
-                  then continue k
-                  else k (Chunks [str]) >>== loop
-    loop step = returnI step
-
-
--- | Predicate whether the given byte is a line terminator (includes CR
--- and LF).
-
-isEol :: Word8 -> Bool
-isEol c = c == 10 || c == 13
-
-
--- | Predicate whether the given byte is an LF line terminator.
-
-isNotEol :: Word8 -> Bool
-isNotEol = not . isEol
-
-
--- | Savely read a line with the given maximum length.  If a longer line
--- is enumerated, the excess data is dropped in constant space.  Returns
--- 'Nothing' on EOF.
---
--- Please note that this function is very error-tolerant in the way it
--- handles line endings.  Both CR and LF are proper line terminators.
--- This function ignores empty lines.
-
-netLine :: forall m r. Monad m => Int -> MaybeT r (Iteratee ByteString m) ByteString
-netLine n =
-    lift (EB.dropWhile isEol) >> netLine' n
-
-    where
-    netLine' :: Int -> MaybeT r (Iteratee ByteString m) ByteString
-    netLine' 0 = B.empty <$ lift (EB.dropWhile isNotEol)
-    netLine' n = do
-        c <- liftF EB.head
-        if isNotEol c
-          then B.cons c <$> netLine' (n-1)
-          else return B.empty
-
-
--- | Variant of 'netLine', which supports empty lines, useful for
--- protocols like HTTP, in which empty lines have a special meaning.
--- This function splits the input stream by LF characters while ignoring
--- CR characters.
-
-netLineEmpty :: Monad m => Int -> MaybeT r (Iteratee ByteString m) ByteString
-netLineEmpty 0 = B.empty <$ lift (EB.dropWhile (/= 10) >> EB.head)
-netLineEmpty n = do
-    c <- liftF EB.head
-    case c of
-      10 -> return B.empty
-      13 -> netLineEmpty (n-1)
-      _  -> B.cons c <$> netLineEmpty (n-1)
-
-
--- | Convert a stream of bytes to a stream of lines with the given
--- maximum length.  Longer lines are silently truncated in constant
--- space.
-
-netLines :: forall b m. Monad m => Int -> Enumeratee ByteString ByteString m b
-netLines maxLen = loop
-    where
-    loop :: Enumeratee ByteString ByteString m b
-    loop (Continue k) = do
-        mLine <- evalMaybeT $ netLine maxLen
-        case mLine of
-          Just line -> k (Chunks [line]) >>== loop
-          Nothing   -> k EOF >>== loop
-    loop step = return step
-
-
--- | This is the same like 'netLines', but is based on 'netLinesEmpty'
--- to support empty lines.
-
-netLinesEmpty :: forall b m. Monad m => Int -> Enumeratee ByteString ByteString m b
-netLinesEmpty maxLen = loop
-    where
-    loop :: Enumeratee ByteString ByteString m b
-    loop (Continue k) = do
-        mLine <- evalMaybeT (netLineEmpty maxLen)
-        case mLine of
-          Just line -> k (Chunks [line]) >>== loop
-          Nothing   -> k EOF >>== loop
-    loop step = return step
diff --git a/netlines.cabal b/netlines.cabal
--- a/netlines.cabal
+++ b/netlines.cabal
@@ -1,5 +1,5 @@
 Name:          netlines
-Version:       0.2.1
+Version:       0.3.0
 Category:      Network
 Synopsis:      Enumerator tools for text-based network protocols
 Maintainer:    Ertugrul Söylemez <es@ertes.de>
@@ -8,7 +8,7 @@
 License:       BSD3
 License-file:  LICENSE
 Build-type:    Simple
-Stability:     experimental
+Stability:     beta
 Cabal-version: >= 1.6
 Description:
 
@@ -25,7 +25,7 @@
         enumerator >= 0.4.7
     GHC-Options: -W
     Exposed-modules:
-        Network.NetLines
+        Data.Enumerator.NetLines
 
 -- Executable netlines-test
 --     Build-depends:
