{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
module Foundation.Format.CSV.Types
(
CSV
, unCSV
, Row
, unRow
, Record(..)
, Field(..)
, Escaping(..)
, IsField(..)
, integral
, float
, string
) where
import Basement.Imports
import Basement.BoxedArray (Array, length, unsafeIndex)
import Basement.NormalForm (NormalForm(..))
import Basement.From (Into, into)
import Basement.String (String, any, elem, null, uncons)
import qualified Basement.String as String (singleton)
import Basement.Types.Word128 (Word128)
import Basement.Types.Word256 (Word256)
import Basement.Types.OffsetSize (Offset, CountOf)
import Foundation.Collection.Element (Element)
import Foundation.Collection.Collection (Collection, nonEmpty_)
import Foundation.Collection.Sequential (Sequential)
import Foundation.Collection.Indexed (IndexedCollection)
import Foundation.Check.Arbitrary (Arbitrary(..), frequency)
import Foundation.String.Read (readDouble, readInteger)
data Field
= FieldInteger Integer
| FieldDouble Double
| FieldString String Escaping
deriving (Eq, Show, Typeable)
instance NormalForm Field where
toNormalForm (FieldInteger i) = toNormalForm i
toNormalForm (FieldDouble d) = toNormalForm d
toNormalForm (FieldString s e) = toNormalForm s `seq` toNormalForm e
instance Arbitrary Field where
arbitrary = frequency $ nonEmpty_ [ (1, FieldInteger <$> arbitrary)
, (1, FieldDouble <$> arbitrary)
, (3, string <$> arbitrary)
]
data Escaping = NoEscape | Escape | DoubleEscape
deriving (Eq, Ord, Enum, Bounded, Show, Typeable)
instance NormalForm Escaping where
toNormalForm !_ = ()
class IsField a where
toField :: a -> Field
fromField :: Field -> Either String a
instance IsField Field where
toField = id
fromField = pure
instance IsField a => IsField (Maybe a) where
toField Nothing = FieldString mempty NoEscape
toField (Just a) = toField a
fromField stuff@(FieldString p NoEscape)
| null p = pure Nothing
| otherwise = Just <$> fromField stuff
fromField stuff = Just <$> fromField stuff
fromIntegralField :: Integral b => Field -> Either String b
fromIntegralField (FieldString str NoEscape) = case readInteger str of
Nothing -> Left "Invalid integral field"
Just v -> pure $ fromInteger v
fromIntegralField (FieldInteger v) = pure (fromInteger v)
fromIntegralField _ = Left "Expected integral value"
fromDoubleField :: Field -> Either String Double
fromDoubleField (FieldString str NoEscape) = case readDouble str of
Nothing -> Left "Invalid double field"
Just v -> pure v
fromDoubleField (FieldDouble v) = pure v
fromDoubleField _ = Left "Expected double value"
instance IsField Bool where
toField = toField . show
fromField (FieldString "True" NoEscape) = pure True
fromField (FieldString "False" NoEscape) = pure False
fromField _ = Left "not a boolean value"
instance IsField Int8 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Int16 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Int32 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Int64 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Int where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word8 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word16 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word32 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word64 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word128 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Word256 where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Integer where
toField = FieldInteger
fromField = fromIntegralField
instance IsField Natural where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField Double where
toField = FieldDouble
fromField = fromDoubleField
instance IsField Char where
toField = string . String.singleton
fromField (FieldString str _) = case uncons str of
Just (c, str') | null str' -> pure c
| otherwise -> Left "Expected a char, but received a String"
Nothing -> Left "Expected a char"
fromField _ = Left "Expected a char"
instance IsField (Offset a) where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField (CountOf a) where
toField = FieldInteger . into
fromField = fromIntegralField
instance IsField [Char] where
toField = string . fromString
fromField (FieldString str _) = pure $ toList str
fromField _ = Left "Expected a Lazy String"
instance IsField String where
toField = string
fromField (FieldString str _) = pure str
fromField _ = Left "Expected a UTF8 String"
integral :: Into Integer a => a -> Field
integral = FieldInteger . into
float :: Double -> Field
float = FieldDouble
string :: String -> Field
string s = FieldString s encoding
where
encoding
| any g s = DoubleEscape
| any f s = Escape
| otherwise = NoEscape
g c = c == '\"'
f c = c `elem` ",\r\n"
newtype Row = Row { unRow :: Array Field }
deriving (Eq, Show, Typeable, Semigroup, Monoid, Collection, NormalForm, Sequential, IndexedCollection)
type instance Element Row = Field
instance IsList Row where
type Item Row = Field
toList = toList . unRow
fromList = Row . fromList
class Record a where
toRow :: a -> Row
fromRow :: Row -> Either String a
instance Record Row where
toRow = id
fromRow = pure
instance (IsField a, IsField b) => Record (a,b) where
toRow (a,b) = fromList [toField a, toField b]
fromRow (Row row)
| length row == 2 = (,) <$> fromField (row `unsafeIndex` 0) <*> fromField (row `unsafeIndex` 1)
| otherwise = Left (show row)
instance (IsField a, IsField b, IsField c) => Record (a,b,c) where
toRow (a,b,c) = fromList [toField a, toField b, toField c]
fromRow (Row row)
| length row == 3 = (,,) <$> fromField (row `unsafeIndex` 0)
<*> fromField (row `unsafeIndex` 1)
<*> fromField (row `unsafeIndex` 2)
| otherwise = Left (show row)
instance (IsField a, IsField b, IsField c, IsField d) => Record (a,b,c,d) where
toRow (a,b,c,d) = fromList [toField a, toField b, toField c, toField d]
fromRow (Row row)
| length row == 4 = (,,,) <$> fromField (row `unsafeIndex` 0)
<*> fromField (row `unsafeIndex` 1)
<*> fromField (row `unsafeIndex` 2)
<*> fromField (row `unsafeIndex` 3)
| otherwise = Left (show row)
instance (IsField a, IsField b, IsField c, IsField d, IsField e) => Record (a,b,c,d,e) where
toRow (a,b,c,d,e) = fromList [toField a, toField b, toField c, toField d, toField e]
fromRow (Row row)
| length row == 5 = (,,,,) <$> fromField (row `unsafeIndex` 0)
<*> fromField (row `unsafeIndex` 1)
<*> fromField (row `unsafeIndex` 2)
<*> fromField (row `unsafeIndex` 3)
<*> fromField (row `unsafeIndex` 4)
| otherwise = Left (show row)
instance (IsField a, IsField b, IsField c, IsField d, IsField e, IsField f) => Record (a,b,c,d,e,f) where
toRow (a,b,c,d,e,f) = fromList [toField a, toField b, toField c, toField d, toField e, toField f]
fromRow (Row row)
| length row == 6 = (,,,,,) <$> fromField (row `unsafeIndex` 0)
<*> fromField (row `unsafeIndex` 1)
<*> fromField (row `unsafeIndex` 2)
<*> fromField (row `unsafeIndex` 3)
<*> fromField (row `unsafeIndex` 4)
<*> fromField (row `unsafeIndex` 5)
| otherwise = Left (show row)
newtype CSV = CSV { unCSV :: Array Row }
deriving (Eq, Show, Typeable, Semigroup, Monoid, Collection, NormalForm, Sequential, IndexedCollection)
type instance Element CSV = Row
instance IsList CSV where
type Item CSV = Row
toList = toList . unCSV
fromList = CSV . fromList