|  | @@ -37,6 +37,7 @@ module System.IO.Uniform.Streamline (
 | 
	
		
			
				|  |  |    startTls,
 | 
	
		
			
				|  |  |    isSecure,
 | 
	
		
			
				|  |  |    transformTarget,
 | 
	
		
			
				|  |  | +  limitInput,
 | 
	
		
			
				|  |  |    echoTo,
 | 
	
		
			
				|  |  |    setEcho
 | 
	
		
			
				|  |  |    ) where
 | 
	
	
		
			
				|  | @@ -52,7 +53,7 @@ import Data.Default.Class
 | 
	
		
			
				|  |  |  import Control.Monad.Trans.Class
 | 
	
		
			
				|  |  |  import Control.Monad.Trans.Interruptible
 | 
	
		
			
				|  |  |  import Control.Monad.Trans.Control
 | 
	
		
			
				|  |  | -import Control.Monad (ap, liftM)
 | 
	
		
			
				|  |  | +import Control.Monad
 | 
	
		
			
				|  |  |  import Control.Monad.Base
 | 
	
		
			
				|  |  |  import Control.Monad.IO.Class
 | 
	
		
			
				|  |  |  import System.IO.Error
 | 
	
	
		
			
				|  | @@ -65,10 +66,10 @@ import Data.IP (IP)
 | 
	
		
			
				|  |  |  import qualified Data.Attoparsec.ByteString as A
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Internal state for a Streamline monad
 | 
	
		
			
				|  |  | -data StreamlineState = StreamlineState {str :: SomeIO, buff :: ByteString, isEOF :: Bool, echo :: Maybe Handle}
 | 
	
		
			
				|  |  | +data StreamlineState = StreamlineState {str :: SomeIO, buff :: ByteString, isEOF :: Bool, echo :: Maybe Handle, inLimit :: Int, sentEmpty :: Bool}
 | 
	
		
			
				|  |  |  instance Default StreamlineState where
 | 
	
		
			
				|  |  |    -- | Will open StdIO
 | 
	
		
			
				|  |  | -  def = StreamlineState (SomeIO Std.StdIO) BS.empty False Nothing
 | 
	
		
			
				|  |  | +  def = StreamlineState (SomeIO Std.StdIO) BS.empty False Nothing (-1) False
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Monad that emulates character stream IO over block IO.
 | 
	
		
			
				|  |  |  newtype Streamline m a = Streamline {withTarget' :: StreamlineState -> m (a, StreamlineState)}
 | 
	
	
		
			
				|  | @@ -76,22 +77,57 @@ newtype Streamline m a = Streamline {withTarget' :: StreamlineState -> m (a, Str
 | 
	
		
			
				|  |  |  blockSize :: Int
 | 
	
		
			
				|  |  |  blockSize = 4096
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -readF :: MonadIO m => StreamlineState -> m ByteString
 | 
	
		
			
				|  |  | -readF cl = case echo cl of
 | 
	
		
			
				|  |  | -  Just h -> do
 | 
	
		
			
				|  |  | -    l <- liftIO $ S.uRead (str cl) blockSize
 | 
	
		
			
				|  |  | -    liftIO $ BS.hPutStr h "< "
 | 
	
		
			
				|  |  | -    liftIO $ BS.hPutStr h l
 | 
	
		
			
				|  |  | -    return l
 | 
	
		
			
				|  |  | -  Nothing -> liftIO $ S.uRead (str cl) blockSize
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -writeF :: MonadIO m => StreamlineState -> ByteString -> m ()
 | 
	
		
			
				|  |  | -writeF cl l = case echo cl of
 | 
	
		
			
				|  |  | +readF :: MonadIO m => Streamline m ()
 | 
	
		
			
				|  |  | +readF = -- Must try just not to read more than the limit, actual limiting is done by takeBuff
 | 
	
		
			
				|  |  | +  Streamline $ \cl -> if not . BS.null . buff $ cl then return ((), cl)
 | 
	
		
			
				|  |  | +  else do
 | 
	
		
			
				|  |  | +  let lim = inLimit cl
 | 
	
		
			
				|  |  | +      sz = if lim < 0 then blockSize
 | 
	
		
			
				|  |  | +           else if lim <= blockSize then lim
 | 
	
		
			
				|  |  | +                else blockSize
 | 
	
		
			
				|  |  | +  l <- liftIO $ S.uRead (str cl) sz
 | 
	
		
			
				|  |  | +  let cl' = cl{buff= l}
 | 
	
		
			
				|  |  | +  case echo cl of
 | 
	
		
			
				|  |  | +    Just h -> do
 | 
	
		
			
				|  |  | +      liftIO $ BS.hPutStr h "< "
 | 
	
		
			
				|  |  | +      liftIO $ BS.hPutStr h l
 | 
	
		
			
				|  |  | +    Nothing -> return ()
 | 
	
		
			
				|  |  | +  return ((), cl')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +-- | Takes the buffer for processing
 | 
	
		
			
				|  |  | +takeBuff :: MonadIO m => Streamline m ByteString
 | 
	
		
			
				|  |  | +takeBuff = do
 | 
	
		
			
				|  |  | +  readF
 | 
	
		
			
				|  |  | +  Streamline $ \cl -> 
 | 
	
		
			
				|  |  | +    let lim = inLimit cl
 | 
	
		
			
				|  |  | +        eof = isEOF cl
 | 
	
		
			
				|  |  | +        b = buff cl
 | 
	
		
			
				|  |  | +    in if eof then eofError "System.IO.Uniform.Streamline"
 | 
	
		
			
				|  |  | +       else if lim < 0 then return (b, cl{buff="", isEOF=BS.null b})
 | 
	
		
			
				|  |  | +            else let (r, b') = BS.splitAt lim b
 | 
	
		
			
				|  |  | +                 in return (r, cl{
 | 
	
		
			
				|  |  | +                                                        isEOF = BS.null b || sentEmpty cl,
 | 
	
		
			
				|  |  | +                                                        sentEmpty = BS.null r,
 | 
	
		
			
				|  |  | +                                                        buff = b',
 | 
	
		
			
				|  |  | +                                                        inLimit = lim - BS.length r
 | 
	
		
			
				|  |  | +                                                        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +-- | Pushes remaining data back into the buffer
 | 
	
		
			
				|  |  | +pushBuff :: Monad m => ByteString -> Streamline m ()
 | 
	
		
			
				|  |  | +pushBuff dt = Streamline $ \cl -> let
 | 
	
		
			
				|  |  | +  lim = inLimit cl
 | 
	
		
			
				|  |  | +  b = buff cl
 | 
	
		
			
				|  |  | +  in if lim <= 0 then return ((), cl{buff = BS.append dt b})
 | 
	
		
			
				|  |  | +     else return ((), cl{buff = BS.append dt b, inLimit = lim - BS.length dt})
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +writeF :: MonadIO m => ByteString -> Streamline m ()
 | 
	
		
			
				|  |  | +writeF l = Streamline $ \cl -> case echo cl of
 | 
	
		
			
				|  |  |    Just h -> do
 | 
	
		
			
				|  |  |      liftIO $ BS.hPutStr h "> "
 | 
	
		
			
				|  |  |      liftIO $ BS.hPutStr h l
 | 
	
		
			
				|  |  |      liftIO $ S.uPut (str cl) l
 | 
	
		
			
				|  |  | -  Nothing -> liftIO $ S.uPut (str cl) l
 | 
	
		
			
				|  |  | +    return ((), cl)
 | 
	
		
			
				|  |  | +  Nothing -> liftIO $ S.uPut (str cl) l >> return ((), cl)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | > withServer f serverIP port
 | 
	
		
			
				|  |  |  --
 | 
	
	
		
			
				|  | @@ -153,16 +189,13 @@ instance MonadIO m => MonadIO (Streamline m) where
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Sends data over the IO target.
 | 
	
		
			
				|  |  |  send :: MonadIO m => ByteString -> Streamline m ()
 | 
	
		
			
				|  |  | -send r = Streamline $ \cl -> do
 | 
	
		
			
				|  |  | -  writeF cl r
 | 
	
		
			
				|  |  | -  return ((), cl)
 | 
	
		
			
				|  |  | +send r = writeF r
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Sends data from a lazy byte string
 | 
	
		
			
				|  |  |  send' :: MonadIO m => LBS.ByteString -> Streamline m ()
 | 
	
		
			
				|  |  | -send' r = Streamline $ \cl -> do
 | 
	
		
			
				|  |  | +send' r = do
 | 
	
		
			
				|  |  |    let dd = LBS.toChunks r
 | 
	
		
			
				|  |  | -  mapM_ (writeF cl) dd
 | 
	
		
			
				|  |  | -  return ((), cl)
 | 
	
		
			
				|  |  | +  mapM_ writeF dd
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  {- |
 | 
	
		
			
				|  |  |  Very much like Attoparsec's runScanner:
 | 
	
	
		
			
				|  | @@ -181,29 +214,24 @@ runScanner state scanner = do
 | 
	
		
			
				|  |  |    (rt, st) <- runScanner' state scanner
 | 
	
		
			
				|  |  |    return (LBS.toStrict rt, st)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | --- | Equivalent to runScanner, but returns a strict, completely
 | 
	
		
			
				|  |  | ---   evaluated ByteString.
 | 
	
		
			
				|  |  | +-- | Equivalent to runScanner, but returns a lazy ByteString
 | 
	
		
			
				|  |  |  runScanner' :: MonadIO m => s -> IOScanner s -> Streamline m (LBS.ByteString, s)
 | 
	
		
			
				|  |  | -runScanner' state scanner = Streamline $ \d ->
 | 
	
		
			
				|  |  | -  do
 | 
	
		
			
				|  |  | -    (tx, st, d') <- in_scan d state
 | 
	
		
			
				|  |  | -    return ((LBS.fromChunks tx, st), d')
 | 
	
		
			
				|  |  | +runScanner' state scanner = do
 | 
	
		
			
				|  |  | +  (tx, st) <- in_scan state
 | 
	
		
			
				|  |  | +  return (LBS.fromChunks tx, st)
 | 
	
		
			
				|  |  |    where
 | 
	
		
			
				|  |  | -    --in_scan :: StreamlineState -> s -> m ([ByteString], s, StreamlineState)
 | 
	
		
			
				|  |  | -    in_scan d st
 | 
	
		
			
				|  |  | -      | isEOF d = eofError "System.IO.Uniform.Streamline.scan'"
 | 
	
		
			
				|  |  | -      | BS.null (buff d) = do
 | 
	
		
			
				|  |  | -        dt <- readF d
 | 
	
		
			
				|  |  | -        if BS.null dt
 | 
	
		
			
				|  |  | -          then return ([], st, d{isEOF=True})
 | 
	
		
			
				|  |  | -          else in_scan d{buff=dt} st
 | 
	
		
			
				|  |  | -      | otherwise = case sscan scanner st 0 (BS.unpack . buff $ d) of
 | 
	
		
			
				|  |  | -        AllInput st' -> do
 | 
	
		
			
				|  |  | -          (tx', st'', d') <- in_scan d{buff=""} st'
 | 
	
		
			
				|  |  | -          return (buff d:tx', st'', d')
 | 
	
		
			
				|  |  | -        SplitAt n st' -> let
 | 
	
		
			
				|  |  | -          (r, i) = BS.splitAt n (buff d)
 | 
	
		
			
				|  |  | -          in return ([r], st', d{buff=i})
 | 
	
		
			
				|  |  | +    --in_scan :: MonadIO m => s -> Streamline m ([ByteString], s)
 | 
	
		
			
				|  |  | +    in_scan st = do
 | 
	
		
			
				|  |  | +      d <- takeBuff
 | 
	
		
			
				|  |  | +      if BS.null d then return ([], st)
 | 
	
		
			
				|  |  | +        else case sscan scanner st 0 $ BS.unpack d of
 | 
	
		
			
				|  |  | +               AllInput st' -> do
 | 
	
		
			
				|  |  | +                 (tx', st'') <- in_scan st'
 | 
	
		
			
				|  |  | +                 return (d:tx', st'')
 | 
	
		
			
				|  |  | +               SplitAt n st' -> do
 | 
	
		
			
				|  |  | +                 let (r, i) = BS.splitAt n d
 | 
	
		
			
				|  |  | +                 pushBuff i
 | 
	
		
			
				|  |  | +                 return ([r], st')
 | 
	
		
			
				|  |  |      -- I'll avoid rebuilding a list on high level code. The ByteString functions are way better.
 | 
	
		
			
				|  |  |      sscan :: (s -> Word8 -> IOScannerState s) -> s -> Int -> [Word8] -> ScanResult s
 | 
	
		
			
				|  |  |      sscan _ s0 _ [] = AllInput s0
 | 
	
	
		
			
				|  | @@ -231,29 +259,27 @@ recieveLine = recieveTill "\n"
 | 
	
		
			
				|  |  |  recieveLine' :: MonadIO m => Streamline m LBS.ByteString
 | 
	
		
			
				|  |  |  recieveLine' = recieveTill' "\n"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | --- | Recieves the given number of bytes.
 | 
	
		
			
				|  |  | +{- |
 | 
	
		
			
				|  |  | +Recieves the given number of bytes, or less in case of end of file.
 | 
	
		
			
				|  |  | +-}
 | 
	
		
			
				|  |  |  recieveN :: MonadIO m => Int -> Streamline m ByteString
 | 
	
		
			
				|  |  |  recieveN n = LBS.toStrict <$> recieveN' n
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Lazy version of recieveN
 | 
	
		
			
				|  |  |  recieveN' :: MonadIO m => Int -> Streamline m LBS.ByteString
 | 
	
		
			
				|  |  | -recieveN' n | n <= 0 = return ""
 | 
	
		
			
				|  |  | -            | otherwise = Streamline $ \cl ->
 | 
	
		
			
				|  |  | -            do
 | 
	
		
			
				|  |  | -              (tt, cl') <- recieve cl n
 | 
	
		
			
				|  |  | -              return (LBS.fromChunks tt, cl')
 | 
	
		
			
				|  |  | +recieveN' n = LBS.fromChunks <$> recieve n
 | 
	
		
			
				|  |  |    where
 | 
	
		
			
				|  |  | -    recieve d b
 | 
	
		
			
				|  |  | -      | isEOF d = eofError "System.IO.Uniform.Streamline.recieveN"
 | 
	
		
			
				|  |  | -      | BS.null . buff $ d = do
 | 
	
		
			
				|  |  | -        dt <- readF d
 | 
	
		
			
				|  |  | -        recieve d{buff=dt}{isEOF=BS.null dt} b
 | 
	
		
			
				|  |  | -      | b <= (BS.length . buff $ d) = let
 | 
	
		
			
				|  |  | -        (r, dt) = BS.splitAt b $ buff d
 | 
	
		
			
				|  |  | -        in return ([r], d{buff=dt})
 | 
	
		
			
				|  |  | +    recieve sz
 | 
	
		
			
				|  |  | +      | sz <= 0 = return []
 | 
	
		
			
				|  |  |        | otherwise = do
 | 
	
		
			
				|  |  | -        (r, d') <- recieve d{buff=""} $ b - (BS.length . buff $ d)
 | 
	
		
			
				|  |  | -        return (buff d : r, d')
 | 
	
		
			
				|  |  | +          d <- takeBuff
 | 
	
		
			
				|  |  | +          if BS.null d then return []
 | 
	
		
			
				|  |  | +            else do
 | 
	
		
			
				|  |  | +            let (h, t) = BS.splitAt sz d
 | 
	
		
			
				|  |  | +                sz' = sz - BS.length h
 | 
	
		
			
				|  |  | +            unless (BS.null t) $ pushBuff t
 | 
	
		
			
				|  |  | +            r <- recieve sz'
 | 
	
		
			
				|  |  | +            return $ h : r
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Recieves data until it matches the argument.
 | 
	
		
			
				|  |  |  --   Returns all of it, including the matching data.
 | 
	
	
		
			
				|  | @@ -277,43 +303,26 @@ startTls st = Streamline $ \cl -> do
 | 
	
		
			
				|  |  |  --  streamlined IO target. Returns both the parser
 | 
	
		
			
				|  |  |  --  result and the string consumed by it.
 | 
	
		
			
				|  |  |  runAttoparsecAndReturn :: MonadIO m => A.Parser a -> Streamline m (ByteString, Either String a)
 | 
	
		
			
				|  |  | -runAttoparsecAndReturn p = Streamline $ \cl ->
 | 
	
		
			
				|  |  | -  if isEOF cl
 | 
	
		
			
				|  |  | -  then eofError "System.IO.Uniform.Streamline.runAttoparsecAndReturn"
 | 
	
		
			
				|  |  | -  else do
 | 
	
		
			
				|  |  | -    let c = A.parse p $ buff cl
 | 
	
		
			
				|  |  | -    (cl', i, a) <- liftIO $ continueResult cl c
 | 
	
		
			
				|  |  | -    return ((i, a), cl')
 | 
	
		
			
				|  |  | +runAttoparsecAndReturn p = do
 | 
	
		
			
				|  |  | +  d <- takeBuff
 | 
	
		
			
				|  |  | +  let c = A.parse p d
 | 
	
		
			
				|  |  | +  continueResult c d
 | 
	
		
			
				|  |  |    where
 | 
	
		
			
				|  |  | -    continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, ByteString, Either String a)
 | 
	
		
			
				|  |  | -    -- tx eof ds 
 | 
	
		
			
				|  |  | -    continueResult cl c = case c of
 | 
	
		
			
				|  |  | -      A.Fail i _ msg -> return (cl{buff=i}, BS.take (BS.length (buff cl) - BS.length i) (buff cl), Left msg)
 | 
	
		
			
				|  |  | -      A.Done i r -> return (cl{buff=i}, BS.take (BS.length (buff cl) - BS.length i) (buff cl), Right r)
 | 
	
		
			
				|  |  | +    continueResult c d = case c of
 | 
	
		
			
				|  |  | +      A.Fail i _ msg -> do
 | 
	
		
			
				|  |  | +        pushBuff i
 | 
	
		
			
				|  |  | +        return (BS.take (BS.length d - BS.length i) d, Left msg)
 | 
	
		
			
				|  |  | +      A.Done i r -> do
 | 
	
		
			
				|  |  | +        pushBuff i
 | 
	
		
			
				|  |  | +        return (BS.take (BS.length d - BS.length i) d, Right r)
 | 
	
		
			
				|  |  |        A.Partial c' -> do
 | 
	
		
			
				|  |  | -        d <- readF cl
 | 
	
		
			
				|  |  | -        let cl' = cl{buff=BS.append (buff cl) d}{isEOF=BS.null d}
 | 
	
		
			
				|  |  | -        continueResult cl' (c' d)
 | 
	
		
			
				|  |  | +        dt <- takeBuff
 | 
	
		
			
				|  |  | +        continueResult (c' dt) dt
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Runs an Attoparsec parser over the data read from the
 | 
	
		
			
				|  |  |  --  streamlined IO target. Returning the parser result.
 | 
	
		
			
				|  |  |  runAttoparsec :: MonadIO m => A.Parser a -> Streamline m (Either String a)
 | 
	
		
			
				|  |  | -runAttoparsec p = Streamline $ \cl -> 
 | 
	
		
			
				|  |  | -  if isEOF cl
 | 
	
		
			
				|  |  | -  then eofError "System.IO.Uniform.Streamline.runAttoparsec"
 | 
	
		
			
				|  |  | -  else do
 | 
	
		
			
				|  |  | -    let c = A.parse p $ buff cl
 | 
	
		
			
				|  |  | -    (cl', a) <- liftIO $ continueResult cl c
 | 
	
		
			
				|  |  | -    return (a, cl')
 | 
	
		
			
				|  |  | -  where
 | 
	
		
			
				|  |  | -    continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, Either String a)
 | 
	
		
			
				|  |  | -    continueResult cl c = case c of
 | 
	
		
			
				|  |  | -        A.Fail i _ msg -> return (cl{buff=i}, Left msg)
 | 
	
		
			
				|  |  | -        A.Done i r -> return (cl{buff=i}, Right r)
 | 
	
		
			
				|  |  | -        A.Partial c' -> do
 | 
	
		
			
				|  |  | -          d <- readF cl
 | 
	
		
			
				|  |  | -          let eof' = BS.null d
 | 
	
		
			
				|  |  | -          continueResult cl{buff=d}{isEOF=eof'} (c' d)
 | 
	
		
			
				|  |  | +runAttoparsec p = snd <$> runAttoparsecAndReturn p
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  -- | Indicates whether transport layer security is being used.
 | 
	
		
			
				|  |  |  isSecure :: Monad m => Streamline m Bool
 | 
	
	
		
			
				|  | @@ -335,6 +344,14 @@ Discards all buffered data in the process.
 | 
	
		
			
				|  |  |  transformTarget :: (UniformIO a, Monad m) => (SomeIO -> a) -> Streamline m ()
 | 
	
		
			
				|  |  |  transformTarget w = Streamline $ \cl -> return ((), cl{str = SomeIO . w . str $ cl})
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +{- |
 | 
	
		
			
				|  |  | +Limits the input to the given number of bytes, emulating an end of file after them.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +If the limit is negative, the input will not be limited.
 | 
	
		
			
				|  |  | +-}
 | 
	
		
			
				|  |  | +limitInput :: Monad m => Int -> Streamline m ()
 | 
	
		
			
				|  |  | +limitInput n = Streamline $ \cl -> return ((), cl{inLimit = n})
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  {- |
 | 
	
		
			
				|  |  |  Sets echo of the streamlined IO target.
 | 
	
		
			
				|  |  |  
 |