{-# LANGUAGE OverloadedStrings #-}
module DataFrame.IO.CSV.Internal.Options (
HeaderSpec (..),
TypeSpec (..),
RaggedRowPolicy (..),
UnclosedQuotePolicy (..),
ReadOptions (..),
defaultReadOptions,
shouldInferFromSample,
schemaTypeMap,
typeInferenceSampleSize,
resolveSelection,
InvalidReadOptions (..),
readOptionsErrors,
validateReadOptions,
) where
import qualified Data.List as L
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Control.Exception (Exception, throw, throwIO)
import Control.Monad (unless)
import DataFrame.Errors (DataFrameException (ColumnsNotFoundException))
import DataFrame.Operations.Typing (SafeReadMode (..))
import DataFrame.Schema (SchemaType)
data
=
|
UseFirstRow
|
ProvideNames [T.Text]
deriving (HeaderSpec -> HeaderSpec -> Bool
(HeaderSpec -> HeaderSpec -> Bool)
-> (HeaderSpec -> HeaderSpec -> Bool) -> Eq HeaderSpec
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: HeaderSpec -> HeaderSpec -> Bool
== :: HeaderSpec -> HeaderSpec -> Bool
$c/= :: HeaderSpec -> HeaderSpec -> Bool
/= :: HeaderSpec -> HeaderSpec -> Bool
Eq, Int -> HeaderSpec -> ShowS
[HeaderSpec] -> ShowS
HeaderSpec -> String
(Int -> HeaderSpec -> ShowS)
-> (HeaderSpec -> String)
-> ([HeaderSpec] -> ShowS)
-> Show HeaderSpec
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> HeaderSpec -> ShowS
showsPrec :: Int -> HeaderSpec -> ShowS
$cshow :: HeaderSpec -> String
show :: HeaderSpec -> String
$cshowList :: [HeaderSpec] -> ShowS
showList :: [HeaderSpec] -> ShowS
Show)
data TypeSpec
=
InferFromSample Int
|
SpecifyTypes [(T.Text, SchemaType)] TypeSpec
|
NoInference
data RaggedRowPolicy
=
PadWithNull
|
Truncate
|
RaiseOnRagged
deriving (RaggedRowPolicy -> RaggedRowPolicy -> Bool
(RaggedRowPolicy -> RaggedRowPolicy -> Bool)
-> (RaggedRowPolicy -> RaggedRowPolicy -> Bool)
-> Eq RaggedRowPolicy
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RaggedRowPolicy -> RaggedRowPolicy -> Bool
== :: RaggedRowPolicy -> RaggedRowPolicy -> Bool
$c/= :: RaggedRowPolicy -> RaggedRowPolicy -> Bool
/= :: RaggedRowPolicy -> RaggedRowPolicy -> Bool
Eq, Int -> RaggedRowPolicy -> ShowS
[RaggedRowPolicy] -> ShowS
RaggedRowPolicy -> String
(Int -> RaggedRowPolicy -> ShowS)
-> (RaggedRowPolicy -> String)
-> ([RaggedRowPolicy] -> ShowS)
-> Show RaggedRowPolicy
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RaggedRowPolicy -> ShowS
showsPrec :: Int -> RaggedRowPolicy -> ShowS
$cshow :: RaggedRowPolicy -> String
show :: RaggedRowPolicy -> String
$cshowList :: [RaggedRowPolicy] -> ShowS
showList :: [RaggedRowPolicy] -> ShowS
Show)
data UnclosedQuotePolicy
=
RaiseOnUnclosedQuote
|
BestEffort
deriving (UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool
(UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool)
-> (UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool)
-> Eq UnclosedQuotePolicy
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool
== :: UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool
$c/= :: UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool
/= :: UnclosedQuotePolicy -> UnclosedQuotePolicy -> Bool
Eq, Int -> UnclosedQuotePolicy -> ShowS
[UnclosedQuotePolicy] -> ShowS
UnclosedQuotePolicy -> String
(Int -> UnclosedQuotePolicy -> ShowS)
-> (UnclosedQuotePolicy -> String)
-> ([UnclosedQuotePolicy] -> ShowS)
-> Show UnclosedQuotePolicy
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> UnclosedQuotePolicy -> ShowS
showsPrec :: Int -> UnclosedQuotePolicy -> ShowS
$cshow :: UnclosedQuotePolicy -> String
show :: UnclosedQuotePolicy -> String
$cshowList :: [UnclosedQuotePolicy] -> ShowS
showList :: [UnclosedQuotePolicy] -> ShowS
Show)
data ReadOptions = ReadOptions
{ :: HeaderSpec
, ReadOptions -> TypeSpec
typeSpec :: TypeSpec
, ReadOptions -> SafeReadMode
safeRead :: SafeReadMode
, ReadOptions -> [(Text, SafeReadMode)]
safeReadOverrides :: [(T.Text, SafeReadMode)]
, ReadOptions -> String
dateFormat :: String
, ReadOptions -> Char
columnSeparator :: Char
, ReadOptions -> Maybe Int
numRowsToRead :: Maybe Int
, ReadOptions -> Maybe [Text]
readColumns :: Maybe [T.Text]
, ReadOptions -> [Text]
missingIndicators :: [T.Text]
, ReadOptions -> RaggedRowPolicy
fastCsvOnRaggedRow :: RaggedRowPolicy
, ReadOptions -> UnclosedQuotePolicy
fastCsvOnUnclosedQuote :: UnclosedQuotePolicy
, ReadOptions -> Bool
fastCsvTrimUnquoted :: Bool
}
shouldInferFromSample :: TypeSpec -> Bool
shouldInferFromSample :: TypeSpec -> Bool
shouldInferFromSample (InferFromSample Int
_) = Bool
True
shouldInferFromSample (SpecifyTypes [(Text, SchemaType)]
_ TypeSpec
fallback) = TypeSpec -> Bool
shouldInferFromSample TypeSpec
fallback
shouldInferFromSample TypeSpec
_ = Bool
False
schemaTypeMap :: TypeSpec -> M.Map T.Text SchemaType
schemaTypeMap :: TypeSpec -> Map Text SchemaType
schemaTypeMap (SpecifyTypes [(Text, SchemaType)]
xs TypeSpec
_) = [(Text, SchemaType)] -> Map Text SchemaType
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(Text, SchemaType)]
xs
schemaTypeMap TypeSpec
_ = Map Text SchemaType
forall k a. Map k a
M.empty
newtype InvalidReadOptions = InvalidReadOptions [T.Text]
instance Show InvalidReadOptions where
show :: InvalidReadOptions -> String
show (InvalidReadOptions [Text]
problems) =
Text -> String
T.unpack
(Text -> [Text] -> Text
T.intercalate Text
"\n" (Text
"Invalid ReadOptions:" Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text
" - " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) [Text]
problems))
instance Exception InvalidReadOptions
readOptionsErrors :: ReadOptions -> [T.Text]
readOptionsErrors :: ReadOptions -> [Text]
readOptionsErrors ReadOptions
opts =
[[Text]] -> [Text]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
[ [ Text
"readColumns is an empty selection; omit it to read every column"
| Just [] <- [ReadOptions -> Maybe [Text]
readColumns ReadOptions
opts]
]
, [ Text
"readColumns names "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
dups
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" more than once"
| Bool -> Bool
not ([Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
dups)
]
, [ Text
"numRowsToRead is negative: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show Int
n)
| Just Int
n <- [ReadOptions -> Maybe Int
numRowsToRead ReadOptions
opts]
, Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0
]
, [ Text
"headerSpec is ProvideNames with no names"
| ProvideNames [] <- [ReadOptions -> HeaderSpec
headerSpec ReadOptions
opts]
]
, [ Text
"columnSeparator is " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Char -> String
forall a. Show a => a -> String
show Char
sep) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", which cannot delimit fields"
| let sep :: Char
sep = ReadOptions -> Char
columnSeparator ReadOptions
opts
, Char
sep Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char
'"', Char
'\n', Char
'\r']
]
]
where
dups :: [Text]
dups = [Text] -> ([Text] -> [Text]) -> Maybe [Text] -> [Text]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\[Text]
cs -> [Text] -> [Text]
forall a. Eq a => [a] -> [a]
L.nub ([Text]
cs [Text] -> [Text] -> [Text]
forall a. Eq a => [a] -> [a] -> [a]
L.\\ [Text] -> [Text]
forall a. Eq a => [a] -> [a]
L.nub [Text]
cs)) (ReadOptions -> Maybe [Text]
readColumns ReadOptions
opts)
validateReadOptions :: ReadOptions -> IO ()
validateReadOptions :: ReadOptions -> IO ()
validateReadOptions ReadOptions
opts =
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
problems) (InvalidReadOptions -> IO ()
forall e a. Exception e => e -> IO a
throwIO ([Text] -> InvalidReadOptions
InvalidReadOptions [Text]
problems))
where
problems :: [Text]
problems = ReadOptions -> [Text]
readOptionsErrors ReadOptions
opts
resolveSelection :: ReadOptions -> [T.Text] -> [(T.Text, Int)]
resolveSelection :: ReadOptions -> [Text] -> [(Text, Int)]
resolveSelection ReadOptions
opts [Text]
names = case ReadOptions -> Maybe [Text]
readColumns ReadOptions
opts of
Maybe [Text]
Nothing -> [(Text, Int)]
indexed
Just [Text]
requested -> case (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Map Text Int -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`M.notMember` Map Text Int
fieldIndex) [Text]
requested of
[] -> [(Text
n, Map Text Int
fieldIndex Map Text Int -> Text -> Int
forall k a. Ord k => Map k a -> k -> a
M.! Text
n) | Text
n <- [Text]
requested]
[Text]
missing -> DataFrameException -> [(Text, Int)]
forall a e. Exception e => e -> a
throw ([Text] -> Text -> [Text] -> DataFrameException
ColumnsNotFoundException [Text]
missing Text
"readCsvWithOpts" [Text]
names)
where
indexed :: [(Text, Int)]
indexed = [Text] -> [Int] -> [(Text, Int)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Text]
names [Int
0 ..]
fieldIndex :: Map Text Int
fieldIndex = [(Text, Int)] -> Map Text Int
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(Text, Int)]
indexed
typeInferenceSampleSize :: TypeSpec -> Int
typeInferenceSampleSize :: TypeSpec -> Int
typeInferenceSampleSize (InferFromSample Int
n) = Int
n
typeInferenceSampleSize (SpecifyTypes [(Text, SchemaType)]
_ TypeSpec
fallback) = TypeSpec -> Int
typeInferenceSampleSize TypeSpec
fallback
typeInferenceSampleSize TypeSpec
_ = Int
0
defaultReadOptions :: ReadOptions
defaultReadOptions :: ReadOptions
defaultReadOptions =
ReadOptions
{ headerSpec :: HeaderSpec
headerSpec = HeaderSpec
UseFirstRow
, typeSpec :: TypeSpec
typeSpec = Int -> TypeSpec
InferFromSample Int
100
, safeRead :: SafeReadMode
safeRead = SafeReadMode
NoSafeRead
, safeReadOverrides :: [(Text, SafeReadMode)]
safeReadOverrides = []
, dateFormat :: String
dateFormat = String
"%Y-%m-%d"
, columnSeparator :: Char
columnSeparator = Char
','
, numRowsToRead :: Maybe Int
numRowsToRead = Maybe Int
forall a. Maybe a
Nothing
, readColumns :: Maybe [Text]
readColumns = Maybe [Text]
forall a. Maybe a
Nothing
, missingIndicators :: [Text]
missingIndicators =
[Text
"Nothing", Text
"NULL", Text
"", Text
" ", Text
"nan", Text
"null", Text
"N/A", Text
"NaN", Text
"NAN", Text
"NA"]
, fastCsvOnRaggedRow :: RaggedRowPolicy
fastCsvOnRaggedRow = RaggedRowPolicy
PadWithNull
, fastCsvOnUnclosedQuote :: UnclosedQuotePolicy
fastCsvOnUnclosedQuote = UnclosedQuotePolicy
RaiseOnUnclosedQuote
, fastCsvTrimUnquoted :: Bool
fastCsvTrimUnquoted = Bool
False
}