Browse Source

Interruptible streamline, and better docs overall

Marcos Dumay de Medeiros 8 years ago
parent
commit
b569870a20

+ 13 - 1
src/System/IO/Uniform.hs

@@ -9,10 +9,12 @@ module System.IO.Uniform (
   UniformIO(..),
   UniformIO(..),
   TlsSettings(..),
   TlsSettings(..),
   SomeIO(..),
   SomeIO(..),
-  mapOverInput
+  mapOverInput,
+  uGetContents
   ) where
   ) where
 
 
 import Data.ByteString (ByteString)
 import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as LBS
 import Control.Exception
 import Control.Exception
 import Control.Applicative ((<$>))
 import Control.Applicative ((<$>))
 import System.IO.Error
 import System.IO.Error
@@ -81,3 +83,13 @@ mapOverInput io block f initial = do
     Right dt -> do
     Right dt -> do
       i <- f initial dt
       i <- f initial dt
       mapOverInput io block f i
       mapOverInput io block f i
+
+{- |
+Returns the entire contents recieved from this target.
+-}
+uGetContents :: UniformIO io => io -> Int -> IO LBS.ByteString
+uGetContents io block = LBS.fromChunks <$> mapOverInput io block atEnd []
+  where
+    atEnd :: [ByteString] -> ByteString -> IO [ByteString]
+    atEnd bb b = return $ bb ++ [b]
+

+ 134 - 58
src/System/IO/Uniform/Streamline.hs

@@ -1,41 +1,58 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 
--- |
--- Streamline exports a monad that, given an uniform IO target, emulates
--- character tream IO using high performance block IO.
+{- |
+Streamline exports a monad that, given an uniform IO target, emulates
+character stream IO using high performance block IO.
+-}
 module System.IO.Uniform.Streamline (
 module System.IO.Uniform.Streamline (
+  -- * Basic Type
   Streamline,
   Streamline,
-  IOScannerState(..),
+  -- * Running streamline targets
+  -- ** Single pass runners
   withClient,
   withClient,
   withServer,
   withServer,
   withTarget,
   withTarget,
+  -- ** Several pass runner
+  StreamlineState,
+  streamline,
+  resume,
+  close,
+  remaining,
+  -- * Sending and recieving data
   send,
   send,
   send',
   send',
   recieveLine,
   recieveLine,
   recieveLine',
   recieveLine',
-  lazyRecieveLine,
   recieveN,
   recieveN,
   recieveN',
   recieveN',
-  lazyRecieveN,
-  recieveTill,
-  recieveTill',
-  startTls,
+  -- ** Running a parser
   runAttoparsec,
   runAttoparsec,
   runAttoparsecAndReturn,
   runAttoparsecAndReturn,
-  isSecure,
-  setTimeout,
-  setEcho,
+  -- ** Scanning the input
   runScanner,
   runScanner,
   runScanner',
   runScanner',
   scan,
   scan,
   scan',
   scan',
-  textScanner
+  recieveTill,
+  recieveTill',
+  -- ** Deprecated functions
+  lazyRecieveLine,
+  lazyRecieveN,
+  -- * Behavior settings
+  startTls,
+  isSecure,
+  setTimeout,
+  echoTo,
+  setEcho
   ) where
   ) where
 
 
+import System.IO (stdout, Handle)
 import qualified System.IO.Uniform as S
 import qualified System.IO.Uniform as S
 import qualified System.IO.Uniform.Network as N
 import qualified System.IO.Uniform.Network as N
+import qualified System.IO.Uniform.Std as Std
 import System.IO.Uniform (UniformIO, SomeIO(..), TlsSettings)
 import System.IO.Uniform (UniformIO, SomeIO(..), TlsSettings)
 import System.IO.Uniform.Streamline.Scanner
 import System.IO.Uniform.Streamline.Scanner
+import Data.Default.Class
 
 
 import Control.Monad.Trans.Class
 import Control.Monad.Trans.Class
 import Control.Applicative
 import Control.Applicative
@@ -51,31 +68,36 @@ import qualified Data.Char as C
 
 
 import qualified Data.Attoparsec.ByteString as A
 import qualified Data.Attoparsec.ByteString as A
 
 
-data Data = Data {str :: SomeIO, timeout :: Int, buff :: ByteString, isEOF :: Bool, echo :: Bool}
+-- | Internal state for a Streamline monad
+data StreamlineState = StreamlineState {str :: SomeIO, timeout :: Int, buff :: ByteString, isEOF :: Bool, echo :: Maybe Handle}
+instance Default StreamlineState where
+  -- | Will open StdIO
+  def = StreamlineState (SomeIO Std.StdIO) defaultTimeout BS.empty False Nothing
+
 -- | Monad that emulates character stream IO over block IO.
 -- | Monad that emulates character stream IO over block IO.
-newtype Streamline m a = Streamline {withTarget' :: Data -> m (a, Data)}
+newtype Streamline m a = Streamline {withTarget' :: StreamlineState -> m (a, StreamlineState)}
 
 
 blockSize :: Int
 blockSize :: Int
 blockSize = 4096
 blockSize = 4096
 defaultTimeout :: Int
 defaultTimeout :: Int
 defaultTimeout = 1000000 * 600
 defaultTimeout = 1000000 * 600
 
 
-readF :: MonadIO m => Data -> m ByteString
-readF cl = if echo cl
-          then do
-            l <- liftIO $ S.uRead (str cl) blockSize
-            liftIO $ BS.putStr "<"
-            liftIO $ BS.putStr l
-            return l
-          else liftIO $ S.uRead (str cl) blockSize
-
-writeF :: MonadIO m => Data -> ByteString -> m ()
-writeF cl l = if echo cl
-             then do
-               liftIO $ BS.putStr ">"
-               liftIO $ BS.putStr l
-               liftIO $ S.uPut (str cl) l
-             else liftIO $ S.uPut (str cl) l
+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
+  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
 
 
 -- | withServer f serverIP port
 -- | withServer f serverIP port
 --
 --
@@ -83,7 +105,7 @@ writeF cl l = if echo cl
 withServer :: MonadIO m => IP -> Int -> Streamline m a -> m a
 withServer :: MonadIO m => IP -> Int -> Streamline m a -> m a
 withServer host port f = do
 withServer host port f = do
   ds <- liftIO $ N.connectTo host port
   ds <- liftIO $ N.connectTo host port
-  (ret, _) <- withTarget' f $ Data (SomeIO ds) defaultTimeout "" False False
+  (ret, _) <- withTarget' f def{str=SomeIO ds}
   liftIO $ S.uClose ds
   liftIO $ S.uClose ds
   return ret
   return ret
 
 
@@ -94,17 +116,37 @@ withClient :: MonadIO m => N.BoundedPort -> (IP -> Int -> Streamline m a) -> m a
 withClient port f = do
 withClient port f = do
   ds <- liftIO $ N.accept port
   ds <- liftIO $ N.accept port
   (peerIp, peerPort) <- liftIO $ N.getPeer ds
   (peerIp, peerPort) <- liftIO $ N.getPeer ds
-  (ret, _) <- withTarget' (f peerIp peerPort) $ Data (SomeIO ds) defaultTimeout "" False False
+  (ret, _) <- withTarget' (f peerIp peerPort) def{str=SomeIO ds}
   liftIO $ S.uClose ds
   liftIO $ S.uClose ds
   return ret
   return ret
 
 
--- | withTarget f someIO
---
---  Runs f wrapped on a Streamline monad that does IO on nomeIO.
-withTarget :: (MonadIO m, UniformIO a) => a -> Streamline m b -> m b
-withTarget s f = do  
-  (ret, _) <- withTarget' f $ Data (SomeIO s) defaultTimeout "" False False
-  return ret
+{- |
+withTarget f someIO
+
+Runs f wrapped on a Streamline monad that does IO on someIO.
+-}
+withTarget :: (Monad m, UniformIO a) => a -> Streamline m b -> m b
+withTarget s f = do
+  (r, _) <- withTarget' f def{str=SomeIO s}
+  return r
+
+{- |
+Run f wrapped on a Streamline monad, returning the final state in a way that
+can be continued with "resume".
+
+If run with this function, the state must be closed, explicitly with "close" or
+implicitly with "remaining".
+-}
+streamline :: (Monad m, UniformIO a) => a -> Streamline m b -> m (b, StreamlineState)
+streamline s f = withTarget' f def{str=SomeIO s}
+
+{- |
+Continues the execution of functions on a Streamline monad comming from
+"start" or another "resume" call.
+-}
+resume :: Monad m => StreamlineState -> Streamline m b -> m (b, StreamlineState)
+resume dt f = withTarget' f dt
+
 
 
 instance Monad m => Monad (Streamline m) where
 instance Monad m => Monad (Streamline m) where
   --return :: (Monad m) => a -> Streamline m a
   --return :: (Monad m) => a -> Streamline m a
@@ -133,7 +175,7 @@ instance MonadTrans Streamline where
 instance MonadIO m => MonadIO (Streamline m) where
 instance MonadIO m => MonadIO (Streamline m) where
   liftIO = lift . liftIO
   liftIO = lift . liftIO
 
 
--- | Sends data over the streamlines an IO target.
+-- | Sends data over the IO target.
 send :: MonadIO m => ByteString -> Streamline m ()
 send :: MonadIO m => ByteString -> Streamline m ()
 send r = Streamline $ \cl -> do
 send r = Streamline $ \cl -> do
   writeF cl r
   writeF cl r
@@ -146,17 +188,10 @@ send' r = Streamline $ \cl -> do
   mapM (writeF cl) dd
   mapM (writeF cl) dd
   return ((), cl)
   return ((), cl)
 
 
--- | Equivalent to runScanner', but returns a strict, completely
---   evaluated ByteString.
-runScanner :: MonadIO m => s -> IOScanner s -> Streamline m (ByteString, s)
-runScanner state scanner = do
-  (rt, st) <- runScanner' state scanner
-  return (LBS.toStrict rt, st)
-
 {- |
 {- |
 Very much like Attoparsec's runScanner:
 Very much like Attoparsec's runScanner:
 
 
-runScanner' scanner initial_state
+runScanner scanner initial_state
 
 
 Recieves data, running the scanner on each byte,
 Recieves data, running the scanner on each byte,
 using the scanner result as initial state for the
 using the scanner result as initial state for the
@@ -165,13 +200,20 @@ Nothing.
 
 
 Returns the scanned ByteString.
 Returns the scanned ByteString.
  -} 
  -} 
+runScanner :: MonadIO m => s -> IOScanner s -> Streamline m (ByteString, s)
+runScanner state scanner = do
+  (rt, st) <- runScanner' state scanner
+  return (LBS.toStrict rt, st)
+
+-- | Equivalent to runScanner, but returns a strict, completely
+--   evaluated ByteString.
 runScanner' :: MonadIO m => s -> IOScanner s -> Streamline m (LBS.ByteString, s)
 runScanner' :: MonadIO m => s -> IOScanner s -> Streamline m (LBS.ByteString, s)
 runScanner' state scanner = Streamline $ \d ->
 runScanner' state scanner = Streamline $ \d ->
   do
   do
     (tx, st, d') <- in_scan d state
     (tx, st, d') <- in_scan d state
     return ((LBS.fromChunks tx, st), d')
     return ((LBS.fromChunks tx, st), d')
   where
   where
-    --in_scan :: Data -> s -> m ([ByteString], s, Data)
+    --in_scan :: StreamlineState -> s -> m ([ByteString], s, StreamlineState)
     in_scan d st
     in_scan d st
       | isEOF d = eofError "System.IO.Uniform.Streamline.scan'"
       | isEOF d = eofError "System.IO.Uniform.Streamline.scan'"
       | BS.null (buff d) = do
       | BS.null (buff d) = do
@@ -197,11 +239,11 @@ runScanner' state scanner = Streamline $ \d ->
 data ScanResult s = SplitAt Int s | AllInput s
 data ScanResult s = SplitAt Int s | AllInput s
 
 
 
 
--- | Equivalent to runScanner, but dischards the final state
+-- | Equivalent to runScanner, but discards the final state
 scan :: MonadIO m => s -> IOScanner s -> Streamline m ByteString
 scan :: MonadIO m => s -> IOScanner s -> Streamline m ByteString
 scan state scanner = fst <$> runScanner state scanner
 scan state scanner = fst <$> runScanner state scanner
 
 
--- | Equivalent to runScanner', but dischards the final state
+-- | Equivalent to runScanner', but discards the final state
 scan' :: MonadIO m => s -> IOScanner s -> Streamline m LBS.ByteString
 scan' :: MonadIO m => s -> IOScanner s -> Streamline m LBS.ByteString
 scan' state scanner = fst <$> runScanner' state scanner
 scan' state scanner = fst <$> runScanner' state scanner
 
 
@@ -218,7 +260,7 @@ lazyRecieveLine :: MonadIO m => Streamline m [ByteString]
 {-# DEPRECATED #-}
 {-# DEPRECATED #-}
 lazyRecieveLine = Streamline $ \cl -> lazyRecieveLine' cl
 lazyRecieveLine = Streamline $ \cl -> lazyRecieveLine' cl
   where
   where
-    lazyRecieveLine' :: MonadIO m => Data -> m ([ByteString], Data)
+    lazyRecieveLine' :: MonadIO m => StreamlineState -> m ([ByteString], StreamlineState)
     lazyRecieveLine' cl' = 
     lazyRecieveLine' cl' = 
       if isEOF cl'
       if isEOF cl'
       then eofError "System.IO.Uniform.Streamline.lazyRecieveLine"
       then eofError "System.IO.Uniform.Streamline.lazyRecieveLine"
@@ -264,7 +306,7 @@ lazyRecieveN :: (Functor m, MonadIO m) => Int -> Streamline m [ByteString]
 {-# DEPRECATED #-}
 {-# DEPRECATED #-}
 lazyRecieveN n' = Streamline $ \cl' -> lazyRecieveN' cl' n'
 lazyRecieveN n' = Streamline $ \cl' -> lazyRecieveN' cl' n'
   where
   where
-    lazyRecieveN' :: (Functor m, MonadIO m) => Data -> Int -> m ([ByteString], Data)
+    lazyRecieveN' :: (Functor m, MonadIO m) => StreamlineState -> Int -> m ([ByteString], StreamlineState)
     lazyRecieveN' cl n =
     lazyRecieveN' cl n =
       if isEOF cl
       if isEOF cl
       then eofError "System.IO.Uniform.Streamline.lazyRecieveN"
       then eofError "System.IO.Uniform.Streamline.lazyRecieveN"
@@ -318,7 +360,7 @@ runAttoparsecAndReturn p = Streamline $ \cl ->
     (cl', i, a) <- liftIO $ continueResult cl c
     (cl', i, a) <- liftIO $ continueResult cl c
     return ((i, a), cl')
     return ((i, a), cl')
   where
   where
-    continueResult :: Data -> A.Result a -> IO (Data, ByteString, (Either String a))
+    continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, ByteString, (Either String a))
     -- tx eof ds 
     -- tx eof ds 
     continueResult cl c = case c of
     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.Fail i _ msg -> return (cl{buff=i}, BS.take (BS.length (buff cl) - BS.length i) (buff cl), Left msg)
@@ -339,7 +381,7 @@ runAttoparsec p = Streamline $ \cl ->
     (cl', a) <- liftIO $ continueResult cl c
     (cl', a) <- liftIO $ continueResult cl c
     return (a, cl')
     return (a, cl')
   where
   where
-    continueResult :: Data -> A.Result a -> IO (Data, (Either String a))
+    continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, (Either String a))
     continueResult cl c = case c of
     continueResult cl c = case c of
         A.Fail i _ msg -> return (cl{buff=i}, Left msg)
         A.Fail i _ msg -> return (cl{buff=i}, Left msg)
         A.Done i r -> return (cl{buff=i}, Right r)
         A.Done i r -> return (cl{buff=i}, Right r)
@@ -347,7 +389,7 @@ runAttoparsec p = Streamline $ \cl ->
           d <- readF cl
           d <- readF cl
           let eof' = BS.null d
           let eof' = BS.null d
           continueResult cl{buff=d}{isEOF=eof'} (c' d)
           continueResult cl{buff=d}{isEOF=eof'} (c' d)
-  
+
 -- | Indicates whether transport layer security is being used.
 -- | Indicates whether transport layer security is being used.
 isSecure :: Monad m => Streamline m Bool
 isSecure :: Monad m => Streamline m Bool
 isSecure = Streamline $ \cl -> return (S.isSecure $ str cl, cl)
 isSecure = Streamline $ \cl -> return (S.isSecure $ str cl, cl)
@@ -361,7 +403,20 @@ setTimeout t = Streamline $ \cl -> return ((), cl{timeout=t})
 --   will be echoed in stdout, with ">" and "<" markers indicating
 --   will be echoed in stdout, with ">" and "<" markers indicating
 --   what is read and written.
 --   what is read and written.
 setEcho :: Monad m => Bool -> Streamline m ()
 setEcho :: Monad m => Bool -> Streamline m ()
-setEcho e = Streamline $ \cl -> return ((), cl{echo=e})
+setEcho e = Streamline $ \cl ->
+  if e then return ((), cl{echo=Just stdout}) else return ((), cl{echo=Nothing})
+
+{- |
+Sets echo of the streamlined IO target.
+
+If echo is set, all the data read an written to the target
+will be echoed to the handle, with ">" and "<" markers indicating
+what is read and written.
+
+Setting to Nothing will disable echo.
+-}
+echoTo :: Monad m => Maybe Handle -> Streamline m ()
+echoTo h = Streamline $ \cl -> return ((), cl{echo=h})
 
 
 lineWithEol :: A.Parser (ByteString, ByteString)
 lineWithEol :: A.Parser (ByteString, ByteString)
 lineWithEol = do
 lineWithEol = do
@@ -377,3 +432,24 @@ lineScanner False c
   | c == (fromIntegral . C.ord $ '\n') = Just True
   | c == (fromIntegral . C.ord $ '\n') = Just True
   | otherwise = Just False
   | otherwise = Just False
 lineScanner True _ = Nothing
 lineScanner True _ = Nothing
+
+{- |
+Closes the target of a streamline state, releasing any used resource.
+-}
+close :: MonadIO m => StreamlineState -> m ()
+close st = liftIO . S.uClose . str $ st
+
+{- |
+Retrieves the remaining contents of a streamline state, closing it afterwards.
+-}
+remaining :: MonadIO m => StreamlineState -> m LBS.ByteString
+remaining st =
+  if isEOF st then close st >> return LBS.empty
+  else
+    if BS.null . buff $ st
+    then do
+      dt <- readF st
+      remaining st{buff=dt}{isEOF=BS.null dt}
+    else do
+      dt' <- remaining st{buff=BS.empty}
+      return $ LBS.append (LBS.fromStrict . buff $ st) dt'

+ 11 - 1
src/System/IO/Uniform/Streamline/Scanner.hs

@@ -1,4 +1,9 @@
-module System.IO.Uniform.Streamline.Scanner where
+module System.IO.Uniform.Streamline.Scanner (
+  IOScanner,
+  IOScannerState(..),
+  textScanner,
+  anyScanner
+  )where
 
 
 import Control.Applicative
 import Control.Applicative
 import Data.Default.Class
 import Data.Default.Class
@@ -43,6 +48,7 @@ instance Monad IOScannerState where
 
 
 type IOScanner a = a -> Word8 -> IOScannerState a
 type IOScanner a = a -> Word8 -> IOScannerState a
 
 
+-- | Creates a scanner that'll finish when any of the given scanners finish.
 anyScanner :: Default a => [IOScanner a] -> IOScanner [a]
 anyScanner :: Default a => [IOScanner a] -> IOScanner [a]
 anyScanner scanners = scan
 anyScanner scanners = scan
   where
   where
@@ -54,6 +60,10 @@ anyScanner scanners = scan
     apScanner (s:ss) (t:tt) h = s t h : apScanner ss tt h
     apScanner (s:ss) (t:tt) h = s t h : apScanner ss tt h
 
 
 
 
+{- |
+Given a sequence of bytes, creates a scanner that will scan
+its input untill that sequence is found.
+-}
 textScanner :: [Word8] -> (IOScanner [[Word8]])
 textScanner :: [Word8] -> (IOScanner [[Word8]])
 textScanner [] = \_ _ -> Finished
 textScanner [] = \_ _ -> Finished
 textScanner t@(c:_) = scanner
 textScanner t@(c:_) = scanner

+ 1 - 1
uniform-io.cabal

@@ -10,7 +10,7 @@ name:                uniform-io
 -- PVP summary:      +-+------- breaking API changes
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
 --                   | | | +--- code changes with no API change
-version:    1.1.1.0
+version:    1.1.2.0
 
 
 -- A short (one-line) description of the package.
 -- A short (one-line) description of the package.
 synopsis:   Uniform IO over files, network, anything.
 synopsis:   Uniform IO over files, network, anything.