attoparsec-conduit 0.4.0.1 → 0.5.0
raw patch · 3 files changed
+184/−64 lines, 3 filesdep +attoparsec-conduitdep +resourcetdep −QuickCheckdep −blaze-builderdep ~attoparsecdep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: attoparsec-conduit, resourcet
Dependencies removed: QuickCheck, blaze-builder
Dependency ranges changed: attoparsec, base, bytestring, conduit, text, transformers
API changes (from Hackage documentation)
+ Data.Conduit.Attoparsec: Position :: Int -> Int -> Position
+ Data.Conduit.Attoparsec: PositionRange :: Position -> Position -> PositionRange
+ Data.Conduit.Attoparsec: conduitParser :: (AttoparsecInput a, MonadThrow m) => Parser a b -> GLInfConduit a m (PositionRange, b)
+ Data.Conduit.Attoparsec: data Position
+ Data.Conduit.Attoparsec: data PositionRange
+ Data.Conduit.Attoparsec: errorPosition :: ParseError -> Position
+ Data.Conduit.Attoparsec: instance Eq Position
+ Data.Conduit.Attoparsec: instance Eq PositionRange
+ Data.Conduit.Attoparsec: instance Ord Position
+ Data.Conduit.Attoparsec: instance Ord PositionRange
+ Data.Conduit.Attoparsec: instance Show Position
+ Data.Conduit.Attoparsec: instance Show PositionRange
+ Data.Conduit.Attoparsec: posCol :: Position -> Int
+ Data.Conduit.Attoparsec: posLine :: Position -> Int
+ Data.Conduit.Attoparsec: posRangeEnd :: PositionRange -> Position
+ Data.Conduit.Attoparsec: posRangeStart :: PositionRange -> Position
- Data.Conduit.Attoparsec: ParseError :: [String] -> String -> ParseError
+ Data.Conduit.Attoparsec: ParseError :: [String] -> String -> Position -> ParseError
- Data.Conduit.Attoparsec: sinkParser :: (AttoparsecInput a, MonadThrow m) => Parser a b -> Sink a m b
+ Data.Conduit.Attoparsec: sinkParser :: (AttoparsecInput a, MonadThrow m) => Parser a b -> GLSink a m b
Files
- Data/Conduit/Attoparsec.hs +118/−30
- attoparsec-conduit.cabal +8/−8
- test/main.hs +58/−26
Data/Conduit/Attoparsec.hs view
@@ -5,35 +5,62 @@ -- Copyright: 2011 Michael Snoyman, 2010 John Millikin -- License: MIT ----- Turn an Attoparsec parser into a 'C.Sink'.+-- Consume attoparsec parsers via conduit. -- -- This code was taken from attoparsec-enumerator and adapted for conduits. module Data.Conduit.Attoparsec- ( ParseError (..)+ ( -- * Sink+ sinkParser+ -- * Conduit+ , conduitParser+ -- * Types+ , ParseError (..)+ , Position (..)+ , PositionRange (..)+ -- * Classes , AttoparsecInput- , sinkParser ) where +import Prelude hiding (lines) import Control.Exception (Exception) import Data.Typeable (Typeable) import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8 import qualified Data.Text as T-import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Class (lift)+import Control.Monad (unless) import qualified Data.Attoparsec.ByteString import qualified Data.Attoparsec.Text import qualified Data.Attoparsec.Types as A-import qualified Data.Conduit as C+import Data.Conduit hiding (Pipe, Sink, Conduit, Source) -- | The context and message from a 'A.Fail' value. data ParseError = ParseError { errorContexts :: [String]- , errorMessage :: String+ , errorMessage :: String+ , errorPosition :: Position } | DivergentParser deriving (Show, Typeable) instance Exception ParseError +data Position = Position+ { posLine :: Int+ , posCol :: Int+ }+ deriving (Eq, Ord)+instance Show Position where+ show (Position l c) = show l ++ ':' : show c++data PositionRange = PositionRange+ { posRangeStart :: Position+ , posRangeEnd :: Position+ }+ deriving (Eq, Ord)+instance Show PositionRange where+ show (PositionRange s e) = show s ++ '-' : show e+ -- | A class of types which may be consumed by an Attoparsec parser. class AttoparsecInput a where parseA :: A.Parser a b -> a -> A.IResult a b@@ -41,6 +68,9 @@ empty :: a isNull :: a -> Bool notEmpty :: [a] -> [a]+ getLinesCols :: a -> (Int, Int)+ take' :: Int -> a -> a+ length' :: a -> Int instance AttoparsecInput B.ByteString where parseA = Data.Attoparsec.ByteString.parse@@ -48,6 +78,16 @@ empty = B.empty isNull = B.null notEmpty = filter (not . B.null)+ getLinesCols b =+ (lines, cols)+ where+ lines = B.count 10 b+ cols =+ case B8.lines b of+ [] -> 0+ ls -> B.length $ last ls+ take' = B.take+ length' = B.length instance AttoparsecInput T.Text where parseA = Data.Attoparsec.Text.parse@@ -55,35 +95,83 @@ empty = T.empty isNull = T.null notEmpty = filter (not . T.null)+ getLinesCols t =+ (lines, cols)+ where+ lines = T.count (T.pack "\n") t+ cols =+ case T.lines t of+ [] -> 0+ ls -> T.length $ last ls+ take' = T.take+ length' = T.length --- | Convert an Attoparsec 'A.Parser' into a 'C.Sink'. The parser will+-- | Convert an Attoparsec 'A.Parser' into a 'Sink'. The parser will -- be streamed bytes until it returns 'A.Done' or 'A.Fail'. ----- If parsing fails, a 'ParseError' will be thrown with 'C.monadThrow'.-sinkParser :: (AttoparsecInput a, C.MonadThrow m) => A.Parser a b -> C.Sink a m b-sinkParser =- sink . parseA- where- sink parser = C.NeedInput (push parser) (close parser)-- push parser c | isNull c = sink parser- push parser c = go (parser c) sink+-- If parsing fails, a 'ParseError' will be thrown with 'monadThrow'.+--+-- Since 0.5.0+sinkParser :: (AttoparsecInput a, MonadThrow m) => A.Parser a b -> GLSink a m b+sinkParser = fmap snd . sinkParserPos (Position 1 1) - close parser = go- (feedA (parser empty) empty)- (const $ C.PipeM exc $ lift exc)+-- | Consume a stream of parsed tokens, returning both the token and the+-- position it appears at.+--+-- Since 0.5.0+conduitParser :: (AttoparsecInput a, MonadThrow m) => A.Parser a b -> GLInfConduit a m (PositionRange, b)+conduitParser parser =+ conduit $ Position 1 0+ where+ conduit pos =+ awaitE >>= either return go where- exc = C.monadThrow DivergentParser+ go x = do+ leftover x+ (pos', res) <- sinkParserPos pos parser+ yield (PositionRange pos pos', res)+ conduit pos' - go (A.Done leftover x) _ =- C.Done lo x- where- lo- | isNull leftover = Nothing- | otherwise = Just leftover- go (A.Fail _ contexts msg) _ =- C.PipeM exc $ lift exc+sinkParserPos :: (AttoparsecInput a, MonadThrow m) => Position -> A.Parser a b -> GLSink a m (Position, b)+sinkParserPos pos0 =+ sink empty pos0 . parseA+ where+ sink prev pos parser = do+ await >>= maybe close push where- exc = C.monadThrow $ ParseError contexts msg - go (A.Partial parser') onPartial = onPartial parser'+ push c+ | isNull c = sink prev pos parser+ | otherwise = go False c $ parser c++ close = go True prev (feedA (parser empty) empty)++ go end c (A.Done lo x) = do+ let pos'+ | end = pos+ | otherwise = addLinesCols prev pos+ y = take' (length' c - length' lo) c+ pos'' = addLinesCols y pos'+ unless (isNull lo) $ leftover lo+ return (pos'', x)+ go end c (A.Fail rest contexts msg) =+ let x = take' (length' c - length' rest) c+ pos'+ | end = pos+ | otherwise = addLinesCols prev pos+ pos'' = addLinesCols x pos'+ in lift $ monadThrow $ ParseError contexts msg pos''+ go end c (A.Partial parser')+ | end = lift $ monadThrow DivergentParser+ | otherwise =+ sink c pos' parser'+ where+ pos' = addLinesCols prev pos++ addLinesCols :: AttoparsecInput a => a -> Position -> Position+ addLinesCols x (Position lines cols) =+ lines' `seq` cols' `seq` Position lines' cols'+ where+ (dlines, dcols) = getLinesCols x+ lines' = lines + dlines+ cols' = (if dlines > 0 then 1 else cols) + dcols
attoparsec-conduit.cabal view
@@ -1,7 +1,7 @@ Name: attoparsec-conduit-Version: 0.4.0.1-Synopsis: Turn attoparsec parsers into sinks.-Description: Turn attoparsec parsers into sinks.+Version: 0.5.0+Synopsis: Consume attoparsec parsers via conduit.+Description: Consume attoparsec parsers via conduit. License: BSD3 License-file: LICENSE Author: Michael Snoyman@@ -19,7 +19,7 @@ , bytestring >= 0.9 , attoparsec >= 0.10 , text >= 0.11- , conduit >= 0.4 && < 0.5+ , conduit >= 0.5 && < 0.6 ghc-options: -Wall test-suite test@@ -31,11 +31,11 @@ , base , hspec , HUnit- , QuickCheck- , bytestring- , blaze-builder- , transformers , text+ , resourcet+ , attoparsec+ , attoparsec-conduit+ , conduit ghc-options: -Wall source-repository head
test/main.hs view
@@ -1,35 +1,67 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} import Test.Hspec.Monadic-{- import Test.Hspec.HUnit ()-import Test.Hspec.QuickCheck (prop) import Test.HUnit+import Control.Exception (fromException) -import qualified Data.Conduit as C+import Data.Conduit+import Data.Conduit.Attoparsec import qualified Data.Conduit.List as CL-import qualified Data.Conduit.Lazy as CLazy-import qualified Data.Conduit.Text as CT-import Data.Conduit.Blaze (builderToByteString)-import Data.Conduit (runResourceT)-import Control.Monad.ST (runST)-import Data.Monoid-import qualified Data.ByteString as S-import qualified Data.IORef as I-import Blaze.ByteString.Builder (fromByteString, toLazyByteString, insertLazyByteString)-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Lazy.Char8 ()-import Data.Maybe (catMaybes)-import Control.Monad.Trans.Writer (Writer)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Encoding as TLE-import Control.Monad.Trans.Resource (runExceptionT_, withIO, resourceForkIO)-import Control.Concurrent (threadDelay, killThread)-import Control.Monad.IO.Class (liftIO)-import Control.Applicative (pure, (<$>), (<*>))--}+import qualified Data.Attoparsec.Text+import qualified Data.Attoparsec.ByteString.Char8+import Control.Applicative ((<|>))+import Control.Monad.Trans.Resource main :: IO ()-main = hspecX $ do- return ()+main = hspec $ do+ describe "error position" $ do+ it "works for text" $ do+ let input = ["aaa\na", "aaa\n\n", "aaa", "aab\n\naaaa"]+ badLine = 4+ badCol = 6+ parser = Data.Attoparsec.Text.endOfInput <|> (Data.Attoparsec.Text.notChar 'b' >> parser)+ sink = sinkParser parser+ ea <- runExceptionT $ CL.sourceList input $$ sink+ case ea of+ Left e ->+ case fromException e of+ Just pe -> do+ errorPosition pe @?= Position badLine badCol+ it "works for bytestring" $ do+ let input = ["aaa\na", "aaa\n\n", "aaa", "aab\n\naaaa"]+ badLine = 4+ badCol = 6+ parser = Data.Attoparsec.ByteString.Char8.endOfInput <|> (Data.Attoparsec.ByteString.Char8.notChar 'b' >> parser)+ sink = sinkParser parser+ ea <- runExceptionT $ CL.sourceList input $$ sink+ case ea of+ Left e ->+ case fromException e of+ Just pe -> do+ errorPosition pe @?= Position badLine badCol+ it "works in last chunk" $ do+ let input = ["aaa\na", "aaa\n\n", "aaa", "aab\n\naaaa"]+ badLine = 6+ badCol = 5+ parser = Data.Attoparsec.Text.char 'c' <|> (Data.Attoparsec.Text.anyChar >> parser)+ sink = sinkParser parser+ ea <- runExceptionT $ CL.sourceList input $$ sink+ case ea of+ Left e ->+ case fromException e of+ Just pe -> do+ errorPosition pe @?= Position badLine badCol+ it "works in last chunk" $ do+ let input = ["aaa\na", "aaa\n\n", "aaa", "aa\n\naaaab"]+ badLine = 6+ badCol = 6+ parser = Data.Attoparsec.Text.string "bc" <|> (Data.Attoparsec.Text.anyChar >> parser)+ sink = sinkParser parser+ ea <- runExceptionT $ CL.sourceList input $$ sink+ case ea of+ Left e ->+ case fromException e of+ Just pe -> do+ errorPosition pe @?= Position badLine badCol