streaming-attoparsec (empty) → 1.0.0
raw patch · 6 files changed
+250/−0 lines, 6 filesdep +attoparsecdep +basedep +bytestring
Dependencies added: attoparsec, base, bytestring, streaming, streaming-attoparsec, streaming-bytestring, tasty, tasty-hunit
Files
- CHANGELOG.md +4/−0
- Data/Attoparsec/ByteString/Streaming.hs +124/−0
- LICENSE +30/−0
- README.md +8/−0
- streaming-attoparsec.cabal +53/−0
- test/Test.hs +31/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+## 1.0.0++- Initial split from `streaming-utils`, meaning the dependency graph is simplified.+- Compatibility with the latest versions of `streaming` and `streaming-bytestring`.
+ Data/Attoparsec/ByteString/Streaming.hs view
@@ -0,0 +1,124 @@+{- | Here is a simple use of 'parsed' and standard @Streaming@ segmentation devices to+ parse a file in which groups of numbers are separated by blank lines. Such a problem+ of \'nesting streams\' is described in the @conduit@ context in+ <http://stackoverflow.com/questions/32957258/how-to-model-nested-streams-with-conduits/32961296 this StackOverflow question>.++> -- $ cat nums.txt+> -- 1+> -- 2+> -- 3+> --+> -- 4+> -- 5+> -- 6+> --+> -- 7+> -- 8++ We will sum the groups and stream the results to standard output:++> import Streaming+> import qualified Streaming.Prelude as S+> import qualified Data.ByteString.Streaming.Char8 as Q+> import qualified Data.Attoparsec.ByteString.Char8 as A+> import qualified Data.Attoparsec.ByteString.Streaming as AS+> import Data.Function ((&))+>+> main :: IO ()+> main = Q.getContents -- raw bytes+> & AS.parsed lineParser -- stream of parsed `Maybe Int`s; blank lines are `Nothing`+> & void -- drop any unparsed nonsense at the end+> & S.split Nothing -- split on blank lines+> & S.maps S.concat -- keep `Just x` values in the sub-streams (cp. catMaybes)+> & S.mapped S.sum -- sum each substream+> & S.print -- stream results to stdout+>+> lineParser = Just <$> A.scientific <* A.endOfLine <|> Nothing <$ A.endOfLine++> -- $ cat nums.txt | ./atto+> -- 6.0+> -- 15.0+> -- 15.0++-}++module Data.Attoparsec.ByteString.Streaming where++import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.Internal.Types as T+import qualified Data.ByteString as B+import Data.ByteString.Streaming+import Data.ByteString.Streaming.Internal+import Streaming hiding (concats, unfold)+import Streaming.Internal (Stream (..))++---++-- | Output from parsing errors.+type Errors = ([String], String)++{- | The result of a parse (@Either ([String], String) a@), with the unconsumed byte stream.++>>> :set -XOverloadedStrings -- the string literal below is a streaming bytestring+>>> (r,rest1) <- AS.parse (A.scientific <* A.many' A.space) "12.3 4.56 78.3"+>>> print r+Left 12.3+>>> (s,rest2) <- AS.parse (A.scientific <* A.many' A.space) rest1+>>> print s+Left 4.56+>>> (t,rest3) <- AS.parse (A.scientific <* A.many' A.space) rest2+>>> print t+Left 78.3+>>> Q.putStrLn rest3+ -- Nothing left, this prints an empty string.+-}+parse :: Monad m => A.Parser a -> ByteString m x -> m (Either Errors a, ByteString m x)+parse parser = begin+ where begin p0 = case p0 of+ Go m -> m >>= begin+ Empty r -> step id (A.parse parser mempty) (return r)+ Chunk bs p1 | B.null bs -> begin p1 -- attoparsec understands "" as eof+ | otherwise -> step (chunk bs >>) (A.parse parser bs) p1++ step diff res p0 = case res of+ T.Fail _ c m -> return (Left (c,m), diff p0)+ T.Done a b -> return (Right b, chunk a >> p0)+ T.Partial k -> do+ let clean p = case p of -- inspect for null chunks before+ Go m -> m >>= clean -- feeding attoparsec+ Empty r -> step diff (k mempty) (return r)+ Chunk bs p1 | B.null bs -> clean p1+ | otherwise -> step (diff . (chunk bs >>)) (k bs) p1+ clean p0+{-# INLINABLE parse #-}++{-| Apply a parser repeatedly to a stream of bytes, streaming the parsed values, but+ ending when the parser fails or the bytes run out.++>>> S.print . void $ AS.parsed (A.scientific <* A.many' A.space) "12.3 4.56 78.9"+12.3+4.56+78.9+-}+parsed+ :: Monad m+ => A.Parser a -- ^ Attoparsec parser+ -> ByteString m r -- ^ Raw input+ -> Stream (Of a) m (Either (Errors, ByteString m r) r)+parsed parser = begin+ where begin p0 = case p0 of -- inspect for null chunks before+ Go m -> lift m >>= begin -- feeding attoparsec+ Empty r -> Return (Right r)+ Chunk bs p1 | B.null bs -> begin p1+ | otherwise -> step (chunk bs >>) (A.parse parser bs) p1+ step diffP res p0 = case res of+ A.Fail _ c m -> Return (Left ((c,m), diffP p0))+ A.Done bs a | B.null bs -> Step (a :> begin p0)+ | otherwise -> Step (a :> begin (chunk bs >> p0))+ A.Partial k -> do+ x <- lift (nextChunk p0)+ case x of+ Left e -> step diffP (k mempty) (return e)+ Right (bs,p1) | B.null bs -> step diffP res p1+ | otherwise -> step (diffP . (chunk bs >>)) (k bs) p1+{-# INLINABLE parsed #-}
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Michael Thompson, 2014 Gabriel Gonzalez, 2014 Renzo Carbonara++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of michaelt nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,8 @@+# streaming-attoparsec++`Data.Attoparsec.ByteString.Streaming` replicates Renzo Carbonara's+[`Pipes.Attoparsec` module](https://hackage.haskell.org/package/pipes-attoparsec-0.5.1.2/docs/Pipes-Attoparsec.html).+It permits parsing an effectful bytestring with an attoparsec parser, and also+the conversion of an effectful bytestring into stream of parsed values.++See the Haddock documentation for usage examples.
+ streaming-attoparsec.cabal view
@@ -0,0 +1,53 @@+-- This file has been generated from package.yaml by hpack version 0.20.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 90500d245bb6e2f2273b7c717ef402a318fabc31c8196248da19d72c3739e64b++name: streaming-attoparsec+version: 1.0.0+synopsis: Attoparsec integration for the streaming ecosystem+description: Attoparsec integration for the streaming ecosystem.+category: Streaming+homepage: https://github.com/haskell-streaming/streaming-attoparsec+author: Michael Thompson+maintainer: Colin Woodbury, colingw@gmail.com+license: BSD3+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ CHANGELOG.md+ README.md++library+ hs-source-dirs:+ ./.+ ghc-options: -fwarn-unused-imports -fwarn-unused-binds -fwarn-name-shadowing -fwarn-unused-matches -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns+ build-depends:+ attoparsec >=0.13 && <0.14+ , base >=4.7 && <5+ , bytestring+ , streaming >=0.1.4.5 && <0.3+ , streaming-bytestring >=0.1.4.0 && <0.2+ exposed-modules:+ Data.Attoparsec.ByteString.Streaming+ default-language: Haskell2010++test-suite streaming-attoparsec-test+ type: exitcode-stdio-1.0+ main-is: Test.hs+ hs-source-dirs:+ test+ ghc-options: -fwarn-unused-imports -fwarn-unused-binds -fwarn-name-shadowing -fwarn-unused-matches -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns -threaded -with-rtsopts=-N+ build-depends:+ attoparsec >=0.13 && <0.14+ , base >=4.7 && <5+ , bytestring+ , streaming >=0.1.4.5 && <0.3+ , streaming-attoparsec+ , streaming-bytestring >=0.1.4.0 && <0.2+ , tasty >=0.11 && <1.1+ , tasty-hunit >=0.9 && <0.11+ default-language: Haskell2010
+ test/Test.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}++module Main ( main ) where++import qualified Data.Attoparsec.ByteString.Char8 as A+import qualified Data.Attoparsec.ByteString.Streaming as AS+import qualified Data.ByteString.Streaming as BS+import Streaming+import qualified Streaming.Prelude as S+import Test.Tasty+import Test.Tasty.HUnit++---++main :: IO ()+main = defaultMain suite++suite :: TestTree+suite = testGroup "Unit Tests"+ [ testCase "parse" $ do+ (r,rest1) <- AS.parse (A.scientific <* A.many' A.space) "12.3 4.56 78.3"+ (s,rest2) <- AS.parse (A.scientific <* A.many' A.space) rest1+ (t,rest3) <- AS.parse (A.scientific <* A.many' A.space) rest2+ r @?= Right 12.3+ s @?= Right 4.56+ t @?= Right 78.3+ BS.null_ rest3 >>= assertBool "Should be empty"+ , testCase "Simple parsing" $ do+ s <- S.sum_ . void . AS.parsed (A.scientific <* A.many' A.space) $ "12.3 4.56 78.9"+ s @?= (12.3 + 4.56 + 78.9)+ ]