packages feed

pipes-lines-1.0.0.0: src/Pipes/Lines.hs

{-# LANGUAGE LambdaCase, OverloadedStrings, PartialTypeSignatures, NoMonomorphismRestriction, DeriveGeneric, Rank2Types #-}

module Pipes.Lines (
  _unLinesRn, _linesRn, _lineRn
) where

import Data.String (IsString)

import Pipes as P
import Pipes.Group as P

import qualified Data.ByteString.Char8 as B
import qualified Data.Text as T

import Control.Monad (when, (>=>))


class (Eq a, IsString a) => TextLike a where
  tlNull :: a -> Bool
  tlBreakSubstring :: a -> a -> (a, a)
  tlLength :: a -> Int
  tlTake :: Int -> a -> a
  tlDrop :: Int -> a -> a
  tlHead :: a -> Char
  tlLast :: a -> Char
  tlIsPrefixOf :: a -> a -> Bool

instance TextLike B.ByteString where
  tlNull = B.null
  tlBreakSubstring = B.breakSubstring
  tlLength = B.length
  tlTake = B.take
  tlDrop = B.drop
  tlHead = B.head
  tlLast = B.last
  tlIsPrefixOf = B.isPrefixOf

instance TextLike T.Text where
  tlNull = T.null
  tlBreakSubstring = T.breakOn
  tlLength = T.length
  tlTake = T.take
  tlDrop = T.drop
  tlHead = T.head
  tlLast = T.last
  tlIsPrefixOf = T.isPrefixOf


_unLinesRn ::(TextLike a, Monad m) => FreeT (Producer a m) m r -> Producer a m r
_unLinesRn = concats . maps (<* yield "\r\n")

-- | Group a producer of bytestrings into a series of producers delimited by "\r\n"
--   The \r\n are dropped.
_linesRn :: (TextLike a, Monad m) => Producer a m r -> FreeT (Producer a m) m r
_linesRn p = FreeT $ do
  next p >>= return . \case
    Left r       -> Pure r
    Right (bs, p') -> Free (go1 (yield bs >> p'))

  where
    go1 = _lineRn >=> return . _linesRn


_lineRn :: (TextLike a, Monad m) => Producer a m r -> Producer a m (Producer a m r)
_lineRn p = do
  nextNonNull p >>= \case
    Left r -> return (return r)
    Right ("\r\n", p') -> return p'
    Right (bs, p') -> do
      case tlBreakSubstring "\r\n" bs of
        (pref,suff) | tlNull pref && tlIsPrefixOf "\r\n" suff -> return (yield (tlDrop 2 suff) >> p')
        (pref,suff) | tlNull pref -> return (yield suff >> p')
        (pref,suff) | suff == "\r\n" -> yield pref >> return p'
        (pref,suff) | tlIsPrefixOf "\r\n" suff -> yield pref >> return (yield (tlDrop 2 suff) >> p')

        (pref,_) | tlLast pref == '\r' -> nextNonNull p' >>= \case
          Left r -> yield pref >> return (return r)
          Right (bs', p'') | tlNull bs' -> yield "\r" >> return p''
          Right (bs', p'') | tlHead bs' == '\n' ->
            yield (tlTake ((tlLength pref) - 1) pref) >> return (when (tlLength bs' > 1) (yield $ tlDrop 1 bs') >> p'')
          Right (bs', p'') -> yield bs >> _lineRn (yield bs' >> p'')

        (pref, suff) | tlNull suff -> nextNonNull p' >>= \case
          Left r -> yield pref >> return (return r)
          Right (bs', p'') | tlIsPrefixOf "\r\n" bs' -> yield bs >> return (when (tlLength bs' > 2) (yield (tlDrop 2 bs')) >> p'')

          Right (bs', p'') | tlLength bs' == 1 && tlHead bs' == '\r' -> nextNonNull p'' >>= \case
            Left r -> yield bs >> yield bs' >> return (return r)
            Right (bs'', p''') | tlHead bs'' == '\n'-> yield bs >> return (when (tlLength bs'' > 1) (yield (tlDrop 1 bs'')) >> p''')
            Right (bs'', p''') -> yield bs >> yield "\r" >> _lineRn (yield bs'' >> p''')

          Right (bs', p'') -> yield bs >> _lineRn (yield bs' >> p'')

        (pref,suff) -> yield pref >> return (yield (tlDrop 2 suff) >> p')

  where
    dropNulls :: (TextLike a, Monad m) => Producer a m r -> Producer a m (Producer a m r)
    dropNulls = lift . next >=> \case
      Left r -> return (return r)
      Right (bs,p') | tlNull bs -> dropNulls p'
      Right (bs,p') -> return (yield bs >> p')

    nextNonNull :: (TextLike a, Monad m) => Producer a m r -> Producer a m (Either r (a, Producer a m r))
    nextNonNull = dropNulls >=> lift . next