Copyright | Ozgun Ataman, Johan Tibell |
---|---|
License | BSD3 |
Maintainer | Ozgun Ataman <ozataman@gmail.com> |
Stability | experimental |
Safe Haskell | None |
Language | Haskell98 |
This module has been shamelessly taken from Johan Tibell's nicely
put together cassava package, which itself borrows the approach
from Bryan OSullivan
s widely used aeson package.
We make the necessary adjustments and some simplifications here to bolt this parsing interface onto our underlying CSV typeclass.
- newtype Only a = Only {
- fromOnly :: a
- newtype Named a = Named {
- getNamed :: a
- type Record = Vector ByteString
- type NamedRecord = Map ByteString ByteString
- class FromRecord a where
- parseRecord :: Record -> Parser a
- class FromNamedRecord a where
- parseNamedRecord :: NamedRecord -> Parser a
- class ToNamedRecord a where
- toNamedRecord :: a -> NamedRecord
- class FromField a where
- parseField :: Field -> Parser a
- class ToRecord a where
- class ToField a where
- toField :: a -> Field
- data Parser a
- runParser :: Parser a -> Either String a
- index :: FromField a => Record -> Int -> Parser a
- (.!) :: FromField a => Record -> Int -> Parser a
- unsafeIndex :: FromField a => Record -> Int -> Parser a
- lookup :: FromField a => NamedRecord -> ByteString -> Parser a
- (.:) :: FromField a => NamedRecord -> ByteString -> Parser a
- namedField :: ToField a => ByteString -> a -> (ByteString, ByteString)
- (.=) :: ToField a => ByteString -> a -> (ByteString, ByteString)
- record :: [ByteString] -> Record
- namedRecord :: [(ByteString, ByteString)] -> NamedRecord
Type conversion
Haskell lacks a single-element tuple type, so if you CSV data
with just one column you can use the Only
type to represent a
single-column result.
A wrapper around custom haskell types that can directly be converted/parsed from an incoming CSV stream.
We define this wrapper to stop GHC from complaining
about overlapping instances. Just use getNamed
to get your
object out of the wrapper.
type Record = Vector ByteString Source
A record corresponds to a single line in a CSV file.
type NamedRecord = Map ByteString ByteString Source
A shorthand for the ByteString case of MapRow
class FromRecord a where Source
A type that can be converted from a single CSV record, with the possibility of failure.
When writing an instance, use empty
, mzero
, or fail
to make a
conversion fail, e.g. if a Record
has the wrong number of
columns.
Given this example data:
John,56 Jane,55
here's an example type and instance:
data Person = Person { name :: !Text, age :: !Int } instance FromRecord Person where parseRecord v | length v == 2 = Person <$> v .! 0 <*> v .! 1 | otherwise = mzero
Nothing
parseRecord :: Record -> Parser a Source
FromField a => FromRecord [a] | |
FromField a => FromRecord (Vector a) | |
(FromField a, Unbox a) => FromRecord (Vector a) | |
FromField a => FromRecord (Only a) | |
(FromField a, FromField b) => FromRecord (a, b) | |
(FromField a, FromField b, FromField c) => FromRecord (a, b, c) | |
(FromField a, FromField b, FromField c, FromField d) => FromRecord (a, b, c, d) | |
(FromField a, FromField b, FromField c, FromField d, FromField e) => FromRecord (a, b, c, d, e) | |
(FromField a, FromField b, FromField c, FromField d, FromField e, FromField f) => FromRecord (a, b, c, d, e, f) | |
(FromField a, FromField b, FromField c, FromField d, FromField e, FromField f, FromField g) => FromRecord (a, b, c, d, e, f, g) |
class FromNamedRecord a where Source
A type that can be converted from a single CSV record, with the possibility of failure.
When writing an instance, use empty
, mzero
, or fail
to make a
conversion fail, e.g. if a Record
has the wrong number of
columns.
Given this example data:
name,age John,56 Jane,55
here's an example type and instance:
{-# LANGUAGE OverloadedStrings #-} data Person = Person { name :: !Text, age :: !Int } instance FromRecord Person where parseNamedRecord m = Person <$> m .: "name" <*> m .: "age"
Note the use of the OverloadedStrings
language extension which
enables ByteString
values to be written as string literals.
Nothing
parseNamedRecord :: NamedRecord -> Parser a Source
FromField a => FromNamedRecord (Map ByteString a) |
class ToNamedRecord a where Source
A type that can be converted to a single CSV record.
An example type and instance:
data Person = Person { name :: !Text, age :: !Int } instance ToRecord Person where toNamedRecord (Person name age) = namedRecord [ "name" .= name, "age" .= age]
Nothing
toNamedRecord :: a -> NamedRecord Source
ToField a => ToNamedRecord (Map ByteString a) |
class FromField a where Source
A type that can be converted from a single CSV field, with the possibility of failure.
When writing an instance, use empty
, mzero
, or fail
to make a
conversion fail, e.g. if a Field
can't be converted to the given
type.
Example type and instance:
{-# LANGUAGE OverloadedStrings #-} data Color = Red | Green | Blue instance FromField Color where parseField s | s == "R" = pure Red | s == "G" = pure Green | s == "B" = pure Blue | otherwise = mzero
parseField :: Field -> Parser a Source
FromField Char | Assumes UTF-8 encoding. |
FromField Double | Accepts same syntax as |
FromField Float | Accepts same syntax as |
FromField Int | Accepts a signed decimal number. |
FromField Int8 | Accepts a signed decimal number. |
FromField Int16 | Accepts a signed decimal number. |
FromField Int32 | Accepts a signed decimal number. |
FromField Int64 | Accepts a signed decimal number. |
FromField Integer | Accepts a signed decimal number. |
FromField Word | Accepts an unsigned decimal number. |
FromField Word8 | Accepts an unsigned decimal number. |
FromField Word16 | Accepts an unsigned decimal number. |
FromField Word32 | Accepts an unsigned decimal number. |
FromField Word64 | Accepts an unsigned decimal number. |
FromField () | Ignores the |
FromField ByteString | |
FromField ByteString | |
FromField Text | Assumes UTF-8 encoding. Fails on invalid byte sequences. |
FromField Text | Assumes UTF-8 encoding. Fails on invalid byte sequences. |
FromField [Char] | Assumes UTF-8 encoding. Fails on invalid byte sequences. |
FromField a => FromField (Maybe a) |
A type that can be converted to a single CSV record.
An example type and instance:
data Person = Person { name :: !Text, age :: !Int } instance ToRecord Person where toRecord (Person name age) = record [ toField name, toField age]
Outputs data on this form:
John,56 Jane,55
Nothing
ToField a => ToRecord [a] | |
ToField a => ToRecord (Vector a) | |
(ToField a, Unbox a) => ToRecord (Vector a) | |
ToField a => ToRecord (Only a) | |
(ToField a, ToField b) => ToRecord (a, b) | |
(ToField a, ToField b, ToField c) => ToRecord (a, b, c) | |
(ToField a, ToField b, ToField c, ToField d) => ToRecord (a, b, c, d) | |
(ToField a, ToField b, ToField c, ToField d, ToField e) => ToRecord (a, b, c, d, e) | |
(ToField a, ToField b, ToField c, ToField d, ToField e, ToField f) => ToRecord (a, b, c, d, e, f) | |
(ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g) => ToRecord (a, b, c, d, e, f, g) |
A type that can be converted to a single CSV field.
Example type and instance:
{-# LANGUAGE OverloadedStrings #-} data Color = Red | Green | Blue instance ToField Color where toField Red = "R" toField Green = "G" toField Blue = "B"
ToField Char | Uses UTF-8 encoding. |
ToField Double | Uses decimal notation or scientific notation, depending on the number. |
ToField Float | Uses decimal notation or scientific notation, depending on the number. |
ToField Int | Uses decimal encoding with optional sign. |
ToField Int8 | Uses decimal encoding with optional sign. |
ToField Int16 | Uses decimal encoding with optional sign. |
ToField Int32 | Uses decimal encoding with optional sign. |
ToField Int64 | Uses decimal encoding with optional sign. |
ToField Integer | Uses decimal encoding with optional sign. |
ToField Word | Uses decimal encoding. |
ToField Word8 | Uses decimal encoding. |
ToField Word16 | Uses decimal encoding. |
ToField Word32 | Uses decimal encoding. |
ToField Word64 | Uses decimal encoding. |
ToField ByteString | |
ToField ByteString | |
ToField Text | Uses UTF-8 encoding. |
ToField Text | Uses UTF-8 encoding. |
ToField [Char] | Uses UTF-8 encoding. |
ToField a => ToField (Maybe a) |
Parser
Conversion of a field to a value might fail e.g. if the field is
malformed. This possibility is captured by the Parser
type, which
lets you compose several field conversions together in such a way
that if any of them fail, the whole record conversion fails.
Accessors
index :: FromField a => Record -> Int -> Parser a Source
Retrieve the nth field in the given record. The result is
empty
if the value cannot be converted to the desired type.
Raises an exception if the index is out of bounds.
index
is a simple convenience function that is equivalent to
. If you're certain that the index is not
out of bounds, using parseField
(v !
idx)unsafeIndex
is somewhat faster.
unsafeIndex :: FromField a => Record -> Int -> Parser a Source
Like index
but without bounds checking.
lookup :: FromField a => NamedRecord -> ByteString -> Parser a Source
Retrieve a field in the given record by name. The result is
empty
if the field is missing or if the value cannot be converted
to the desired type.
(.:) :: FromField a => NamedRecord -> ByteString -> Parser a Source
Alias for lookup
.
namedField :: ToField a => ByteString -> a -> (ByteString, ByteString) Source
Construct a pair from a name and a value. For use with
namedRecord
.
(.=) :: ToField a => ByteString -> a -> (ByteString, ByteString) Source
Alias for namedField
.
record :: [ByteString] -> Record Source
Construct a record from a list of ByteString
s. Use toField
to convert values to ByteString
s for use with record
.
namedRecord :: [(ByteString, ByteString)] -> NamedRecord Source
Construct a named record from a list of name-value ByteString
pairs. Use .=
to construct such a pair from a name and a value.