potoki 0.11.1 → 0.11.2
raw patch · 10 files changed
+527/−404 lines, 10 filesdep +ptr
Dependencies added: ptr
Files
- library/Potoki/Prelude.hs +16/−0
- library/Potoki/Transform.hs +13/−277
- library/Potoki/Transform/Attoparsec.hs +84/−0
- library/Potoki/Transform/Basic.hs +133/−0
- library/Potoki/Transform/ByteString.hs +61/−0
- library/Potoki/Transform/FileIO.hs +30/−0
- library/Potoki/Transform/State.hs +43/−0
- potoki.cabal +9/−3
- test/Main.hs +138/−0
- tests/Main.hs +0/−124
library/Potoki/Prelude.hs view
@@ -3,6 +3,7 @@ module Exports, ioChunkSize, textString,+ unsnoc, ) where @@ -54,3 +55,18 @@ textString :: Text -> String textString = A.unpack++{-# INLINABLE unsnoc #-}+unsnoc :: [a] -> Maybe ([a], a)+unsnoc list =+ case process list of+ (init, lastMaybe) -> fmap (\ last -> (init, last)) lastMaybe+ where+ process list =+ case list of+ head : tail -> case tail of+ [] -> ([], Just head)+ _ -> case process tail of+ (init, lastMaybe) -> (head : init, lastMaybe)+ _ -> ([], Nothing)+
library/Potoki/Transform.hs view
@@ -16,15 +16,17 @@ vector, distinctBy, distinct,- builderChunks, executeIO, mapInIO,- runState,- execState,- evalState,+ -- * ByteString+ module Potoki.Transform.ByteString,+ -- * State+ R.runState,+ R.execState,+ R.evalState, -- * Parsing- parseBytes,- parseText,+ A.parseBytes,+ A.parseText, -- * Concurrency N.bufferize, N.concurrently,@@ -38,277 +40,11 @@ ) where -import Potoki.Prelude hiding (take, takeWhile, filter, drop) import Potoki.Core.Transform-import qualified Potoki.Fetch as A-import qualified Potoki.Core.Fetch as A-import qualified Potoki.Core.IO as G-import qualified Potoki.Core.Produce as H-import qualified Data.Attoparsec.ByteString as K-import qualified Data.Attoparsec.Text as L-import qualified Data.Attoparsec.Types as M-import qualified Data.HashSet as C-import qualified Data.ByteString.Builder as E-import qualified Data.ByteString.Lazy as F-import qualified Data.ByteString as J-import qualified Data.Text.IO as Q-import qualified Data.Vector as P-import qualified System.Directory as I-import qualified Control.Concurrent.Chan.Unagi.Bounded as B-import qualified Control.Monad.Trans.State.Strict as O+import Potoki.Transform.Basic+import Potoki.Transform.FileIO+import Potoki.Transform.ByteString+import qualified Potoki.Transform.Attoparsec as A import qualified Potoki.Transform.Concurrency as N---{-# INLINE mapFilter #-}-mapFilter :: (input -> Maybe output) -> Transform input output-mapFilter mapping =- Transform (pure . A.mapFilter mapping)--{-# INLINE filter #-}-filter :: (input -> Bool) -> Transform input input-filter predicate =- Transform (pure . A.filter predicate)--{-# INLINE just #-}-just :: Transform (Maybe input) input-just =- Transform (pure . A.just)--{-# INLINE takeWhile #-}-takeWhile :: (input -> Bool) -> Transform input input-takeWhile predicate =- Transform (pure . A.takeWhile predicate)--{-# INLINE drop #-}-drop :: Int -> Transform input input-drop amount =- Transform $ \ (A.Fetch fetchIO) -> do- countRef <- newIORef amount- return $ A.Fetch $ \ nil just -> fix $ \ loop -> do- count <- readIORef countRef- if count > 0- then do- writeIORef countRef $! pred count- loop- else fetchIO nil just--{-# INLINE mapWithParseResult #-}-mapWithParseResult :: forall input parsed. (Monoid input, Eq input) => (input -> M.IResult input parsed) -> Transform input (Either Text parsed)-mapWithParseResult inputToResult =- Transform $ \ inputFetch ->- do- unconsumedRef <- newIORef mempty- finishedRef <- newIORef False- return (A.Fetch (fetchParsed inputFetch finishedRef unconsumedRef))- where- fetchParsed :: A.Fetch input -> IORef Bool -> IORef input -> forall x. x -> (Either Text parsed -> x) -> IO x- fetchParsed (A.Fetch inputFetchIO) finishedRef unconsumedRef nil just =- do- finished <- readIORef finishedRef- if finished- then return nil- else do- unconsumed <- readIORef unconsumedRef- if unconsumed == mempty- then- join $ inputFetchIO- (return nil)- (\input -> do- if input == mempty- then return nil- else matchResult (inputToResult input))- else do- writeIORef unconsumedRef mempty- matchResult (inputToResult unconsumed)- where- matchResult =- \case- M.Partial inputToResult ->- consume inputToResult- M.Done unconsumed parsed ->- do- writeIORef unconsumedRef unconsumed- return (just (Right parsed))- M.Fail unconsumed contexts message ->- do- writeIORef unconsumedRef unconsumed- writeIORef finishedRef True- return (just (Left resultMessage))- where- resultMessage =- if null contexts- then fromString message- else fromString (showString (intercalate " > " contexts) (showString ": " message))- consume inputToResult =- join $ inputFetchIO- (do- writeIORef finishedRef True- matchResult (inputToResult mempty))- (\input -> do- when (input == mempty) (writeIORef finishedRef True)- matchResult (inputToResult input))--{-|-Lift an Attoparsec ByteString parser.--}-{-# INLINE parseBytes #-}-parseBytes :: K.Parser parsed -> Transform ByteString (Either Text parsed)-parseBytes parser =- mapWithParseResult (K.parse parser)--{-|-Lift an Attoparsec Text parser.--}-{-# INLINE parseText #-}-parseText :: L.Parser parsed -> Transform Text (Either Text parsed)-parseText parser =- mapWithParseResult (L.parse parser)--{-# INLINE mapInIO #-}-mapInIO :: (a -> IO b) -> Transform a b-mapInIO io =- Transform $ \ (A.Fetch fetch) ->- return $ A.Fetch $ \ nil just ->- join $ fetch (return nil) $ (fmap . fmap) just io--{-# INLINE deleteFile #-}-deleteFile :: Transform FilePath (Either IOException ())-deleteFile =- mapInIO (try . I.removeFile)--{-# INLINE appendBytesToFile #-}-appendBytesToFile :: Transform (FilePath, ByteString) (Either IOException ())-appendBytesToFile =- mapInIO $ \ (path, bytes) ->- try $ - withFile path AppendMode $ \ handle -> - J.hPut handle bytes--{-# INLINABLE writeTextToFile #-}-writeTextToFile :: Transform (FilePath, Text) (Either IOException ())-writeTextToFile =- mapInIO $ \ (path, text) ->- try $ - Q.writeFile path text--{-# INLINE distinctBy #-}-distinctBy :: (Eq comparable, Hashable comparable) => (element -> comparable) -> Transform element element-distinctBy f =- Transform $ \ (A.Fetch fetch) -> do- stateRef <- newIORef mempty- return $ A.Fetch $ \ nil just -> fix $ \ loop -> join $ fetch (return nil) $ \ !input -> do- let comparable = f input- !set <- readIORef stateRef- if C.member comparable set- then loop- else do- writeIORef stateRef $! C.insert comparable set- return (just input)--{-# INLINE distinct #-}-distinct :: (Eq element, Hashable element) => Transform element element-distinct = distinctBy id--{-# INLINE builderChunks #-}-builderChunks :: Transform E.Builder ByteString-builderChunks =- produce (H.list . F.toChunks . E.toLazyByteString)--{-# INLINE ioTransform #-}-ioTransform :: IO (Transform a b) -> Transform a b-ioTransform io =- Transform $ \ fetch -> do- Transform transformIO <- io- transformIO fetch--{-|-Notice that you can control the emission of output of each step-by producing a list of outputs and then composing the transform with-the "list" transform.--}-{-# INLINE runState #-}-runState :: (a -> O.State s b) -> s -> Transform a (s, b)-runState stateFn initialState =- Transform $ \ (A.Fetch fetchIO) -> do- stateRef <- newIORef initialState- return $ A.Fetch $ \ nil just -> do- let- nilIO =- return nil- justIO input =- do- currentState <- readIORef stateRef- case O.runState (stateFn input) currentState of- (output, newState) -> do- writeIORef stateRef newState- return (just (newState, output))- in join (fetchIO nilIO justIO)--{-# INLINE evalState #-}-evalState :: (a -> O.State s b) -> s -> Transform a b-evalState stateFn initialState =- runState stateFn initialState >>> arr snd---{-# INLINE execState #-}-execState :: (a -> O.State s b) -> s -> Transform a s-execState stateFn initialState =- runState stateFn initialState >>> arr fst- --{-# INLINE list #-}-list :: Transform [a] a-list =- Transform $ \ (A.Fetch fetchListIO) -> do- bufferRef <- newIORef []- return $ A.Fetch $ \ nil just -> do- buffer <- readIORef bufferRef- case buffer of- head : tail -> do- writeIORef bufferRef tail- return (just head)- _ ->- let- fetchElementIO =- let- nilIO =- return nil- justIO input =- case input of- head : tail -> do- writeIORef bufferRef tail- return (just head)- _ -> do- writeIORef bufferRef []- return nil- in join (fetchListIO nilIO justIO)- in fetchElementIO--vector :: Transform (Vector a) a-vector =- Transform $ \ (A.Fetch fetchVectorIO) -> do- indexRef <- newIORef 0- vectorRef <- newIORef mempty- return $ A.Fetch $ \ nil just -> fix $ \ loop -> do- vector <- readIORef vectorRef- index <- readIORef indexRef- if index < P.length vector- then do- writeIORef indexRef (succ index)- return (just (P.unsafeIndex vector index))- else join $ fetchVectorIO (return nil) $ \ vector -> do- writeIORef vectorRef vector- writeIORef indexRef 0- loop+import qualified Potoki.Transform.State as R -{-|-Useful for debugging--}-traceWithCounter :: (Int -> String) -> Transform a a-traceWithCounter show =- ioTransform $ do- counter <- newIORef 0- return $ mapInIO $ \ x -> do- n <- atomicModifyIORef' counter (\ n -> (succ n, n))- putStrLn (show n)- return x
+ library/Potoki/Transform/Attoparsec.hs view
@@ -0,0 +1,84 @@+module Potoki.Transform.Attoparsec+where++import Potoki.Prelude hiding (take, takeWhile, filter, drop)+import Potoki.Core.Transform+import Potoki.Transform.Basic+import qualified Potoki.Fetch as A+import qualified Potoki.Core.Fetch as A+import qualified Data.Attoparsec.ByteString as K+import qualified Data.Attoparsec.Text as L+import qualified Data.Attoparsec.Types as M+++{-# INLINE mapWithParseResult #-}+mapWithParseResult :: forall input parsed. (Monoid input, Eq input) => (input -> M.IResult input parsed) -> Transform input (Either Text parsed)+mapWithParseResult inputToResult =+ Transform $ \ inputFetch ->+ do+ unconsumedRef <- newIORef mempty+ finishedRef <- newIORef False+ return (A.Fetch (fetchParsed inputFetch finishedRef unconsumedRef))+ where+ fetchParsed :: A.Fetch input -> IORef Bool -> IORef input -> forall x. x -> (Either Text parsed -> x) -> IO x+ fetchParsed (A.Fetch inputFetchIO) finishedRef unconsumedRef nil just =+ do+ finished <- readIORef finishedRef+ if finished+ then return nil+ else do+ unconsumed <- readIORef unconsumedRef+ if unconsumed == mempty+ then+ join $ inputFetchIO+ (return nil)+ (\input -> do+ if input == mempty+ then return nil+ else matchResult (inputToResult input))+ else do+ writeIORef unconsumedRef mempty+ matchResult (inputToResult unconsumed)+ where+ matchResult =+ \case+ M.Partial inputToResult ->+ consume inputToResult+ M.Done unconsumed parsed ->+ do+ writeIORef unconsumedRef unconsumed+ return (just (Right parsed))+ M.Fail unconsumed contexts message ->+ do+ writeIORef unconsumedRef unconsumed+ writeIORef finishedRef True+ return (just (Left resultMessage))+ where+ resultMessage =+ if null contexts+ then fromString message+ else fromString (showString (intercalate " > " contexts) (showString ": " message))+ consume inputToResult =+ join $ inputFetchIO+ (do+ writeIORef finishedRef True+ matchResult (inputToResult mempty))+ (\input -> do+ when (input == mempty) (writeIORef finishedRef True)+ matchResult (inputToResult input))++{-|+Lift an Attoparsec ByteString parser.+-}+{-# INLINE parseBytes #-}+parseBytes :: K.Parser parsed -> Transform ByteString (Either Text parsed)+parseBytes parser =+ mapWithParseResult (K.parse parser)++{-|+Lift an Attoparsec Text parser.+-}+{-# INLINE parseText #-}+parseText :: L.Parser parsed -> Transform Text (Either Text parsed)+parseText parser =+ mapWithParseResult (L.parse parser)
+ library/Potoki/Transform/Basic.hs view
@@ -0,0 +1,133 @@+module Potoki.Transform.Basic+where++import Potoki.Prelude hiding (take, takeWhile, filter, drop)+import Potoki.Core.Transform+import qualified Potoki.Fetch as A+import qualified Potoki.Core.Fetch as A+import qualified Data.HashSet as C+import qualified Data.Vector as P+++{-# INLINE mapFilter #-}+mapFilter :: (input -> Maybe output) -> Transform input output+mapFilter mapping =+ Transform (pure . A.mapFilter mapping)++{-# INLINE filter #-}+filter :: (input -> Bool) -> Transform input input+filter predicate =+ Transform (pure . A.filter predicate)++{-# INLINE just #-}+just :: Transform (Maybe input) input+just =+ Transform (pure . A.just)++{-# INLINE takeWhile #-}+takeWhile :: (input -> Bool) -> Transform input input+takeWhile predicate =+ Transform (pure . A.takeWhile predicate)++{-# INLINE drop #-}+drop :: Int -> Transform input input+drop amount =+ Transform $ \ (A.Fetch fetchIO) -> do+ countRef <- newIORef amount+ return $ A.Fetch $ \ nil just -> fix $ \ loop -> do+ count <- readIORef countRef+ if count > 0+ then do+ writeIORef countRef $! pred count+ loop+ else fetchIO nil just++{-# INLINE list #-}+list :: Transform [a] a+list =+ Transform $ \ (A.Fetch fetchListIO) -> do+ bufferRef <- newIORef []+ return $ A.Fetch $ \ nil just -> do+ buffer <- readIORef bufferRef+ case buffer of+ head : tail -> do+ writeIORef bufferRef tail+ return (just head)+ _ ->+ let+ fetchElementIO =+ let+ nilIO =+ return nil+ justIO input =+ case input of+ head : tail -> do+ writeIORef bufferRef tail+ return (just head)+ _ -> do+ writeIORef bufferRef []+ return nil+ in join (fetchListIO nilIO justIO)+ in fetchElementIO++{-# INLINABLE vector #-}+vector :: Transform (Vector a) a+vector =+ Transform $ \ (A.Fetch fetchVectorIO) -> do+ indexRef <- newIORef 0+ vectorRef <- newIORef mempty+ return $ A.Fetch $ \ nil just -> fix $ \ loop -> do+ vector <- readIORef vectorRef+ index <- readIORef indexRef+ if index < P.length vector+ then do+ writeIORef indexRef (succ index)+ return (just (P.unsafeIndex vector index))+ else join $ fetchVectorIO (return nil) $ \ vector -> do+ writeIORef vectorRef vector+ writeIORef indexRef 0+ loop++{-# INLINE distinctBy #-}+distinctBy :: (Eq comparable, Hashable comparable) => (element -> comparable) -> Transform element element+distinctBy f =+ Transform $ \ (A.Fetch fetch) -> do+ stateRef <- newIORef mempty+ return $ A.Fetch $ \ nil just -> fix $ \ loop -> join $ fetch (return nil) $ \ !input -> do+ let comparable = f input+ !set <- readIORef stateRef+ if C.member comparable set+ then loop+ else do+ writeIORef stateRef $! C.insert comparable set+ return (just input)++{-# INLINE distinct #-}+distinct :: (Eq element, Hashable element) => Transform element element+distinct = distinctBy id++{-# INLINE mapInIO #-}+mapInIO :: (a -> IO b) -> Transform a b+mapInIO io =+ Transform $ \ (A.Fetch fetch) ->+ return $ A.Fetch $ \ nil just ->+ join $ fetch (return nil) $ (fmap . fmap) just io++{-# INLINE ioTransform #-}+ioTransform :: IO (Transform a b) -> Transform a b+ioTransform io =+ Transform $ \ fetch -> do+ Transform transformIO <- io+ transformIO fetch++{-|+Useful for debugging+-}+traceWithCounter :: (Int -> String) -> Transform a a+traceWithCounter show =+ ioTransform $ do+ counter <- newIORef 0+ return $ mapInIO $ \ x -> do+ n <- atomicModifyIORef' counter (\ n -> (succ n, n))+ putStrLn (show n)+ return x
+ library/Potoki/Transform/ByteString.hs view
@@ -0,0 +1,61 @@+module Potoki.Transform.ByteString+where++import Potoki.Prelude hiding (filter)+import Potoki.Core.Transform+import Potoki.Transform.Basic+import Potoki.Transform.State+import qualified Potoki.Fetch as A+import qualified Potoki.Core.Fetch as A+import qualified Potoki.Core.Produce as H+import qualified Ptr.Poking as C+import qualified Ptr.ByteString as D+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as E+import qualified Data.ByteString.Lazy as F+import qualified Control.Monad.Trans.State.Strict as O+++{-# INLINE builderChunks #-}+builderChunks :: Transform E.Builder ByteString+builderChunks =+ produce (H.list . F.toChunks . E.toLazyByteString)++{-|+Convert freeform bytestring chunks into chunks,+which are strictly separated by newline no matter how long they may be.+-}+extractLines :: Transform ByteString ByteString+extractLines =+ lineList >>> filter (not . null) >>> list+ -- lineList >>> list+ where+ lineList =+ Transform $ \ (A.Fetch fetchIO) ->+ do+ stateRef <- newIORef Nothing+ return $ A.Fetch $ \ nil just -> join $ fetchIO+ (do+ state <- readIORef stateRef+ case state of+ Just poking -> do+ writeIORef stateRef Nothing+ return (just [D.poking poking])+ Nothing -> return nil)+ (\ chunk ->+ case B.split 10 chunk of+ firstInput : tail -> do+ state <- readIORef stateRef+ let+ newPoking =+ fold state <> C.bytes firstInput+ in case unsnoc tail of+ Just (init, last) ->+ do+ writeIORef stateRef (Just (C.bytes last))+ return (just (D.poking newPoking : init))+ Nothing ->+ do+ writeIORef stateRef (Just newPoking)+ return (just [])+ _ -> return (just []))
+ library/Potoki/Transform/FileIO.hs view
@@ -0,0 +1,30 @@+module Potoki.Transform.FileIO+where++import Potoki.Prelude hiding (take, takeWhile, filter, drop)+import Potoki.Core.Transform+import Potoki.Transform.Basic+import qualified Data.ByteString as J+import qualified Data.Text.IO as Q+import qualified System.Directory as I+++{-# INLINE deleteFile #-}+deleteFile :: Transform FilePath (Either IOException ())+deleteFile =+ mapInIO (try . I.removeFile)++{-# INLINE appendBytesToFile #-}+appendBytesToFile :: Transform (FilePath, ByteString) (Either IOException ())+appendBytesToFile =+ mapInIO $ \ (path, bytes) ->+ try $ + withFile path AppendMode $ \ handle -> + J.hPut handle bytes++{-# INLINABLE writeTextToFile #-}+writeTextToFile :: Transform (FilePath, Text) (Either IOException ())+writeTextToFile =+ mapInIO $ \ (path, text) ->+ try $ + Q.writeFile path text
+ library/Potoki/Transform/State.hs view
@@ -0,0 +1,43 @@+module Potoki.Transform.State+where++import Potoki.Prelude+import Potoki.Core.Transform+import qualified Potoki.Fetch as A+import qualified Potoki.Core.Fetch as A+import qualified Data.ByteString as B+import qualified Control.Monad.Trans.State.Strict as O+++{-|+Notice that you can control the emission of output of each step+by producing a list of outputs and then composing the transform with+the "list" transform.+-}+{-# INLINE runState #-}+runState :: (a -> O.State s b) -> s -> Transform a (s, b)+runState stateFn initialState =+ Transform $ \ (A.Fetch fetchIO) -> do+ stateRef <- newIORef initialState+ return $ A.Fetch $ \ nil just -> do+ let+ nilIO =+ return nil+ justIO input =+ do+ currentState <- readIORef stateRef+ case O.runState (stateFn input) currentState of+ (output, newState) -> do+ writeIORef stateRef newState+ return (just (newState, output))+ in join (fetchIO nilIO justIO)++{-# INLINE evalState #-}+evalState :: (a -> O.State s b) -> s -> Transform a b+evalState stateFn initialState =+ runState stateFn initialState >>> arr snd++{-# INLINE execState #-}+execState :: (a -> O.State s b) -> s -> Transform a s+execState stateFn initialState =+ runState stateFn initialState >>> arr fst
potoki.cabal view
@@ -1,7 +1,7 @@ name: potoki version:- 0.11.1+ 0.11.2 synopsis: Simple streaming in IO description:@@ -75,7 +75,12 @@ other-modules: Potoki.Fetch Potoki.Prelude+ Potoki.Transform.Attoparsec+ Potoki.Transform.Basic+ Potoki.Transform.ByteString Potoki.Transform.Concurrency+ Potoki.Transform.FileIO+ Potoki.Transform.State build-depends: attoparsec >=0.13 && <0.15, base >=4.7 && <5,@@ -86,17 +91,18 @@ hashable >=1 && <2, potoki-core >=1.5 && <1.6, profunctors >=5.2 && <6,+ ptr >=0.16.2 && <0.17, text >=1 && <2, transformers >=0.5 && <0.6, unagi-chan >=0.4 && <0.5, unordered-containers >=0.2 && <0.3, vector >=0.12 && <0.13 -test-suite tests+test-suite test type: exitcode-stdio-1.0 hs-source-dirs:- tests+ test main-is: Main.hs default-extensions:
+ test/Main.hs view
@@ -0,0 +1,138 @@+module Main where++import Prelude hiding (first, second)+import Control.Arrow+import Test.QuickCheck.Instances+import Test.Tasty+import Test.Tasty.Runners+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import qualified Potoki.IO as C+import qualified Potoki.Consume as D+import qualified Potoki.Transform as A+import qualified Potoki.Produce as E+import qualified Data.Attoparsec.ByteString.Char8 as B+import qualified Data.ByteString as F+import qualified Data.Vector as G+import qualified System.Random as H+++main =+ defaultMain $+ testGroup "All tests" $+ [+ testCase "vector to list" $ do+ result <- C.produceAndConsume (E.vector (G.fromList [1,2,3])) (D.list)+ assertEqual "" [1,2,3] result+ ,+ testCase "just" $ do+ result <- C.produceAndConsume (E.list [Just 1, Nothing, Just 2]) (D.transform A.just D.list)+ assertEqual "" [1,2] result+ ,+ testCase "transform,consume,take" $ do+ let+ transform = A.consume (D.transform (A.take 3) D.list)+ consume = D.transform transform D.list+ produceAndConsume list = C.produceAndConsume (E.list list) (consume)+ assertEqual "" [[1,2,3], [4,5,6], [7,8]] =<< produceAndConsume [1,2,3,4,5,6,7,8]+ assertEqual "" [[1,2,3], [4,5,6], [7,8,9]] =<< produceAndConsume [1,2,3,4,5,6,7,8,9]+ assertEqual "" [] =<< produceAndConsume ([] :: [Int])+ ,+ testCase "File reading" $ do+ let produce =+ E.transform (arr (either (const Nothing) Just) >>> A.just) $+ E.fileBytes "samples/1"+ result <- C.produceAndConsume produce (fmap F.length D.concat)+ assertEqual "" 17400 result+ ,+ transform+ ,+ parsing+ ]++transform :: TestTree+transform =+ testGroup "Transform" $+ [+ testCase "Order" $ do+ let+ list = [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3]+ transform = left (A.consume (D.transform (A.take 2) D.sum))+ result <- C.produceAndConsume (E.list list) (D.transform transform D.list)+ assertEqual "" [Left 3, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 7] result+ ,+ testCase "Interrupted order" $ do+ let+ list = [Left 1, Left 2, Right 'a']+ transform = left (A.consume (D.transform (A.take 3) D.sum))+ result <- C.produceAndConsume (E.list list) (D.transform transform D.list)+ assertEqual "" [Left 3, Right 'a'] result+ ,+ testCase "Distinct" $ do+ let+ list = [1,2,3,2,3,2,1,4,1] :: [Int]+ result <- C.produceAndConsume (E.list list) (D.transform A.distinct D.list)+ assertEqual "" [1,2,3,4] result+ ,+ testCase "Distinct By" $ do+ let+ list = [(1, ""),(2, ""),(3, ""),(2, ""),(3, ""),(2, ""),(1, ""),(4, ""),(1, "")] :: [(Int, String)]+ result <- C.produceAndConsume (E.list list) (D.transform (A.distinctBy fst) D.list)+ assertEqual "" [(1, ""),(2, ""),(3, ""),(4, "")] result+ ,+ testCase "Concurrently" $ do+ let+ list = [1..20000]+ produce = E.list list+ transform =+ A.concurrently 12 $+ arr (\ x -> H.randomRIO (0, 100) >>= threadDelay >> return x) >>>+ A.executeIO+ consume = D.transform transform D.list+ result <- C.produceAndConsume produce consume+ assertBool "Is dispersed" (list /= result)+ assertEqual "Contains no duplicates" 0 (length result - length (nub result))+ assertEqual "Equals the original once sorted" list (sort result)+ ,+ testProperty "Line" $ \ chunks ->+ let+ expected =+ mconcat chunks+ actual =+ unsafePerformIO (C.produceAndConsume produce consume)+ where+ produce =+ E.list chunks+ consume =+ rmap (mconcat . intersperse "\n") $+ D.transform A.extractLines D.list+ in expected === actual+ ]++parsing :: TestTree+parsing =+ testGroup "Parsing" $+ [+ testCase "Sample 1" $ do+ let parser = B.double <* B.char ','+ transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser+ produce = E.transform transform (E.fileBytes "samples/1")+ result <- C.produceAndConsume produce D.count+ assertEqual "" 4350 result+ ,+ testCase "Sample 1 greedy" $ do+ let parser = B.sepBy B.double (B.char ',')+ transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser+ produce = E.transform transform (E.fileBytes "samples/1")+ result <- C.produceAndConsume produce D.list+ assertEqual "" [Right 4350] (fmap (fmap length) result)+ ,+ testCase "Split chunk" $+ let+ produce = E.list ["1", "2", "3"]+ parser = B.anyChar+ transform = A.parseBytes parser >>> arr (either (const Nothing) Just) >>> A.just+ consume = D.transform transform D.count+ in do+ assertEqual "" 3 =<< C.produceAndConsume produce consume+ ]
− tests/Main.hs
@@ -1,124 +0,0 @@-module Main where--import Prelude hiding (first, second)-import Control.Arrow-import Test.QuickCheck.Instances-import Test.Tasty-import Test.Tasty.Runners-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck-import qualified Potoki.IO as C-import qualified Potoki.Consume as D-import qualified Potoki.Transform as A-import qualified Potoki.Produce as E-import qualified Data.Attoparsec.ByteString.Char8 as B-import qualified Data.ByteString as F-import qualified Data.Vector as G-import qualified System.Random as H---main =- defaultMain $- testGroup "All tests" $- [- testCase "vector to list" $ do- result <- C.produceAndConsume (E.vector (G.fromList [1,2,3])) (D.list)- assertEqual "" [1,2,3] result- ,- testCase "just" $ do- result <- C.produceAndConsume (E.list [Just 1, Nothing, Just 2]) (D.transform A.just D.list)- assertEqual "" [1,2] result- ,- testCase "transform,consume,take" $ do- let- transform = A.consume (D.transform (A.take 3) D.list)- consume = D.transform transform D.list- produceAndConsume list = C.produceAndConsume (E.list list) (consume)- assertEqual "" [[1,2,3], [4,5,6], [7,8]] =<< produceAndConsume [1,2,3,4,5,6,7,8]- assertEqual "" [[1,2,3], [4,5,6], [7,8,9]] =<< produceAndConsume [1,2,3,4,5,6,7,8,9]- assertEqual "" [] =<< produceAndConsume ([] :: [Int])- ,- testCase "File reading" $ do- let produce =- E.transform (arr (either (const Nothing) Just) >>> A.just) $- E.fileBytes "samples/1"- result <- C.produceAndConsume produce (fmap F.length D.concat)- assertEqual "" 17400 result- ,- transform- ,- parsing- ]--transform :: TestTree-transform =- testGroup "Transform" $- [- testCase "Order" $ do- let- list = [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3]- transform = left (A.consume (D.transform (A.take 2) D.sum))- result <- C.produceAndConsume (E.list list) (D.transform transform D.list)- assertEqual "" [Left 3, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 7] result- ,- testCase "Interrupted order" $ do- let- list = [Left 1, Left 2, Right 'a']- transform = left (A.consume (D.transform (A.take 3) D.sum))- result <- C.produceAndConsume (E.list list) (D.transform transform D.list)- assertEqual "" [Left 3, Right 'a'] result- ,- testCase "Distinct" $ do- let- list = [1,2,3,2,3,2,1,4,1] :: [Int]- result <- C.produceAndConsume (E.list list) (D.transform A.distinct D.list)- assertEqual "" [1,2,3,4] result- ,- testCase "Distinct By" $ do- let- list = [(1, ""),(2, ""),(3, ""),(2, ""),(3, ""),(2, ""),(1, ""),(4, ""),(1, "")] :: [(Int, String)]- result <- C.produceAndConsume (E.list list) (D.transform (A.distinctBy fst) D.list)- assertEqual "" [(1, ""),(2, ""),(3, ""),(4, "")] result- ,- testCase "Concurrently" $ do- let- list = [1..20000]- produce = E.list list- transform =- A.concurrently 12 $- arr (\ x -> H.randomRIO (0, 100) >>= threadDelay >> return x) >>>- A.executeIO- consume = D.transform transform D.list- result <- C.produceAndConsume produce consume- assertBool "Is dispersed" (list /= result)- assertEqual "Contains no duplicates" 0 (length result - length (nub result))- assertEqual "Equals the original once sorted" list (sort result)- ]--parsing :: TestTree-parsing =- testGroup "Parsing" $- [- testCase "Sample 1" $ do- let parser = B.double <* B.char ','- transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser- produce = E.transform transform (E.fileBytes "samples/1")- result <- C.produceAndConsume produce D.count- assertEqual "" 4350 result- ,- testCase "Sample 1 greedy" $ do- let parser = B.sepBy B.double (B.char ',')- transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser- produce = E.transform transform (E.fileBytes "samples/1")- result <- C.produceAndConsume produce D.list- assertEqual "" [Right 4350] (fmap (fmap length) result)- ,- testCase "Split chunk" $- let- produce = E.list ["1", "2", "3"]- parser = B.anyChar- transform = A.parseBytes parser >>> arr (either (const Nothing) Just) >>> A.just- consume = D.transform transform D.count- in do- assertEqual "" 3 =<< C.produceAndConsume produce consume- ]