module Drasil.Data.Formats.CSV.Core
(
CSV,
ColumnCount,
RowCount,
header,
rows,
columnCount,
rowCount,
mkCSV,
)
where
import Data.List (find)
import Data.Text (Text)
import Numeric.Natural (Natural)
type ColumnCount = Natural
type RowCount = Natural
data CSV = CSV
{ :: Maybe [Text],
CSV -> [[Text]]
_rows :: [[Text]],
CSV -> Natural
_columnCount :: ColumnCount,
CSV -> Natural
_rowCount :: RowCount
}
deriving (Int -> CSV -> ShowS
[CSV] -> ShowS
CSV -> [Char]
(Int -> CSV -> ShowS)
-> (CSV -> [Char]) -> ([CSV] -> ShowS) -> Show CSV
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CSV -> ShowS
showsPrec :: Int -> CSV -> ShowS
$cshow :: CSV -> [Char]
show :: CSV -> [Char]
$cshowList :: [CSV] -> ShowS
showList :: [CSV] -> ShowS
Show, CSV -> CSV -> Bool
(CSV -> CSV -> Bool) -> (CSV -> CSV -> Bool) -> Eq CSV
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CSV -> CSV -> Bool
== :: CSV -> CSV -> Bool
$c/= :: CSV -> CSV -> Bool
/= :: CSV -> CSV -> Bool
Eq)
header :: CSV -> Maybe [Text]
= CSV -> Maybe [Text]
_header
{-# INLINE header #-}
rows :: CSV -> [[Text]]
rows :: CSV -> [[Text]]
rows = CSV -> [[Text]]
_rows
{-# INLINE rows #-}
columnCount :: CSV -> ColumnCount
columnCount :: CSV -> Natural
columnCount = CSV -> Natural
_columnCount
{-# INLINE columnCount #-}
rowCount :: CSV -> RowCount
rowCount :: CSV -> Natural
rowCount = CSV -> Natural
_rowCount
{-# INLINE rowCount #-}
mkCSV :: Maybe ColumnCount -> Maybe [Text] -> [[Text]] -> Either String CSV
mkCSV :: Maybe Natural -> Maybe [Text] -> [[Text]] -> Either [Char] CSV
mkCSV Maybe Natural
mcols Maybe [Text]
mhr [[Text]]
rs = Either [Char] CSV
-> ([Char] -> Either [Char] CSV)
-> Maybe [Char]
-> Either [Char] CSV
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (CSV -> Either [Char] CSV
forall a b. b -> Either a b
Right (CSV -> Either [Char] CSV) -> CSV -> Either [Char] CSV
forall a b. (a -> b) -> a -> b
$ Maybe [Text] -> [[Text]] -> Natural -> Natural -> CSV
CSV Maybe [Text]
mhr [[Text]]
rs Natural
cc ([[Text]] -> Natural
forall n a. Integral n => [a] -> n
len [[Text]]
rs)) [Char] -> Either [Char] CSV
forall a b. a -> Either a b
Left ((Natural, [Char]) -> Maybe [Text] -> [[Text]] -> Maybe [Char]
saneLengths (Natural
cc, [Char]
src) Maybe [Text]
mhr [[Text]]
rs)
where
(Natural
cc, [Char]
src) = Maybe Natural -> Maybe [Text] -> [[Text]] -> (Natural, [Char])
expectedColumnCount Maybe Natural
mcols Maybe [Text]
mhr [[Text]]
rs
saneLengths :: (Natural, String) -> Maybe [Text] -> [[Text]] -> Maybe String
saneLengths :: (Natural, [Char]) -> Maybe [Text] -> [[Text]] -> Maybe [Char]
saneLengths (Natural
expLen, [Char]
expLenSrc) Maybe [Text]
mhr [[Text]]
rs =
case Maybe [Text]
mhr of
Just [Text]
hdr | let l :: Natural
l = [Text] -> Natural
forall n a. Integral n => [a] -> n
len [Text]
hdr, Natural
l Natural -> Natural -> Bool
forall a. Eq a => a -> a -> Bool
/= Natural
expLen -> [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just ([Char] -> Maybe [Char]) -> [Char] -> Maybe [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> Natural -> [Char]
formatErr [Char]
"Header" Natural
l
Maybe [Text]
_ -> (Natural, Natural) -> [Char]
format ((Natural, Natural) -> [Char])
-> Maybe (Natural, Natural) -> Maybe [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ((Natural, Natural) -> Bool)
-> [(Natural, Natural)] -> Maybe (Natural, Natural)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((Natural -> Natural -> Bool
forall a. Eq a => a -> a -> Bool
/= Natural
expLen) (Natural -> Bool)
-> ((Natural, Natural) -> Natural) -> (Natural, Natural) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Natural, Natural) -> Natural
forall a b. (a, b) -> b
snd) ([Natural] -> [Natural] -> [(Natural, Natural)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Natural
1 :: Natural ..] ([Text] -> Natural
forall n a. Integral n => [a] -> n
len ([Text] -> Natural) -> [[Text]] -> [Natural]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [[Text]]
rs))
where
formatErr :: [Char] -> Natural -> [Char]
formatErr [Char]
target Natural
actualLen = [[Char]] -> [Char]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
[Char]
target, [Char]
" has ", Natural -> [Char]
forall a. Show a => a -> [Char]
show Natural
actualLen, [Char]
" columns, but expected ",
Natural -> [Char]
forall a. Show a => a -> [Char]
show Natural
expLen, [Char]
" (based on ", [Char]
expLenSrc, [Char]
")"
]
format :: (Natural, Natural) -> [Char]
format (Natural
i, Natural
l) = [Char] -> Natural -> [Char]
formatErr ([Char]
"Row " [Char] -> ShowS
forall a. Semigroup a => a -> a -> a
<> Natural -> [Char]
forall a. Show a => a -> [Char]
show Natural
i) Natural
l
expectedColumnCount :: Maybe ColumnCount -> Maybe [Text] -> [[Text]] -> (Natural, String)
expectedColumnCount :: Maybe Natural -> Maybe [Text] -> [[Text]] -> (Natural, [Char])
expectedColumnCount (Just Natural
cols) Maybe [Text]
_ [[Text]]
_ = (Natural
cols, [Char]
"expected columns input")
expectedColumnCount Maybe Natural
_ (Just [Text]
header') [[Text]]
_ = ([Text] -> Natural
forall n a. Integral n => [a] -> n
len [Text]
header', [Char]
"header length")
expectedColumnCount Maybe Natural
_ Maybe [Text]
_ ([Text]
fr : [[Text]]
_) = ([Text] -> Natural
forall n a. Integral n => [a] -> n
len [Text]
fr, [Char]
"first row length")
expectedColumnCount Maybe Natural
_ Maybe [Text]
_ [[Text]]
_ = (Natural
0, [Char]
"empty data")
len :: (Integral n) => [a] -> n
len :: forall n a. Integral n => [a] -> n
len = Int -> n
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> n) -> ([a] -> Int) -> [a] -> n
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length
{-# INLINE len #-}