packages feed

cassava-megaparsec 2.0.2 → 2.0.3

raw patch · 6 files changed

+191/−157 lines, 6 filesdep ~bytestringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

- Data.Csv.Parser.Megaparsec: instance Data.Data.Data Data.Csv.Parser.Megaparsec.ConversionError
- Data.Csv.Parser.Megaparsec: instance GHC.Classes.Eq Data.Csv.Parser.Megaparsec.ConversionError
- Data.Csv.Parser.Megaparsec: instance GHC.Classes.Ord Data.Csv.Parser.Megaparsec.ConversionError
- Data.Csv.Parser.Megaparsec: instance GHC.Read.Read Data.Csv.Parser.Megaparsec.ConversionError
- Data.Csv.Parser.Megaparsec: instance GHC.Show.Show Data.Csv.Parser.Megaparsec.ConversionError
- Data.Csv.Parser.Megaparsec: instance Text.Megaparsec.Error.ShowErrorComponent Data.Csv.Parser.Megaparsec.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: ConversionError :: String -> ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: csv :: FromRecord a => DecodeOptions -> Parser (Vector a)
+ Data.Csv.Parser.Megaparsec.Internals: csvWithHeader :: FromNamedRecord a => DecodeOptions -> Parser (Header, Vector a)
+ Data.Csv.Parser.Megaparsec.Internals: decodeWithC :: (DecodeOptions -> Parser a) -> DecodeOptions -> HasHeader -> FilePath -> ByteString -> Either (ParseErrorBundle ByteString ConversionError) a
+ Data.Csv.Parser.Megaparsec.Internals: escapedField :: Parser ByteString
+ Data.Csv.Parser.Megaparsec.Internals: field :: Word8 -> Parser Field
+ Data.Csv.Parser.Megaparsec.Internals: header :: Word8 -> Parser Header
+ Data.Csv.Parser.Megaparsec.Internals: instance Data.Data.Data Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: instance GHC.Classes.Eq Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: instance GHC.Classes.Ord Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: instance GHC.Read.Read Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: instance GHC.Show.Show Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: instance Text.Megaparsec.Error.ShowErrorComponent Data.Csv.Parser.Megaparsec.Internals.ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: name :: Word8 -> Parser Name
+ Data.Csv.Parser.Megaparsec.Internals: newtype ConversionError
+ Data.Csv.Parser.Megaparsec.Internals: record :: Word8 -> (Record -> Parser a) -> Parser a
+ Data.Csv.Parser.Megaparsec.Internals: toNamedRecord :: Header -> Record -> NamedRecord
+ Data.Csv.Parser.Megaparsec.Internals: type Parser = Parsec ConversionError ByteString
+ Data.Csv.Parser.Megaparsec.Internals: unescapedField :: Word8 -> Parser ByteString

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+## Cassava Megaparsec 2.0.3++* Exposing Internals+ ## Cassava Megaparsec 2.0.2  * Add suppport for `megaparsec-9.0.0`
Data/Csv/Parser/Megaparsec.hs view
@@ -1,6 +1,6 @@ -- | -- Module      :  Data.Csv.Parser.Megaparsec--- Copyright   :  © 2016–2018 Stack Builders+-- Copyright   :  © 2016–2021 Stack Builders -- License     :  MIT -- -- Maintainer  :  Mark Karpov <markkarpov92@gmail.org>@@ -19,58 +19,27 @@ -- The parser provides better error messages than the parser that comes with -- Cassava library, while being compatible with the rest of the library. -{-# LANGUAGE BangPatterns       #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE OverloadedStrings  #-}-{-# LANGUAGE RecordWildCards    #-}- module Data.Csv.Parser.Megaparsec   ( ConversionError (..)   , decode   , decodeWith   , decodeByName-  , decodeByNameWith )+  , decodeByNameWith) where -import Control.Monad-import Data.ByteString (ByteString) import Data.Csv hiding-  ( Parser-  , record-  , namedRecord-  , header-  , toNamedRecord-  , decode+  ( decode   , decodeWith   , decodeByName   , decodeByNameWith )-import Data.Data import Data.Vector (Vector)-import Data.Word (Word8) import Text.Megaparsec-import Text.Megaparsec.Byte-import qualified Data.ByteString      as B import qualified Data.ByteString.Lazy as BL-import qualified Data.Csv             as C-import qualified Data.HashMap.Strict  as H-import qualified Data.Vector          as V--------------------------------------------------------------------------------- Custom error component and other types---- | Custom error component for CSV parsing. It allows typed reporting of--- conversion errors.--newtype ConversionError = ConversionError String-  deriving (Eq, Data, Typeable, Ord, Read, Show)--instance ShowErrorComponent ConversionError where-  showErrorComponent (ConversionError msg) =-    "conversion error: " ++ msg---- | Parser type that uses “custom error component” 'ConversionError'.--type Parser = Parsec ConversionError BL.ByteString+import Data.Csv.Parser.Megaparsec.Internals+    ( ConversionError (..)+    , csv+    , csvWithHeader+    , decodeWithC)  ---------------------------------------------------------------------------- -- Top level interface@@ -135,116 +104,3 @@   -> Either (ParseErrorBundle BL.ByteString ConversionError) (Header, Vector a) decodeByNameWith opts = parse (csvWithHeader opts) {-# INLINE decodeByNameWith #-}---- | Decode CSV data using the provided parser, skipping a leading header if--- necessary.--decodeWithC-  :: (DecodeOptions -> Parser a)-     -- ^ Parsing function parametrized by 'DecodeOptions'-  -> DecodeOptions-     -- ^ Decoding options-  -> HasHeader-     -- ^ Whether to expect a header in the input-  -> FilePath-     -- ^ File name (only for displaying in parse error messages, use empty-     -- string if you have none)-  -> BL.ByteString-     -- ^ CSV data-  -> Either (ParseErrorBundle BL.ByteString ConversionError) a-decodeWithC p opts@DecodeOptions {..} hasHeader = parse parser-  where-    parser = case hasHeader of-      HasHeader -> header decDelimiter *> p opts-      NoHeader  -> p opts-{-# INLINE decodeWithC #-}--------------------------------------------------------------------------------- The parser---- | Parse a CSV file that does not include a header.--csv :: FromRecord a-  => DecodeOptions     -- ^ Decoding options-  -> Parser (Vector a) -- ^ The parser that parses collection of records-csv DecodeOptions {..} = do-  xs <- sepEndBy1 (record decDelimiter parseRecord) eol-  eof-  return $! V.fromList xs---- | Parse a CSV file that includes a header.--csvWithHeader :: FromNamedRecord a-  => DecodeOptions     -- ^ Decoding options-  -> Parser (Header, Vector a)-     -- ^ The parser that parser collection of named records-csvWithHeader DecodeOptions {..} = do-  !hdr <- header decDelimiter-  let f = parseNamedRecord . toNamedRecord hdr-  xs   <- sepEndBy1 (record decDelimiter f) eol-  eof-  return $ let !v = V.fromList xs in (hdr, v)---- | Convert a 'Record' to a 'NamedRecord' by attaching column names. The--- 'Header' and 'Record' must be of the same length.--toNamedRecord :: Header -> Record -> NamedRecord-toNamedRecord hdr v = H.fromList . V.toList $ V.zip hdr v-{-# INLINE toNamedRecord #-}---- | Parse a header, including the terminating line separator.--header :: Word8 -> Parser Header-header del = V.fromList <$!> p <* eol-  where-    p = sepBy1 (name del) (void $ char del) <?> "file header"-{-# INLINE header #-}---- | Parse a header name. Header names have the same format as regular--- 'field's.--name :: Word8 -> Parser Name-name del = field del <?> "name in header"-{-# INLINE name #-}---- | Parse a record, not including the terminating line separator. The--- terminating line separate is not included as the last record in a CSV--- file is allowed to not have a terminating line separator.--record-  :: Word8             -- ^ Field delimiter-  -> (Record -> C.Parser a)-     -- ^ How to “parse” record to get the data of interest-  -> Parser a-record del f = do-  notFollowedBy eof -- to prevent reading empty line at the end of file-  r <- V.fromList <$!> (sepBy1 (field del) (void $ char del) <?> "record")-  case C.runParser (f r) of-    Left msg -> customFailure (ConversionError msg)-    Right x  -> return x-{-# INLINE record #-}---- | Parse a field. The field may be in either the escaped or non-escaped--- format. The returned value is unescaped.--field :: Word8 -> Parser Field-field del = label "field" (escapedField <|> unescapedField del)-{-# INLINE field #-}---- | Parse an escaped field.--escapedField :: Parser ByteString-escapedField =-  B.pack <$!> between (char 34) (char 34) (many $ normalChar <|> escapedDq)-  where-    normalChar = anySingleBut 34 <?> "unescaped character"-    escapedDq  = label "escaped double-quote" (34 <$ string "\"\"")-{-# INLINE escapedField #-}---- | Parse an unescaped field.--unescapedField :: Word8 -> Parser ByteString-unescapedField del = BL.toStrict <$> takeWhileP (Just "unescaped character") f-  where-    f x = x /= del && x /= 34 && x /= 10 && x /= 13-{-# INLINE unescapedField #-}
+ Data/Csv/Parser/Megaparsec/Internals.hs view
@@ -0,0 +1,173 @@+-- |+-- Module      :  Data.Csv.Parser.Megaparsec.Internals+-- Copyright   :  © 2016–2021 Stack Builders+-- License     :  MIT+--++{-# LANGUAGE BangPatterns       #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE RecordWildCards    #-}++module Data.Csv.Parser.Megaparsec.Internals +    ( ConversionError (..)+    , Parser+    , csv+    , csvWithHeader+    , decodeWithC+    , toNamedRecord+    , header+    , name+    , record+    , field+    , escapedField+    , unescapedField)+where++import Control.Monad+import Data.ByteString (ByteString)+import Data.Csv hiding+  ( Parser+  , record+  , header+  , toNamedRecord )+import Data.Data+import Data.Vector (Vector)+import Data.Word (Word8)+import Text.Megaparsec+import Text.Megaparsec.Byte+import qualified Data.ByteString      as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Csv             as C+import qualified Data.HashMap.Strict  as H+import qualified Data.Vector          as V++----------------------------------------------------------------------------+-- Custom error component and other types++-- | Custom error component for CSV parsing. It allows typed reporting of+-- conversion errors.++newtype ConversionError = ConversionError String+  deriving (Eq, Data, Typeable, Ord, Read, Show)++instance ShowErrorComponent ConversionError where+  showErrorComponent (ConversionError msg) =+    "conversion error: " ++ msg++-- | Parser type that uses “custom error component” 'ConversionError'.++type Parser = Parsec ConversionError BL.ByteString++----------------------------------------------------------------------------+-- The parser++-- | Parse a CSV file that does not include a header.++csv :: FromRecord a+  => DecodeOptions     -- ^ Decoding options+  -> Parser (Vector a) -- ^ The parser that parses collection of records+csv DecodeOptions {..} = do+  xs <- sepEndBy1 (record decDelimiter parseRecord) eol+  eof+  return $! V.fromList xs++-- | Parse a CSV file that includes a header.++csvWithHeader :: FromNamedRecord a+  => DecodeOptions     -- ^ Decoding options+  -> Parser (Header, Vector a)+     -- ^ The parser that parser collection of named records+csvWithHeader DecodeOptions {..} = do+  !hdr <- header decDelimiter+  let f = parseNamedRecord . toNamedRecord hdr+  xs   <- sepEndBy1 (record decDelimiter f) eol+  eof+  return $ let !v = V.fromList xs in (hdr, v)++-- | Decode CSV data using the provided parser, skipping a leading header if+-- necessary.++decodeWithC+  :: (DecodeOptions -> Parser a)+     -- ^ Parsing function parametrized by 'DecodeOptions'+  -> DecodeOptions+     -- ^ Decoding options+  -> HasHeader+     -- ^ Whether to expect a header in the input+  -> FilePath+     -- ^ File name (only for displaying in parse error messages, use empty+     -- string if you have none)+  -> BL.ByteString+     -- ^ CSV data+  -> Either (ParseErrorBundle BL.ByteString ConversionError) a+decodeWithC p opts@DecodeOptions {..} hasHeader = parse parser+  where+    parser = case hasHeader of+      HasHeader -> header decDelimiter *> p opts+      NoHeader  -> p opts+{-# INLINE decodeWithC #-}++-- | Convert a 'Record' to a 'NamedRecord' by attaching column names. The+-- 'Header' and 'Record' must be of the same length.++toNamedRecord :: Header -> Record -> NamedRecord+toNamedRecord hdr v = H.fromList . V.toList $ V.zip hdr v+{-# INLINE toNamedRecord #-}++-- | Parse a header, including the terminating line separator.++header :: Word8 -> Parser Header+header del = V.fromList <$!> p <* eol+  where+    p = sepBy1 (name del) (void $ char del) <?> "file header"+{-# INLINE header #-}++-- | Parse a header name. Header names have the same format as regular+-- 'field's.++name :: Word8 -> Parser Name+name del = field del <?> "name in header"+{-# INLINE name #-}++-- | Parse a record, not including the terminating line separator. The+-- terminating line separate is not included as the last record in a CSV+-- file is allowed to not have a terminating line separator.++record+  :: Word8             -- ^ Field delimiter+  -> (Record -> C.Parser a)+     -- ^ How to “parse” record to get the data of interest+  -> Parser a+record del f = do+  notFollowedBy eof -- to prevent reading empty line at the end of file+  r <- V.fromList <$!> (sepBy1 (field del) (void $ char del) <?> "record")+  case C.runParser (f r) of+    Left msg -> customFailure (ConversionError msg)+    Right x  -> return x+{-# INLINE record #-}++-- | Parse a field. The field may be in either the escaped or non-escaped+-- format. The returned value is unescaped.++field :: Word8 -> Parser Field+field del = label "field" (escapedField <|> unescapedField del)+{-# INLINE field #-}++-- | Parse an escaped field.++escapedField :: Parser ByteString+escapedField =+  B.pack <$!> between (char 34) (char 34) (many $ normalChar <|> escapedDq)+  where+    normalChar = anySingleBut 34 <?> "unescaped character"+    escapedDq  = label "escaped double-quote" (34 <$ string "\"\"")+{-# INLINE escapedField #-}++-- | Parse an unescaped field.++unescapedField :: Word8 -> Parser ByteString+unescapedField del = BL.toStrict <$> takeWhileP (Just "unescaped character") f+  where+    f x = x /= del && x /= 34 && x /= 10 && x /= 13+{-# INLINE unescapedField #-}
LICENSE.md view
@@ -1,6 +1,6 @@ # MIT License -Copyright © 2016–2018 Stack Builders+Copyright © 2016–2021 Stack Builders  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to
README.md view
@@ -60,6 +60,6 @@  ## License -Copyright © 2016–2018 Stack Builders+Copyright © 2016–2021 Stack Builders  Distributed under MIT license.
cassava-megaparsec.cabal view
@@ -1,5 +1,5 @@ name:                 cassava-megaparsec-version:              2.0.2+version:              2.0.3 cabal-version:        1.18 tested-with:          GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3, GHC==8.6.1 license:              MIT@@ -26,12 +26,13 @@  library   build-depends:      base             >= 4.8   && < 5.0-                    , bytestring       >= 0.9   && < 0.11+                    , bytestring       >= 0.9   && < 0.12                     , cassava          >= 0.4.2 && < 0.6                     , megaparsec       >= 7.0   && < 10.0                     , unordered-containers >= 0.2.7 && < 0.3                     , vector           >= 0.11  && < 0.13   exposed-modules:    Data.Csv.Parser.Megaparsec+                    , Data.Csv.Parser.Megaparsec.Internals   if flag(dev)     ghc-options:      -Wall -Werror   else@@ -49,7 +50,7 @@   hs-source-dirs:     tests   type:               exitcode-stdio-1.0   build-depends:      base               >= 4.8  && < 5.0-                    , bytestring         >= 0.9  && < 0.11+                    , bytestring         >= 0.9  && < 0.12                     , cassava            >= 0.4.2 && < 0.6                     , cassava-megaparsec                     , hspec              >= 2.0  && < 3.0