packages feed

cassava 0.5.0.0 → 0.5.1.0

raw patch · 9 files changed

+552/−114 lines, 9 filesdep +natsdep +quickcheck-instancesdep +scientificdep ~attoparsecdep ~containersPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: nats, quickcheck-instances, scientific

Dependency ranges changed: attoparsec, containers

API changes (from Hackage documentation)

+ Data.Csv: class GFromNamedRecord f
+ Data.Csv: class GFromRecord f
+ Data.Csv: class GToNamedRecordHeader a
+ Data.Csv: class GToRecord a f
+ Data.Csv: data Options
+ Data.Csv: defaultOptions :: Options
+ Data.Csv: fieldLabelModifier :: Options -> String -> String
+ Data.Csv: genericHeaderOrder :: (Generic a, GToNamedRecordHeader (Rep a)) => Options -> a -> Header
+ Data.Csv: genericParseNamedRecord :: (Generic a, GFromNamedRecord (Rep a)) => Options -> NamedRecord -> Parser a
+ Data.Csv: genericParseRecord :: (Generic a, GFromRecord (Rep a)) => Options -> Record -> Parser a
+ Data.Csv: genericToNamedRecord :: (Generic a, GToRecord (Rep a) (ByteString, ByteString)) => Options -> a -> NamedRecord
+ Data.Csv: genericToRecord :: (Generic a, GToRecord (Rep a) Field) => Options -> a -> Record
- Data.Csv: class DefaultOrdered a where headerOrder = fromList . gtoNamedRecordHeader . from
+ Data.Csv: class DefaultOrdered a
- Data.Csv: class FromNamedRecord a where parseNamedRecord r = to <$> gparseNamedRecord r
+ Data.Csv: class FromNamedRecord a
- Data.Csv: class FromRecord a where parseRecord r = to <$> gparseRecord r
+ Data.Csv: class FromRecord a
- Data.Csv: class ToNamedRecord a where toNamedRecord = namedRecord . gtoRecord . from
+ Data.Csv: class ToNamedRecord a
- Data.Csv: class ToRecord a where toRecord = fromList . gtoRecord . from
+ Data.Csv: class ToRecord a
- Data.Csv: newtype Only a :: * -> *
+ Data.Csv: newtype Only a

Files

CHANGES.md view
@@ -1,3 +1,13 @@+## Version 0.5.1.0++ * Add `FromField`/`ToField` instance for `Natural` (#141,#142)+ * Add `FromField`/`ToField` instances for `Scientific` (#143,#144)+ * Add support for modifying Generics-based instances (adding+   `Options`, `defaultOptions`, `fieldLabelModifier`,+   `genericParseRecord`, `genericToRecord`, `genericToNamedRecord`,+   `genericHeaderOrder`) (#139,#140)+ * Documentation improvements+ ## Version 0.5.0.0  ### Semantic changes
Data/Csv.hs view
@@ -1,6 +1,7 @@--- | This module implements encoding and decoding of CSV data. The--- implementation is RFC 4180 compliant, with the following--- extensions:+-- | This module implements encoding and decoding+-- of [comma-separated values (CSV)](https://en.wikipedia.org/wiki/Comma-separated_values)+-- data. The implementation is [RFC 4180](https://tools.ietf.org/html/rfc4180)+-- compliant, with the following extensions: -- --  * Empty lines are ignored. --@@ -93,6 +94,26 @@     -- $fieldconversion     , FromField(..)     , ToField(..)++    -- ** 'Generic' record conversion+    -- $genericconversion+    , genericParseRecord+    , genericToRecord+    , genericParseNamedRecord+    , genericToNamedRecord+    , genericHeaderOrder++    -- *** 'Generic' type conversion options+    , Options+    , defaultOptions+    , fieldLabelModifier++    -- *** 'Generic' type conversion class name+    -- $genericconversionclass+    , GFromRecord+    , GToRecord+    , GFromNamedRecord+    , GToNamedRecordHeader     ) where  import Prelude hiding (lookup)@@ -105,19 +126,23 @@ -- -- Encoding standard Haskell types: ----- > >>> encode [("John" :: Text, 27), ("Jane", 28)]--- > "John,27\r\nJane,28\r\n"+-- >>> :set -XOverloadedStrings+-- >>> import Data.Text (Text)+-- >>> encode [("John" :: Text, 27 :: Int), ("Jane", 28)]+-- "John,27\r\nJane,28\r\n" ----- Since string literals are overloaded we have to supply a type+-- Since we enabled the [-XOverloadedStrings extension](https://downloads.haskell.org/~ghc/8.2.1/docs/html/users_guide/glasgow_exts.html#overloaded-string-literals),+-- string literals are polymorphic and we have to supply a type -- signature as the compiler couldn't deduce which string type (i.e.--- 'String' or 'Text') we want to use. In most cases type inference--- will infer the type from the context and you can omit type--- signatures.+-- 'String', 'Data.Text.Short.ShortText', or 'Data.Text.Text') we want to use. In most cases+-- type inference will infer the type from the context and you can+-- omit type signatures. -- -- Decoding standard Haskell types: ----- > >>> decode NoHeader "John,27\r\nJane,28\r\n" :: Either String (Vector (Text, Int))--- > Right [("John",27),("Jane",28)]+-- >>> import Data.Vector (Vector)+-- >>> decode NoHeader "John,27\r\nJane,28\r\n" :: Either String (Vector (Text, Int))+-- Right [("John",27),("Jane",28)] -- -- We pass 'NoHeader' as the first argument to indicate that the CSV -- input data isn't preceded by a header.@@ -137,50 +162,59 @@  -- $example-indexed-instance ----- Derived:+-- "GHC.Generics"-derived: -- -- > {-# LANGUAGE DeriveGeneric #-} -- >+-- > import Data.Text    (Text)+-- > import GHC.Generics (Generic)+-- > -- > data Person = Person { name :: !Text , salary :: !Int }--- >     deriving Generic+-- >     deriving (Generic, Show) -- > -- > instance FromRecord Person -- > instance ToRecord Person -- -- Manually defined: --+-- > import Control.Monad (mzero)+-- > -- > data Person = Person { name :: !Text , salary :: !Int }+-- >     deriving (Show) -- > -- > instance FromRecord Person where -- >     parseRecord v -- >         | length v == 2 = Person <$> v .! 0 <*> v .! 1 -- >         | otherwise     = mzero -- > instance ToRecord Person where--- >     toRecord (Person name age) = record [--- >         toField name, toField age]+-- >     toRecord (Person name' age') = record [+-- >         toField name', toField age'] -- -- We can now use e.g. 'encode' and 'decode' to encode and decode our -- data type. -- -- Encoding: ----- > >>> encode [Person ("John" :: Text) 27]--- > "John,27\r\n"+-- >>> encode [Person ("John" :: Text) 27]+-- "John,27\r\n" -- -- Decoding: ----- > >>> decode NoHeader "John,27\r\n" :: Either String (Vector Person)--- > Right [Person {name = "John", salary = 27}]+-- >>> decode NoHeader "John,27\r\n" :: Either String (Vector Person)+-- Right [Person {name = "John", salary = 27}] --  -- $example-named-instance ----- Derived:+-- "GHC.Generics"-derived: -- -- > {-# LANGUAGE DeriveGeneric #-} -- >+-- > import Data.Text    (Text)+-- > import GHC.Generics (Generic)+-- > -- > data Person = Person { name :: !Text , salary :: !Int }--- >     deriving Generic+-- >     deriving (Generic, Show) -- > -- > instance FromNamedRecord Person -- > instance ToNamedRecord Person@@ -189,6 +223,7 @@ -- Manually defined: -- -- > data Person = Person { name :: !Text , salary :: !Int }+-- >     deriving (Show) -- > -- > instance FromNamedRecord Person where -- >     parseNamedRecord m = Person <$> m .: "name" <*> m .: "salary"@@ -204,13 +239,13 @@ -- -- Encoding: ----- > >>> encodeDefaultOrderedByName [Person ("John" :: Text) 27]--- > "name,salary\r\nJohn,27\r\n"+-- >>> encodeDefaultOrderedByName [Person ("John" :: Text) 27]+-- "name,salary\r\nJohn,27\r\n" -- -- Decoding: ----- > >>> decodeByName "name,salary\r\nJohn,27\r\n" :: Either String (Header, Vector Person)--- > Right (["name","salary"],[Person {name = "John", salary = 27}])+-- >>> decodeByName "name,salary\r\nJohn,27\r\n" :: Either String (Header, Vector Person)+-- Right (["name","salary"],[Person {name = "John", salary = 27}]) --  -- $generic-processing@@ -221,8 +256,9 @@ -- parse a CSV file to a generic representation, just convert each -- record to a @'Vector' 'ByteString'@ value, like so: ----- > >>> decode NoHeader "John,27\r\nJane,28\r\n" :: Either String (Vector (Vector ByteString))--- > Right [["John","27"],["Jane","28"]]+-- >>> import Data.ByteString (ByteString)+-- >>> decode NoHeader "John,27\r\nJane,28\r\n" :: Either String (Vector (Vector ByteString))+-- Right [["John","27"],["Jane","28"]] -- -- As the example output above shows, all the fields are returned as -- uninterpreted 'ByteString' values.@@ -325,3 +361,46 @@ -- 'Field's and values you care about (e.g. 'Int's). Most of the time -- you don't need to write your own instances as the standard ones -- cover most use cases.++-- $genericconversion+--+-- There may be times that you do not want to manually write out class+-- instances for record conversion, but you can't rely upon the+-- default instances (e.g. you can't create field names that match the+-- actual column names in expected data).+--+-- For example, consider you have a type @MyType@ where you have+-- prefixed certain columns with an underscore, but in the actual data+-- they're not.  You can then write:+--+-- > myOptions :: Options+-- > myOptions = defaultOptions { fieldLabelmodifier = rmUnderscore }+-- >   where+-- >     rmUnderscore ('_':str) = str+-- >     rmUnderscore str       = str+-- >+-- > instance ToNamedRecord MyType where+-- >   toNamedRecord = genericToNamedRecord myOptions+-- >+-- > instance FromNamedRecord MyType where+-- >   parseNamedRecord = genericParseNamedRecord myOptions+-- >+-- > instance DefaultOrdered MyType where+-- >   headerOrder = genericHeaderOrder myOptions++-- $genericconversionclass+--+-- __NOTE__: Only the class /names/ are exposed in order to make it possible to write type signatures referring to these classes++-- $setup+-- >>> :set -XOverloadedStrings -XDeriveGeneric+-- >>> import Data.Text (Text)+-- >>> import Data.Vector (Vector)+-- >>> import GHC.Generics (Generic)+-- >>>+-- >>> data Person = Person { name :: !Text, salary :: !Int } deriving (Generic, Show)+-- >>> instance FromRecord Person+-- >>> instance ToRecord Person+-- >>> instance FromNamedRecord Person+-- >>> instance ToNamedRecord Person+-- >>> instance DefaultOrdered Person
Data/Csv/Builder.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE ScopedTypeVariables #-}  -- | Low-level bytestring builders. Most users want to use the more--- type-safe 'Data.Csv.Incremental' module instead.+-- type-safe "Data.Csv.Incremental" module instead. module Data.Csv.Builder     (     -- * Encoding single records and headers
Data/Csv/Conversion.hs view
@@ -38,6 +38,24 @@     , ToRecord(..)     , ToField(..) +    -- ** Generic type conversion+    , genericParseRecord+    , genericToRecord+    , genericParseNamedRecord+    , genericToNamedRecord+    , genericHeaderOrder++    -- *** Generic type conversion options+    , Options+    , defaultOptions+    , fieldLabelModifier++    -- *** Generic type conversion class names+    , GFromRecord+    , GToRecord+    , GFromNamedRecord+    , GToNamedRecordHeader+     -- * Parser     , Parser     , runParser@@ -66,11 +84,13 @@ #if MIN_VERSION_bytestring(0,10,4) import qualified Data.ByteString.Short as SBS #endif+import Data.List (intercalate) import Data.Hashable (Hashable) import qualified Data.HashMap.Lazy as HM import Data.Int (Int8, Int16, Int32, Int64) import qualified Data.IntMap as IM import qualified Data.Map as M+import Data.Scientific (Scientific) import Data.Semigroup (Semigroup, (<>)) import qualified Data.Text as T import qualified Data.Text.Encoding as T@@ -86,6 +106,7 @@ import Data.Word (Word8, Word16, Word32, Word64) import GHC.Float (double2Float) import GHC.Generics+import Numeric.Natural import Prelude hiding (lookup, takeWhile)  import Data.Csv.Conversion.Internal@@ -119,6 +140,41 @@ ------------------------------------------------------------------------ -- Index-based conversion +-- | Options to customise how to generically encode\/decode your+--   datatype to\/from CSV.+--+--   @since 0.5.1.0+newtype Options = Options+  { fieldLabelModifier :: String -> String+    -- ^ How to convert Haskell field labels to CSV fields.+    --+    --   @since 0.5.1.0+  }++instance Show Options where+  show (Options fld) =+    "Options {"+      ++ intercalate ","+         [ "fieldLabelModifier =~ " ++ show sampleField ++ " -> " ++ show (fld sampleField)+         ]+      ++ "}"+    where+      sampleField = "_column_A"++-- | Default conversion options.+--+--   @+--   Options+--   { 'fieldLabelModifier' = id+--   }+--   @+--+--   @since 0.5.1.0+defaultOptions :: Options+defaultOptions = Options+  { fieldLabelModifier = id+  }+ -- | A type that can be converted from a single CSV record, with the -- possibility of failure. --@@ -145,8 +201,16 @@     parseRecord :: Record -> Parser a      default parseRecord :: (Generic a, GFromRecord (Rep a)) => Record -> Parser a-    parseRecord r = to <$> gparseRecord r+    parseRecord = genericParseRecord defaultOptions +-- | A configurable CSV record parser.  This function applied to+--   'defaultOptions' is used as the default for 'parseRecord' when the+--   type is an instance of 'Generic'.+--+--   @since 0.5.1.0+genericParseRecord :: (Generic a, GFromRecord (Rep a)) => Options -> Record -> Parser a+genericParseRecord opts r = to <$> gparseRecord opts r+ -- | A type that can be converted to a single CSV record. -- -- An example type and instance:@@ -166,8 +230,16 @@     toRecord :: a -> Record      default toRecord :: (Generic a, GToRecord (Rep a) Field) => a -> Record-    toRecord = V.fromList . gtoRecord . from+    toRecord = genericToRecord defaultOptions +-- | A configurable CSV record creator.  This function applied to+--   'defaultOptions' is used as the default for 'toRecord' when the+--   type is an instance of 'Generic'.+--+--   @since 0.5.1.0+genericToRecord :: (Generic a, GToRecord (Rep a) Field) => Options -> a -> Record+genericToRecord opts = V.fromList . gtoRecord opts . from+ instance FromField a => FromRecord (Only a) where     parseRecord v         | n == 1    = Only <$> unsafeIndex v 0@@ -563,8 +635,16 @@     parseNamedRecord :: NamedRecord -> Parser a      default parseNamedRecord :: (Generic a, GFromNamedRecord (Rep a)) => NamedRecord -> Parser a-    parseNamedRecord r = to <$> gparseNamedRecord r+    parseNamedRecord = genericParseNamedRecord defaultOptions +-- | A configurable CSV named record parser.  This function applied to+--   'defaultOptions' is used as the default for 'parseNamedRecord'+--   when the type is an instance of 'Generic'.+--+--   @since 0.5.1.0+genericParseNamedRecord :: (Generic a, GFromNamedRecord (Rep a)) => Options -> NamedRecord -> Parser a+genericParseNamedRecord opts r = to <$> gparseNamedRecord opts r+ -- | A type that can be converted to a single CSV record. -- -- An example type and instance:@@ -581,8 +661,17 @@     default toNamedRecord ::         (Generic a, GToRecord (Rep a) (B.ByteString, B.ByteString)) =>         a -> NamedRecord-    toNamedRecord = namedRecord . gtoRecord . from+    toNamedRecord = genericToNamedRecord defaultOptions +-- | A configurable CSV named record creator.  This function applied+--   to 'defaultOptions' is used as the default for 'toNamedRecord' when+--   the type is an instance of 'Generic'.+--+--   @since 0.5.1.0+genericToNamedRecord :: (Generic a, GToRecord (Rep a) (B.ByteString, B.ByteString))+                        => Options -> a -> NamedRecord+genericToNamedRecord opts = namedRecord . gtoRecord opts . from+ -- | A type that has a default field order when converted to CSV. This -- class lets you specify how to get the headers to use for a record -- type that's an instance of 'ToNamedRecord'.@@ -617,8 +706,17 @@     default headerOrder ::         (Generic a, GToNamedRecordHeader (Rep a)) =>         a -> Header-    headerOrder = V.fromList. gtoNamedRecordHeader . from+    headerOrder = genericHeaderOrder defaultOptions +-- | A configurable CSV header record generator.  This function+--   applied to 'defaultOptions' is used as the default for+--   'headerOrder' when the type is an instance of 'Generic'.+--+--   @since 0.5.1.0+genericHeaderOrder :: (Generic a, GToNamedRecordHeader (Rep a))+                      => Options -> a -> Header+genericHeaderOrder opts = V.fromList. gtoNamedRecordHeader opts . from+ instance (FromField a, FromField b, Ord a) => FromNamedRecord (M.Map a b) where     parseNamedRecord m = M.fromList <$>                          (traverse parseBoth $ HM.toList m)@@ -715,6 +813,22 @@     toField = toField . T.encodeUtf8 . T.singleton     {-# INLINE toField #-} +-- | Accepts the same syntax as 'rational'. Ignores whitespace.+--+-- @since 0.5.1.0+instance FromField Scientific where+  parseField s = case parseOnly (ws *> A8.scientific <* ws) s of+                   Left err -> typeError "Scientific" s (Just err)+                   Right n  -> pure n+  {-# INLINE parseField #-}++-- | Uses decimal notation or scientific notation, depending on the number.+--+-- @since 0.5.1.0+instance ToField Scientific where+  toField = scientific+  {-# INLINE toField #-}+ -- | Accepts same syntax as 'rational'. Ignores whitespace. instance FromField Double where     parseField = parseDouble@@ -814,6 +928,20 @@     {-# INLINE toField #-}  -- | Accepts an unsigned decimal number. Ignores whitespace.+--+-- @since 0.5.1.0+instance FromField Natural where+    parseField = parseUnsigned "Natural"+    {-# INLINE parseField #-}++-- | Uses decimal encoding.+--+-- @since 0.5.1.0+instance ToField Natural where+    toField = decimal+    {-# INLINE toField #-}++-- | Accepts an unsigned decimal number. Ignores whitespace. instance FromField Word8 where     parseField = parseUnsigned "Word8"     {-# INLINE parseField #-}@@ -880,10 +1008,16 @@ #endif  #if MIN_VERSION_text_short(0,1,0)+-- | Assumes UTF-8 encoding. Fails on invalid byte sequences.+--+-- @since 0.5.0.0 instance FromField T.S.ShortText where     parseField = maybe (fail "Invalid UTF-8 stream") pure . T.S.fromByteString     {-# INLINE parseField #-} +-- | Uses UTF-8 encoding.+--+-- @since 0.5.0.0 instance ToField T.S.ShortText where     toField = T.S.toByteString     {-# INLINE toField #-}@@ -1057,6 +1191,7 @@     fail = Fail.fail     {-# INLINE fail #-} +-- | @since 0.5.0.0 instance Fail.MonadFail Parser where     fail msg = Parser $ \kf _ks -> kf msg     {-# INLINE fail #-}@@ -1085,6 +1220,7 @@                                    in unParser a kf' ks     {-# INLINE mplus #-} +-- | @since 0.5.0.0 instance Semigroup (Parser a) where     (<>) = mplus     {-# INLINE (<>) #-}@@ -1119,116 +1255,118 @@ -- Generics  class GFromRecord f where-    gparseRecord :: Record -> Parser (f p)+    gparseRecord :: Options -> Record -> Parser (f p)  instance GFromRecordSum f Record => GFromRecord (M1 i n f) where-    gparseRecord v =-        case (IM.lookup n gparseRecordSum) of+    gparseRecord opts v =+        case IM.lookup n (gparseRecordSum opts) of             Nothing -> lengthMismatch n v             Just p -> M1 <$> p v       where         n = V.length v  class GFromNamedRecord f where-    gparseNamedRecord :: NamedRecord -> Parser (f p)+    gparseNamedRecord :: Options -> NamedRecord -> Parser (f p)  instance GFromRecordSum f NamedRecord => GFromNamedRecord (M1 i n f) where-    gparseNamedRecord v =-        foldr (\f p -> p <|> M1 <$> f v) empty (IM.elems gparseRecordSum)+    gparseNamedRecord opts v =+        foldr (\f p -> p <|> M1 <$> f v) empty (IM.elems (gparseRecordSum opts))  class GFromRecordSum f r where-    gparseRecordSum :: IM.IntMap (r -> Parser (f p))+    gparseRecordSum :: Options -> IM.IntMap (r -> Parser (f p))  instance (GFromRecordSum a r, GFromRecordSum b r) => GFromRecordSum (a :+: b) r where-    gparseRecordSum =+    gparseRecordSum opts =         IM.unionWith (\a b r -> a r <|> b r)-            (fmap (L1 <$>) <$> gparseRecordSum)-            (fmap (R1 <$>) <$> gparseRecordSum)+            (fmap (L1 <$>) <$> gparseRecordSum opts)+            (fmap (R1 <$>) <$> gparseRecordSum opts)  instance GFromRecordProd f r => GFromRecordSum (M1 i n f) r where-    gparseRecordSum = IM.singleton n (fmap (M1 <$>) f)+    gparseRecordSum opts = IM.singleton n (fmap (M1 <$>) f)       where-        (n, f) = gparseRecordProd 0+        (n, f) = gparseRecordProd opts 0  class GFromRecordProd f r where-    gparseRecordProd :: Int -> (Int, r -> Parser (f p))+    gparseRecordProd :: Options -> Int -> (Int, r -> Parser (f p))  instance GFromRecordProd U1 r where-    gparseRecordProd n = (n, const (pure U1))+    gparseRecordProd _ n = (n, const (pure U1))  instance (GFromRecordProd a r, GFromRecordProd b r) => GFromRecordProd (a :*: b) r where-    gparseRecordProd n0 = (n2, f)+    gparseRecordProd opts n0 = (n2, f)       where         f r = (:*:) <$> fa r <*> fb r-        (n1, fa) = gparseRecordProd n0-        (n2, fb) = gparseRecordProd n1+        (n1, fa) = gparseRecordProd opts n0+        (n2, fb) = gparseRecordProd opts n1  instance GFromRecordProd f Record => GFromRecordProd (M1 i n f) Record where-    gparseRecordProd n = fmap (M1 <$>) <$> gparseRecordProd n+    gparseRecordProd opts n = fmap (M1 <$>) <$> gparseRecordProd opts n  instance FromField a => GFromRecordProd (K1 i a) Record where-    gparseRecordProd n = (n + 1, \v -> K1 <$> parseField (V.unsafeIndex v n))+    gparseRecordProd _ n = (n + 1, \v -> K1 <$> parseField (V.unsafeIndex v n))  data Proxy s (f :: * -> *) a = Proxy  instance (FromField a, Selector s) => GFromRecordProd (M1 S s (K1 i a)) NamedRecord where-    gparseRecordProd n = (n + 1, \v -> (M1 . K1) <$> v .: name)+    gparseRecordProd opts n = (n + 1, \v -> (M1 . K1) <$> v .: name)       where-        name = T.encodeUtf8 (T.pack (selName (Proxy :: Proxy s f a)))+        name = T.encodeUtf8 (T.pack (fieldLabelModifier opts (selName (Proxy :: Proxy s f a))))   class GToRecord a f where-    gtoRecord :: a p -> [f]+    gtoRecord :: Options -> a p -> [f]  instance GToRecord U1 f where-    gtoRecord U1 = []+    gtoRecord _ U1 = []  instance (GToRecord a f, GToRecord b f) => GToRecord (a :*: b) f where-    gtoRecord (a :*: b) = gtoRecord a ++ gtoRecord b+    gtoRecord opts (a :*: b) = gtoRecord opts a ++ gtoRecord opts b  instance (GToRecord a f, GToRecord b f) => GToRecord (a :+: b) f where-    gtoRecord (L1 a) = gtoRecord a-    gtoRecord (R1 b) = gtoRecord b+    gtoRecord opts (L1 a) = gtoRecord opts a+    gtoRecord opts (R1 b) = gtoRecord opts b  instance GToRecord a f => GToRecord (M1 D c a) f where-    gtoRecord (M1 a) = gtoRecord a+    gtoRecord opts (M1 a) = gtoRecord opts a  instance GToRecord a f => GToRecord (M1 C c a) f where-    gtoRecord (M1 a) = gtoRecord a+    gtoRecord opts (M1 a) = gtoRecord opts a  instance GToRecord a Field => GToRecord (M1 S c a) Field where-    gtoRecord (M1 a) = gtoRecord a+    gtoRecord opts (M1 a) = gtoRecord opts a  instance ToField a => GToRecord (K1 i a) Field where-    gtoRecord (K1 a) = [toField a]+    gtoRecord _ (K1 a) = [toField a]  instance (ToField a, Selector s) => GToRecord (M1 S s (K1 i a)) (B.ByteString, B.ByteString) where-    gtoRecord m@(M1 (K1 a)) = [T.encodeUtf8 (T.pack (selName m)) .= toField a]+    gtoRecord opts m@(M1 (K1 a)) = [name .= toField a]+      where+        name = T.encodeUtf8 (T.pack (fieldLabelModifier opts (selName m)))  -- We statically fail on sum types and product types without selectors -- (field names).  class GToNamedRecordHeader a   where-    gtoNamedRecordHeader :: a p -> [Name]+    gtoNamedRecordHeader :: Options -> a p -> [Name]  instance GToNamedRecordHeader U1   where-    gtoNamedRecordHeader _ = []+    gtoNamedRecordHeader _ _ = []  instance (GToNamedRecordHeader a, GToNamedRecordHeader b) =>          GToNamedRecordHeader (a :*: b)   where-    gtoNamedRecordHeader _ = gtoNamedRecordHeader (undefined :: a p) ++-                             gtoNamedRecordHeader (undefined :: b p)+    gtoNamedRecordHeader opts _ = gtoNamedRecordHeader opts (undefined :: a p) +++                                  gtoNamedRecordHeader opts (undefined :: b p)  instance GToNamedRecordHeader a => GToNamedRecordHeader (M1 D c a)   where-    gtoNamedRecordHeader _ = gtoNamedRecordHeader (undefined :: a p)+    gtoNamedRecordHeader opts _ = gtoNamedRecordHeader opts (undefined :: a p)  instance GToNamedRecordHeader a => GToNamedRecordHeader (M1 C c a)   where-    gtoNamedRecordHeader _ = gtoNamedRecordHeader (undefined :: a p)+    gtoNamedRecordHeader opts _ = gtoNamedRecordHeader opts (undefined :: a p)  -- | Instance to ensure that you cannot derive DefaultOrdered for -- constructors without selectors.@@ -1239,12 +1377,12 @@ instance DefaultOrdered (M1 S NoSelector a ()) => GToNamedRecordHeader (M1 S NoSelector a) #endif   where-    gtoNamedRecordHeader _ =+    gtoNamedRecordHeader _ _ =         error "You cannot derive DefaultOrdered for constructors without selectors."  instance Selector s => GToNamedRecordHeader (M1 S s a)   where-    gtoNamedRecordHeader m+    gtoNamedRecordHeader opts m         | null name = error "Cannot derive DefaultOrdered for constructors without selectors"-        | otherwise = [B8.pack (selName m)]+        | otherwise = [B8.pack (fieldLabelModifier opts (selName m))]       where name = selName m
Data/Csv/Conversion/Internal.hs view
@@ -1,17 +1,20 @@ module Data.Csv.Conversion.Internal     ( decimal+    , scientific     , realFloat     ) where  import Data.ByteString.Builder (Builder, toLazyByteString, word8, char8,                                 string8, byteString) import qualified Data.ByteString.Builder.Prim as BP+import Data.ByteString.Builder.Scientific (scientificBuilder) import Data.Array.Base (unsafeAt) import Data.Array.IArray import qualified Data.ByteString as B import Data.Char (ord) import Data.Int import Data.Monoid+import Data.Scientific (Scientific) import Data.Word  import Data.Csv.Util (toStrict)@@ -98,6 +101,10 @@  ------------------------------------------------------------------------ -- Floating point numbers++scientific :: Scientific -> B.ByteString+scientific = toStrict . toLazyByteString . scientificBuilder+{-# INLINE scientific #-}  realFloat :: RealFloat a => a -> B.ByteString {-# SPECIALIZE realFloat :: Float -> B.ByteString #-}
Data/Csv/Incremental.hs view
@@ -344,6 +344,7 @@       runBuilder :: Quoting -> Word8 -> Bool -> Builder.Builder     } +-- | @since 0.5.0.0 instance Semigroup (Builder a) where     Builder f <> Builder g =         Builder $ \ qtng delim useCrlf ->@@ -420,6 +421,7 @@       runNamedBuilder :: Header -> Quoting -> Word8 -> Bool -> Builder.Builder     } +-- | @since 0.5.0.0 instance Semigroup (NamedBuilder a) where     NamedBuilder f <> NamedBuilder g =         NamedBuilder $ \ hdr qtng delim useCrlf ->
+ README.md view
@@ -0,0 +1,98 @@+# `cassava`: A CSV parsing and encoding library [![Hackage](https://img.shields.io/hackage/v/cassava.svg)](https://hackage.haskell.org/package/cassava) [![Build Status](https://travis-ci.org/hvr/cassava.svg)](https://travis-ci.org/hvr/cassava)++**Please refer to the [package description](https://hackage.haskell.org/package/cassava#description) for an overview of `cassava`.**++## Usage example++Here's the two second crash course in using the library. Given a CSV file with this content:++```csv+John Doe,50000+Jane Doe,60000+```++here's how you'd process it record-by-record:++```haskell+{-# LANGUAGE ScopedTypeVariables #-}++import qualified Data.ByteString.Lazy as BL+import Data.Csv+import qualified Data.Vector as V++main :: IO ()+main = do+    csvData <- BL.readFile "salaries.csv"+    case decode NoHeader csvData of+        Left err -> putStrLn err+        Right v -> V.forM_ v $ \ (name, salary :: Int) ->+            putStrLn $ name ++ " earns " ++ show salary ++ " dollars"+```++If you want to parse a file that includes a header, like this one++```csv+name,salary+John Doe,50000+Jane Doe,60000+```++use [`decodeByName`](https://hackage.haskell.org/package/cassava/docs/Data-Csv.html#v:decodeByName):++```haskell+{-# LANGUAGE OverloadedStrings #-}++import Control.Applicative+import qualified Data.ByteString.Lazy as BL+import Data.Csv+import qualified Data.Vector as V++data Person = Person+    { name   :: !String+    , salary :: !Int+    }++instance FromNamedRecord Person where+    parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"++main :: IO ()+main = do+    csvData <- BL.readFile "salaries.csv"+    case decodeByName csvData of+        Left err -> putStrLn err+        Right (_, v) -> V.forM_ v $ \ p ->+            putStrLn $ name p ++ " earns " ++ show (salary p) ++ " dollars"+```++You can find more code examples in the [`examples/` folder](https://github.com/hvr/cassava/tree/master/examples) as well as smaller usage examples in the [`Data.Csv` module documentation](https://hackage.haskell.org/package/cassava/docs/Data-Csv.html).++## Project Goals for `cassava`++There's no end to what people consider CSV data. Most programs don't+follow [RFC4180](https://tools.ietf.org/html/rfc4180) so one has to+make a judgment call which contributions to accept.  Consequently, not+everything gets accepted, because then we'd end up with a (slow)+general purpose parsing library. There are plenty of those. The goal+is to roughly accept what the Python+[`csv`](https://docs.python.org/3/library/csv.html) module accepts.++The Python `csv` module (which is implemented in C) is also considered+the base-line for performance.  Adding options (e.g. the above+mentioned parsing "flexibility") will have to be a trade off against+performance. There's been complaints about performance in the past,+therefore, if in doubt performance wins over features.++Last but not least, it's important to keep the dependency footprint+light, as each additional dependency incurs costs and risks in terms+of additional maintenance overhead and loss of flexibility. So adding+a new package dependency should only be done if that dependency is+known to be a reliable package and there's a clear benefit which+outweights the cost.++## Further reading++The primary API documentation for `cassava` is its Haddock documentation which can be found at http://hackage.haskell.org/package/cassava/docs/Data-Csv.html ++Below are listed additional recommended third-party blogposts and tutorials++ - [CSV encoding and decoding in Haskell with Cassava](https://www.stackbuilders.com/tutorials/haskell/csv-encoding-decoding/)
cassava.cabal view
@@ -1,9 +1,33 @@+cabal-version:       1.12 Name:                cassava-Version:             0.5.0.0+Version:             0.5.1.0 Synopsis:            A CSV parsing and encoding library-Description:-  A CSV parsing and encoding library optimized for ease of use and high-  performance.+Description: {++@cassava@ is a library for parsing and encoding [RFC 4180](https://tools.ietf.org/html/rfc4180)+compliant [comma-separated values (CSV)](https://en.wikipedia.org/wiki/Comma-separated_values) data,+which is a textual line-oriented format commonly used for exchanging tabular data.+.+@cassava@'s API includes support for+.+- Index-based record-conversion+- Name-based record-conversion+- Typeclass directed conversion of fields and records+- Built-in field-conversion instances for standard types+- Customizable record-conversion instance derivation via GHC generics+- Low-level [bytestring](https://hackage.haskell.org/package/bytestring) builders (see "Data.Csv.Builder")+- Incremental decoding and encoding API (see "Data.Csv.Incremental")+- Streaming API for constant-space decoding (see "Data.Csv.Streaming")+.+Moreover, this library is designed to be easy to use; for instance, here's a+very simple example of encoding CSV data:+.+>>> Data.Csv.encode [("John",27),("Jane",28)]+"John,27\r\nJane,28\r\n"+.+Please refer to the documentation in "Data.Csv" and the included [README](#readme) for more usage examples.++} Homepage:            https://github.com/hvr/cassava License:             BSD3 License-file:        LICENSE@@ -15,16 +39,20 @@ Maintainer:          hvr@gnu.org Category:            Text, Web, CSV Build-type:          Simple-Cabal-version:       >=1.10 Extra-source-files:  examples/*.hs,-                     CHANGES.md+                     CHANGES.md,+                     README.md Tested-with:         GHC ==8.2.1, GHC ==8.0.2, GHC ==8.0.1, GHC ==7.10.3, GHC ==7.8.4, GHC ==7.6.3, GHC ==7.4.2  ---------------------------------------------------------------------------- -flag pre-bytestring-0-10-4-  description: bytestring < 0.10.4+source-repository head+  type:     git+  location: https://github.com/hvr/cassava.git +flag bytestring--LT-0_10_4+  description: [bytestring](https://hackage.haskell.org/haskell/package/bytestring) < 0.10.4+ Library   default-language: Haskell2010   other-extensions:@@ -65,18 +93,19 @@    Build-depends:     array >= 0.4 && < 0.6,-    attoparsec >= 0.10.2 && < 0.14,+    attoparsec >= 0.11.3.0 && < 0.14,     base >= 4.5 && < 5,     bytestring >= 0.9.2 && < 0.11,     containers >= 0.4.2 && < 0.6,     deepseq >= 1.1 && < 1.5,     hashable < 1.3,+    scientific >= 0.3.4.7 && < 0.4,     text < 1.3,     unordered-containers < 0.3,     vector >= 0.8 && < 0.13,     Only >= 0.1 && < 0.1.1 -  if flag(pre-bytestring-0-10-4)+  if flag(bytestring--LT-0_10_4)     build-depends: bytestring <  0.10.4                  , bytestring-builder >= 0.10.8 && < 0.11   else@@ -87,6 +116,10 @@   if impl(ghc < 7.6)     build-depends: ghc-prim == 0.2.* +  -- For Numeric.Natural+  if impl(ghc < 7.10)+    build-depends: nats >= 1 && < 1.2+   -- https://ghc.haskell.org/trac/ghc/wiki/Migration/8.0#Recommendationsforforward-compatibility   if impl(ghc >= 8.0)     ghc-options: -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances@@ -112,17 +145,27 @@                , bytestring                , cassava                , hashable+               , scientific                , text                , unordered-containers                , vector   -- extra dependencies not already used by lib:cassava   build-depends: HUnit < 1.7                , QuickCheck == 2.10.*+               , quickcheck-instances >= 0.3.12 && < 0.4                , test-framework == 0.8.*                , test-framework-hunit == 0.3.*                , test-framework-quickcheck2 == 0.3.*    hs-source-dirs: tests++  -- GHC.Generics lived in `ghc-prim` for GHC 7.2 & GHC 7.4 only+  if impl(ghc < 7.6)+    build-depends: ghc-prim == 0.2.*++  -- For Numeric.Natural+  if impl(ghc < 7.10)+    build-depends: nats >= 1 && < 1.2    -- https://ghc.haskell.org/trac/ghc/wiki/Migration/8.0#Recommendationsforforward-compatibility   if impl(ghc >= 8.0)
tests/UnitTests.hs view
@@ -1,6 +1,11 @@-{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE CPP, DeriveGeneric, OverloadedStrings, ScopedTypeVariables #-} +#if __GLASGOW_HASKELL__ >= 801+{-# OPTIONS_GHC -Wno-orphans -Wno-unused-top-binds #-}+#else+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-unused-binds #-}+#endif+ module Main     ( main     ) where@@ -10,20 +15,28 @@ import qualified Data.ByteString.Lazy.Char8 as BL8 import qualified Data.HashMap.Strict as HM import Data.Int+import Data.Scientific (Scientific) import qualified Data.Text as T import qualified Data.Text.Lazy as LT import qualified Data.Vector as V import qualified Data.Foldable as F import Data.Word+import Numeric.Natural+import GHC.Generics (Generic) import Test.HUnit import Test.Framework as TF import Test.Framework.Providers.HUnit as TF import Test.QuickCheck+import Test.QuickCheck.Instances () import Test.Framework.Providers.QuickCheck2 as TF  import Data.Csv hiding (record) import qualified Data.Csv.Streaming as S +#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>), (<*>))+#endif+ ------------------------------------------------------------------------ -- Parse tests @@ -240,18 +253,6 @@ ------------------------------------------------------------------------ -- Conversion tests -instance Arbitrary B.ByteString where-    arbitrary = B.pack `fmap` arbitrary--instance Arbitrary BL.ByteString where-    arbitrary = BL.fromChunks `fmap` arbitrary--instance Arbitrary T.Text where-    arbitrary = T.pack `fmap` arbitrary--instance Arbitrary LT.Text where-    arbitrary = LT.fromChunks `fmap` arbitrary- -- A single column with an empty string is indistinguishable from an -- empty line (which we will ignore.) We therefore encode at least two -- columns.@@ -291,11 +292,13 @@       , testProperty "Int16" (roundTrip :: Int16 -> Bool)       , testProperty "Int32" (roundTrip :: Int32 -> Bool)       , testProperty "Int64" (roundTrip :: Int64 -> Bool)+      , testProperty "Natural" (roundTrip :: Natural -> Bool)       , testProperty "Word" (roundTrip :: Word -> Bool)       , testProperty "Word8" (roundTrip :: Word8 -> Bool)       , testProperty "Word16" (roundTrip :: Word16 -> Bool)       , testProperty "Word32" (roundTrip :: Word32 -> Bool)       , testProperty "Word64" (roundTrip :: Word64 -> Bool)+      , testProperty "Scientific" (roundTrip :: Scientific -> Bool)       , testProperty "lazy ByteString"         (roundTrip :: BL.ByteString -> Bool)       , testProperty "Text" (roundTrip :: T.Text -> Bool)@@ -321,25 +324,35 @@                             "Cam yiyebilirim, bana zararı dokunmaz.")       ]     , testGroup "Partial Decodes"-      [ testCase "Int"     (partialDecode-                            (parseField "12.7" :: Parser Int))-      , testCase "Word"    (partialDecode-                            (parseField "12.7" :: Parser Word))-      , testCase "Double"  (partialDecode-                            (parseField "1.0+" :: Parser Double))-      , testCase "Integer" (partialDecode-                            (parseField "1e6"  :: Parser Integer))+      [ testCase "Int"         (partialDecode+                                (parseField "12.7" :: Parser Int))+      , testCase "Natural"     (partialDecode+                                (parseField "12.7" :: Parser Natural))+      , testCase "Word"        (partialDecode+                                (parseField "12.7" :: Parser Word))+      , testCase "Scientific"  (partialDecode+                                (parseField "1.0+" :: Parser Scientific))+      , testCase "Double"      (partialDecode+                                (parseField "1.0+" :: Parser Double))+      , testCase "Integer"     (partialDecode+                                (parseField "1e6"  :: Parser Integer))       ]     , testGroup "Space trimming"-      [ testCase "_Int"     (expect (parseField " 12"     :: Parser Int)    12)-      , testCase "Int_"     (expect (parseField "12 "     :: Parser Int)    12)-      , testCase "_Int_"    (expect (parseField " 12 "    :: Parser Int)    12)-      , testCase "_Word"    (expect (parseField " 12"     :: Parser Word)   12)-      , testCase "Word_"    (expect (parseField "12 "     :: Parser Word)   12)-      , testCase "_Word_"   (expect (parseField " 12 "    :: Parser Word)   12)-      , testCase "_Double"  (expect (parseField " 1.2e1"  :: Parser Double) 12)-      , testCase "Double_"  (expect (parseField "1.2e1 "  :: Parser Double) 12)-      , testCase "_Double_" (expect (parseField " 1.2e1 " :: Parser Double) 12)+      [ testCase "_Int"         (expect (parseField " 12"     :: Parser Int)        12)+      , testCase "Int_"         (expect (parseField "12 "     :: Parser Int)        12)+      , testCase "_Int_"        (expect (parseField " 12 "    :: Parser Int)        12)+      , testCase "_Natural"     (expect (parseField " 12"     :: Parser Natural)    12)+      , testCase "Natural_"     (expect (parseField "12 "     :: Parser Natural)    12)+      , testCase "_Natural_"    (expect (parseField " 12 "    :: Parser Natural)    12)+      , testCase "_Word"        (expect (parseField " 12"     :: Parser Word)       12)+      , testCase "Word_"        (expect (parseField "12 "     :: Parser Word)       12)+      , testCase "_Word_"       (expect (parseField " 12 "    :: Parser Word)       12)+      , testCase "_Scientific"  (expect (parseField " 1.2e1"  :: Parser Scientific) 12)+      , testCase "Scientific_"  (expect (parseField "1.2e1 "  :: Parser Scientific) 12)+      , testCase "_Scientific_" (expect (parseField " 1.2e1 " :: Parser Scientific) 12)+      , testCase "_Double"      (expect (parseField " 1.2e1"  :: Parser Double)     12)+      , testCase "Double_"      (expect (parseField "1.2e1 "  :: Parser Double)     12)+      , testCase "_Double_"     (expect (parseField " 1.2e1 " :: Parser Double)     12)       ]     ] @@ -379,6 +392,53 @@     expected = ["a" :: String]  ------------------------------------------------------------------------+-- Custom conversion option tests++genericConversionTests :: [TF.Test]+genericConversionTests =+    [ testCase "headerOrder" (header ["column1", "column2", "column_3"] @=? hdrs)+    , testCase "encode" (encodeDefaultOrderedByName sampleValues @?= sampleEncoding)+    , testCase "decode" (Right (hdrs, V.fromList sampleValues) @=? decodeByName sampleEncoding)+    , testProperty "roundTrip" rtProp+    ]+  where+    hdrs = headerOrder (undefined :: SampleType)++    sampleValues = [ SampleType ""      1     Nothing+                   , SampleType "field" 99999 (Just 1.234)+                   ]++    sampleEncoding = "column1,column2,column_3\r\n,1,\r\nfield,99999,1.234\r\n"++    rtProp :: [SampleType] -> Bool+    rtProp vs = Right (hdrs, V.fromList vs)+                == decodeByName (encodeDefaultOrderedByName vs)++data SampleType = SampleType+  { _column1  :: !T.Text+  , column2   :: !Int+  , _column_3 :: !(Maybe Double)+  } deriving (Eq, Show, Read, Generic)++sampleOptions :: Options+sampleOptions = defaultOptions { fieldLabelModifier = rmUnderscore }+  where+    rmUnderscore ('_':str) = str+    rmUnderscore str       = str++instance ToNamedRecord SampleType where+  toNamedRecord = genericToNamedRecord sampleOptions++instance FromNamedRecord SampleType where+  parseNamedRecord = genericParseNamedRecord sampleOptions++instance DefaultOrdered SampleType where+  headerOrder = genericHeaderOrder sampleOptions++instance Arbitrary SampleType where+  arbitrary = SampleType <$> arbitrary <*> arbitrary <*> arbitrary++------------------------------------------------------------------------ -- Test harness  allTests :: [TF.Test]@@ -387,6 +447,7 @@            , testGroup "conversion" conversionTests            , testGroup "custom-options" customOptionsTests            , testGroup "instances" instanceTests+           , testGroup "generic-conversions" genericConversionTests            ]  main :: IO ()