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 -> String
(Int -> CSV -> ShowS)
-> (CSV -> String) -> ([CSV] -> ShowS) -> Show CSV
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CSV -> ShowS
showsPrec :: Int -> CSV -> ShowS
$cshow :: CSV -> String
show :: CSV -> String
$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 String CSV
mkCSV Maybe Natural
mcols Maybe [Text]
mhr [[Text]]
rs = Either String CSV
-> (String -> Either String CSV)
-> Maybe String
-> Either String CSV
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (CSV -> Either String CSV
forall a b. b -> Either a b
Right (CSV -> Either String CSV) -> CSV -> Either String 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)) String -> Either String CSV
forall a b. a -> Either a b
Left ((Natural, String) -> Maybe [Text] -> [[Text]] -> Maybe String
saneLengths (Natural
cc, String
src) Maybe [Text]
mhr [[Text]]
rs)
where
(Natural
cc, String
src) = Maybe Natural -> Maybe [Text] -> [[Text]] -> (Natural, String)
expectedColumnCount Maybe Natural
mcols Maybe [Text]
mhr [[Text]]
rs
saneLengths :: (Natural, String) -> Maybe [Text] -> [[Text]] -> Maybe String
saneLengths :: (Natural, String) -> Maybe [Text] -> [[Text]] -> Maybe String
saneLengths (Natural
expLen, String
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 -> String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Natural -> String
forall {a}. Show a => String -> a -> String
formatErr String
"Header" Natural
l
Maybe [Text]
_ -> (Natural, Natural) -> String
forall {a} {a}. (Show a, Show a) => (a, a) -> String
format ((Natural, Natural) -> String)
-> Maybe (Natural, Natural) -> Maybe String
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) -> [[Text]] -> [Natural]
forall a b. (a -> b) -> [a] -> [b]
map [Text] -> Natural
forall n a. Integral n => [a] -> n
len [[Text]]
rs))
where
formatErr :: String -> a -> String
formatErr String
target a
actualLen = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
String
target, String
" has ", a -> String
forall a. Show a => a -> String
show a
actualLen, String
" columns, but expected ",
Natural -> String
forall a. Show a => a -> String
show Natural
expLen, String
" (based on ", String
expLenSrc, String
")"
]
format :: (a, a) -> String
format (a
i, a
l) = String -> a -> String
forall {a}. Show a => String -> a -> String
formatErr (String
"Row " String -> ShowS
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
i) a
l
expectedColumnCount :: Maybe ColumnCount -> Maybe [Text] -> [[Text]] -> (Natural, String)
expectedColumnCount :: Maybe Natural -> Maybe [Text] -> [[Text]] -> (Natural, String)
expectedColumnCount (Just Natural
cols) Maybe [Text]
_ [[Text]]
_ = (Natural
cols, String
"expected columns input")
expectedColumnCount Maybe Natural
_ (Just [Text]
header') [[Text]]
_ = ([Text] -> Natural
forall n a. Integral n => [a] -> n
len [Text]
header', String
"header length")
expectedColumnCount Maybe Natural
_ Maybe [Text]
_ ([Text]
fr : [[Text]]
_) = ([Text] -> Natural
forall n a. Integral n => [a] -> n
len [Text]
fr, String
"first row length")
expectedColumnCount Maybe Natural
_ Maybe [Text]
_ [[Text]]
_ = (Natural
0, String
"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 #-}